Python 进阶指南(编程轻松进阶):十六面向对象编程和继承
Posted 布客飞龙
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python 进阶指南(编程轻松进阶):十六面向对象编程和继承相关的知识,希望对你有一定的参考价值。
定义一个函数,并从几个地方调用它,可以省去复制和粘贴源代码的麻烦。不复制代码是一个很好的实践,因为如果你需要修改它(无论是为了修复一个错误还是添加新特性),你只需要在一个地方修改它。没有重复的代码,程序也更短,更容易阅读。
类似于函数,继承是一种代码重用技术,您可以将其应用于类。这是将类置于父子关系中的行为,在这种关系中,子类继承父类方法的副本,使您不必在多个类中复制一个方法。
许多程序员认为继承被高估了,甚至是危险的,因为大量的继承类增加了程序的复杂性。以“继承是邪恶的”为标题的博客文章并非完全不着边际;继承当然容易被过度使用。但是在组织代码时,有限地使用这种技术可以节省大量时间。
继承是如何工作的
要创建一个新的子类,可以将现有父类的名称放在class
语句的括号中。要练习创建子类,请打开一个新的文件编辑器窗口,并输入以下代码;保存为inheritanceExample.py
:
class ParentClass: # 1
def printHello(self): # 2
print('Hello, world!')
class ChildClass(ParentClass): # 3
def someNewMethod(self):
print('ParentClass objects don't have this method.')
class GrandchildClass(ChildClass): # 4
def anotherNewMethod(self):
print('Only GrandchildClass objects have this method.')
print('Create a ParentClass object and call its methods:')
parent = ParentClass()
parent.printHello()
print('Create a ChildClass object and call its methods:')
child = ChildClass()
child.printHello()
child.someNewMethod()
print('Create a GrandchildClass object and call its methods:')
grandchild = GrandchildClass()
grandchild.printHello()
grandchild.someNewMethod()
grandchild.anotherNewMethod()
print('An error:')
parent.someNewMethod()
运行该程序时,输出应该如下所示:
Create a ParentClass object and call its methods:
Hello, world!
Create a ChildClass object and call its methods:
Hello, world!
ParentClass objects don't have this method.
Create a GrandchildClass object and call its methods:
Hello, world!
ParentClass objects don't have this method.
Only GrandchildClass objects have this method.
An error:
Traceback (most recent call last):
File "inheritanceExample.py", line 35, in <module>
parent.someNewMethod() # ParentClass objects don't have this method.
AttributeError: 'ParentClass' object has no attribute 'someNewMethod'
我们创建了三个名为ParentClass
1 、ChildClass
3 和GrandchildClass
4 的类。ChildClass
子类 ParentClass
,意味着ChildClass
将拥有与ParentClass
相同的所有方法。我们说ChildClass
继承了ParentClass
的方法。另外,GrandchildClass
继承了ChildClass
的子类,因此它拥有与ChildClass
及其父类ParentClass
相同的所有方法。
使用这种技术,我们已经有效地将printHello()
方法 2 的代码复制并粘贴到了ChildClass
和GrandchildClass
类中。我们对printHello()
中代码的任何更改不仅会更新ParentClass
,还会更新ChildClass
和GrandchildClass
。这与更改函数中的代码会更新其所有函数调用是一样的。你可以在图 16-1 中看到这个关系。注意,在类图中,箭头是从子类指向基类的。这反映了一个事实,即一个类总是知道它的基类,但不知道它的子类。
图 16-1:一个层次图(左)和文氏图(右)显示了三个类和它们拥有的方法之间的关系
人们常说父子类代表“是一种”关系。一个ChildClass
对象是一个ParentClass
对象,因为它拥有与一个ParentClass
对象相同的所有方法,包括一些它定义的额外方法。这种关系是单向的:ParentClass
对象不是ChildClass
对象。如果一个ParentClass
对象试图调用someNewMethod()
,它只存在于ChildClass
对象(以及ChildClass
的子类),Python 会抛出一个AttributeError
。
程序员通常认为相关的类必须适应现实世界中的“是”层次结构。OOP 教程一般有Vehicle``FourWheelVehicle▶``Car``Animal``Bird``Sparrow
,或者Shape``Rectangle``Square
的父类、子类、孙类。但是请记住,继承的主要目的是代码重用。如果您的程序需要一个具有一组方法的类,这些方法是其他类的方法的完全超集,继承允许您避免复制和粘贴代码。
我们有时也称子类为子类或派生类,称父类为超类或基类。
覆盖方法
子类继承其父类的所有方法。但是子类可以通过用自己的代码提供自己的方法来覆盖继承的方法。子类的覆盖方法将与父类的方法同名。
为了说明这个概念,让我们回到上一章创建的井字棋。这一次,我们将创建一个新类MiniBoard
,它继承了TTTBoard
并覆盖了getBoardStr()
,以提供一个更小的井字棋棋盘。程序会询问玩家使用哪种风格的棋盘。我们不需要复制和粘贴其余的TTTBoard
方法,因为MiniBoard
将继承它们。
将以下内容添加到您的tictactoe_oop.py
文件的末尾,以创建原始TTTBoard
类的子类,然后覆盖getBoardStr()
方法:
class MiniBoard(TTTBoard):
def getBoardStr(self):
"""Return a tiny text-representation of the board."""
# Change blank spaces to a '.'
for space in ALL_SPACES:
if self._spaces[space] == BLANK:
self._spaces[space] = '.'
boardStr = f'''
self._spaces['1']self._spaces['2']self._spaces['3'] 123
self._spaces['4']self._spaces['5']self._spaces['6'] 456
self._spaces['7']self._spaces['8']self._spaces['9'] 789'''
# Change '.' back to blank spaces.
for space in ALL_SPACES:
if self._spaces[space] == '.':
self._spaces[space] = BLANK
return boardStr
与TTTBoard
类的getBoardStr()
方法一样,MiniBoard
的getBoardStr()
方法创建了一个井字棋棋盘的多行字符串,在传递给print()
函数时显示。但是这个字符串要小得多,放弃了 X 和 O 标记之间的线,使用点号来表示空格。
更改main()
中的行,使其实例化一个MiniBoard
对象,而不是一个TTTBoard
对象:
if input('Use mini board? Y/N: ').lower().startswith('y'):
gameBoard = MiniBoard() # Create a MiniBoard object.
else:
gameBoard = TTTBoard() # Create a TTTBoard object.
除了对main()
的这一行修改,程序的其余部分和以前一样。当您现在运行该程序时,输出将如下所示:
Welcome to Tic-Tac-Toe!
Use mini board? Y/N: y
... 123
... 456
... 789
What is X's move? (1-9)
1
X.. 123
... 456
... 789
What is O's move? (1-9)
`--snip--`
XXX 123
.OO 456
O.X 789
X has won the game!
Thanks for playing!
您的程序现在可以轻松地拥有这两个井字棋棋盘类的实现。当然,如果你只想要迷你版的板,你可以简单地替换TTTBoard
的getBoardStr()
方法中的代码。但是如果你需要和,继承可以让你通过重用它们的公共代码轻松地创建两个类。
如果我们不使用继承,我们可以给TTTBoard
添加一个名为useMiniBoard
的新属性,并在getBoardStr()
中放置一个if-else
语句来决定何时显示常规面板或迷你面板。对于这样一个简单的改变,这将很好地工作。但是如果MiniBoard
子类需要覆盖 2、3 甚至 100 个方法呢?如果我们想创建几个不同的TTTBoard
子类会怎么样?不使用继承会导致我们的方法中的if-else
语句爆炸,并大大增加代码的复杂性。通过使用子类和覆盖方法,我们可以更好地将代码组织成单独的类,以处理这些不同的用例。
super()
函数
子类的覆盖方法通常类似于父类的方法。尽管继承是一种代码重用技术,但覆盖方法可能会导致您覆盖父类方法中的相同代码,作为子类方法的一部分。为了防止这种重复代码,内置的super()
函数允许一个覆盖方法调用父类中的原始方法。
例如,让我们创建一个名为HintBoard
的新类,它是TTTBoard
的子类。新的类覆盖了getBoardStr()
,所以在画了井字棋棋盘之后,它还添加了一个提示,如果 X 或 O 能在他们的下一步棋中获胜。这意味着HintBoard
类的getBoardStr()
方法必须做所有与TTTBoard
类的getBoardStr()
方法绘制井字棋棋盘相同的任务。我们可以使用super()
从HintBoard
类的getBoardStr()
方法中调用TTTBoard
类的getBoardStr()
方法,而不是重复代码来完成这项工作。将以下内容添加到您的tictactoe_oop.py
文件的末尾:
class HintBoard(TTTBoard):
def getBoardStr(self):
"""Return a text-representation of the board with hints."""
boardStr = super().getBoardStr() # Call getBoardStr() in TTTBoard. # 1
xCanWin = False
oCanWin = False
originalSpaces = self._spaces # Backup _spaces. # 2
for space in ALL_SPACES: # Check each space:
# Simulate X moving on this space:
self._spaces = copy.copy(originalSpaces)
if self._spaces[space] == BLANK:
self._spaces[space] = X
if self.isWinner(X):
xCanWin = True
# Simulate O moving on this space:
self._spaces = copy.copy(originalSpaces) # 3
if self._spaces[space] == BLANK:
self._spaces[space] = O
if self.isWinner(O):
oCanWin = True
if xCanWin:
boardStr += '\\nX can win in one more move.'
if oCanWin:
boardStr += '\\nO can win in one more move.'
self._spaces = originalSpaces
return boardStr
首先,super().getBoardStr()
1 运行父TTTBoard
类的getBoardStr()
内部的代码,返回井字棋盘的字符串。我们暂时将这个字符串保存在一个名为boardStr
的变量中。使用通过重用TTTBoard
类的getBoardStr()
创建的棋盘字符串,该方法中的其余代码处理提示的生成。然后,getBoardStr()
方法将xCanWin
和oCanWin
变量设置为False
,并将self._spaces
字典备份为originalSpaces
变量 2 。然后一个for
循环在从'1'
到'9'
的所有棋盘空间上循环。在循环内部,self._spaces
属性被设置为一个originalSpaces
字典的副本,如果正在循环的当前空格为空,则在那里放置一个 X。这模拟了 X 在这个空白空间上的下一步移动。对self.isWinner()
的调用将确定这是否是一个赢棋,如果是,则将xCanWin
设置为True
。然后对 O 重复这些步骤,看看 O 是否能在这个空间上移动 3 获胜。这个方法使用copy
模块来复制self._spaces
中的字典,所以在tictactoe.py
的顶部添加下面一行:
import copy
接下来,更改main()
中的行,使其实例化一个HintBoard
对象,而不是一个TTTBoard
对象:
gameBoard = HintBoard() # Create a TTT board object.
除了对main()
的这一行修改之外,程序的其余部分和以前完全一样。当您现在运行该程序时,输出将如下所示:
Welcome to Tic-Tac-Toe!
`--snip--`
X| | 1 2 3
-+-+-
| |O 4 5 6
-+-+-
| |X 7 8 9
X can win in one more move.
What is O's move? (1-9)
5
X| | 1 2 3
-+-+-
|O|O 4 5 6
-+-+-
| |X 7 8 9
O can win in one more move.
`--snip--`
The game is a tie!
Thanks for playing!
在该方法结束时,如果xCanWin
或oCanWin
是True
,一个附加的声明消息被添加到boardStr
字符串中。最后,boardStr
又回来了。
不是每个被覆盖的方法都需要使用super()
!如果一个类的覆盖方法做的事情与父类中被覆盖的方法完全不同,就没有必要使用super()
调用被覆盖的方法。当一个类有不止一个父方法时,super()
函数特别有用,这将在本章后面的“多重继承”中解释。
首选组合而非继承
继承对于代码重用来说是一项伟大的技术,您可能希望在所有的类中立即开始使用它。但是您可能不总是希望基类和子类如此紧密地耦合。创建多层次的继承不会给你的代码增加组织性,反而会增加官僚主义。
虽然您可以对具有“是”关系的类使用继承(换句话说,当子类是一种父类时),但是对具有“有”关系的类使用一种称为组合的技术通常是有利的。组合是一种类设计技术,它将对象包含在类中,而不是继承那些对象的类。这就是我们在给类添加属性时所做的事情。当使用继承设计你的类时,支持组合而不是继承。这就是我们在本章和上一章的所有例子中所做的,如下表所述:
- 一个对象“有”一定数量的大帆船、镰刀和克努特硬币。
- 一个对象“有”一组九个空格。
- 一个
MiniBoard
对象是一个TTTBoard
对象,所以它也“有”一组九个空格。 - 一个
HintBoard
对象是一个TTTBoard
对象,所以它也“有”一组九个空格。
让我们回到上一章的WizCoin
类。如果我们创建一个新的WizardCustomer
类来代表巫师世界中的顾客,这些顾客将携带一定数量的钱,我们可以通过WizCoin
类来表示这些钱。但是这两个阶级之间没有“是一”的关系;一个WizardCustomer
对象不是一种WizCoin
对象。如果我们使用继承,可能会产生一些笨拙的代码:
import wizcoin
class WizardCustomer(wizcoin.WizCoin): # 1
def __init__(self, name):
self.name = name
super().__init__(0, 0, 0)
wizard = WizardCustomer('Alice')
print(f'wizard.name has wizard.value() knuts worth of money.')
print(f'wizard.name\\'s coins weigh wizard.weightInGrams() grams.')
在这个例子中,WizardCustomer
继承了一个WizCoin
1 对象的方法,比如value()
和weightInGrams()
。从技术上来说,继承自WizCoin
的WizardCustomer
可以完成与包含WizCoin
对象作为属性的WizardCustomer
相同的任务。但是wizard.value()
和wizard.weightInGrams()
方法的名字有误导性:它们似乎会返回巫师的价值和重量,而不是巫师硬币的价值和重量。此外,如果我们后来想为向导的权重添加一个weightInGrams()
方法,那么这个方法名已经被占用了。
最好有一个WizCoin
对象作为属性,因为一个巫师顾客“有”一定数量的巫师硬币:
import wizcoin
class WizardCustomer:
def __init__(self, name):
self.name = name
self.purse = wizcoin.WizCoin(0, 0, 0) # 1
wizard = WizardCustomer('Alice')
print(f'wizard.name has wizard.purse.value() knuts worth of money.')
print(f'wizard.name\\'s coins weigh wizard.purse.weightInGrams() grams.')
我们没有让WizardCustomer
类从WizCoin
继承方法,而是给了WizardCustomer
类一个purse
属性 1 ,其中包含一个WizCoin
对象。当使用组合时,对WizCoin
类方法的任何改变都不会改变WizardCustomer
类的方法。这种技术为两个类的未来设计变更提供了更大的灵活性,并使代码更易于维护。
继承的缺点
继承的主要缺点是,将来您对父类所做的任何更改必然会被它的所有子类继承。在大多数情况下,这种紧密耦合正是您想要的。但是在某些情况下,您的代码需求不太适合您的继承模型。
例如,假设我们在一个车辆模拟程序中有Car
、Motorcycle
和LunarRover
类。他们都需要类似的方法,比如startIgnition()
和changeTire()
。我们可以创建一个父类Vehicle
,并让Car
、Motorcycle
和LunarRover
继承它,而不是将这些代码复制并粘贴到每个类中。现在,如果我们需要修复一个 bug,比如说,changeTire()
方法,我们只需要在一个地方进行修改。如果我们有几十个继承自Vehicle
的不同的车辆相关类,这尤其有用。这些类的代码如下所示:
class Vehicle:
def __init__(self):
print('Vehicle created.')
def startIgnition(self):
pass # Ignition starting code goes here.
def changeTire(self):
pass # Tire changing code goes here.
class Car(Vehicle):
def __init__(self):
print('Car created.')
class Motorcycle(Vehicle):
def __init__(self):
print('Motorcycle created.')
class LunarRover(Vehicle):
def __init__(self):
print('LunarRover created.')
但是将来对Vehicle
的所有更改也会影响这些子类。如果我们需要一个changeSparkPlug()
方法会怎么样?汽车和摩托车有带火花塞的内燃机,但月球车没有。通过支持组合胜过继承,我们可以创建单独的CombustionEngine
和ElectricEngine
类。然后我们设计了Vehicle
类,这样它就“有一个”engine attribute, either a
CombustionEngine或
ElectricEngine对象,使用适当的方法:
class CombustionEngine:
def __init__(self):
print('Combustion engine created.')
def changeSparkPlug(self):
pass # Spark plug changing code goes here.
class ElectricEngine:
def __init__(self):
print('Electric engine created.')
class Vehicle:
def __init__(self):
print('Vehicle created.')
self.engine = CombustionEngine() # Use this engine by default.
`--snip--`
class LunarRover(Vehicle):
def __init__(self):
print('LunarRover created.')
self.engine = ElectricEngine()
这可能需要重写大量的代码,特别是如果你有几个继承自先前存在的Vehicle
类的类:对于Vehicle
类或其子类的每个对象,所有的vehicleObj.changeSparkPlug()
调用都需要变成vehicleObj.engine.changeSparkPlug()
。因为如此大的变化可能会引入错误,所以您可能希望简单地让LunarVehicle
的changeSparkPlug()
方法什么都不做。在这种情况下,Python 风格的方法是在LunarVehicle
类内将changeSparkPlug
设置为None
:
class LunarRover(Vehicle):
changeSparkPlug = None
def __init__(self):
print('LunarRover created.')
changeSparkPlug = None
行遵循本章后面的“类属性”中描述的语法。这覆盖了从Vehicle
继承的changeSparkPlug()
方法,所以用LunarRover
对象调用它会导致错误:
>>> myVehicle = LunarRover()
LunarRover created.
>>> myVehicle.changeSparkPlug()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not callable
如果我们试图用一个LunarRover
对象调用这个不合适的方法,这个错误允许我们快速失败并立即发现问题。任何LunarRover
的子类也为changeSparkPlug()
继承这个None
值。TypeError: 'NoneType' object is not callable
错误信息告诉我们LunarRover
类的程序员有意将changeSparkPlug()
方法设置为None
。如果一开始就没有这样的方法,我们就会收到一条NameError: name 'changeSparkPlug' is not defined
错误消息。
继承可以创造出复杂矛盾的阶级。用作文来代替通常是有利的。
isinstance()
和issubclass()
函数
当我们需要知道一个对象的类型时,我们可以将该对象传递给内置的type()
函数,如前一章所述。但是如果我们正在做一个对象的类型检查,使用更灵活的内置函数isinstance()
是一个更好的主意。如果对象属于给定类或给定类的子类,则isinstance()
函数将返回True
。在交互式 Shell 中输入以下内容:
>>> class ParentClass:
... pass
...
>>> class ChildClass(ParentClass):
... pass
...
>>> parent = ParentClass() # Create a ParentClass object.
>>> child = ChildClass() # Create a ChildClass object.
>>> isinstance(parent, ParentClass)
True
>>> isinstance(parent, ChildClass)
False
>>> isinstance(child, ChildClass) # 1
True
>>> isinstance(child, ParentClass) # 2
True
注意,isinstance()
表示child
中的ChildClass
对象是ChildClass
1 的实例,也是ParentClass
2 的实例。这是有意义的,因为一个ChildClass
对象是一种ParentClass
对象。
您还可以传递一个类对象元组作为第二个参数,以查看第一个参数是否是元组中的任何一个类:
>>> isinstance(42, (int, str, bool)) # True if 42 is an int, str, or bool.
True
不太常用的issubclass()
内置函数可以识别为第一个参数传递的类对象是否是为第二个参数传递的类对象的子类(或同一个类):
>>> issubclass(ChildClass, ParentClass) # ChildClass subclasses ParentClass.
True
>>> issubclass(ChildClass, str) # ChildClass doesn't subclass str.
False
>>> issubclass(ChildClass, ChildClass) # ChildClass is ChildClass.
True
就像使用isinstance()
一样,您可以将一个类对象元组作为第二个参数传递给issubclass()
,以查看第一个参数是否是元组中任何类的子类。isinstance()
和issubclass()
的关键区别在于issubclass()
被传递了两个类对象,而isinstance()
被传递了一个对象和一个类对象。
类方法
类方法与类相关联,而不是像常规方法那样与单个对象相关联。当您看到两个标记时,您可以在代码中识别出一个类方法:方法的def
语句前的@classmethod
装饰器和使用cls
作为第一个参数,如下例所示。
class ExampleClass:
def exampleRegularMethod(self):
print('This is a regular method.')
@classmethod
def exampleClassMethod(cls):
print('This is a class method.')
# Call the class method without instantiating an object:
ExampleClass.exampleClassMethod()
obj = ExampleClass()
# Given the above line, these two lines are equivalent:
obj.exampleClassMethod()
obj.__class__.exampleClassMethod()
除了self
引用一个对象,而cls
引用一个对象的类之外,cls
参数的行为类似于self
。这意味着类方法中的代码不能访问单个对象的属性或调用对象的常规方法。类方法只能调用其他类方法或访问类属性。我们使用名称cls
是因为class
是一个 Python 关键字,就像其他关键字一样,比如if
、while
或import
,我们不能将其用于参数名称。我们经常通过类对象调用类属性,如在ExampleClass.exampleClassMethod()
中。但是我们也可以通过类的任何对象调用它们,就像在obj.exampleClassMethod()
中一样。
类方法并不常用。最常见的用例是提供除了__init__()
之外的可选构造方法。例如,如果构造器既可以接受新对象需要的数据字符串,也可以接受包含新对象需要的数据的文件名字符串,会怎么样呢?我们不希望__init__()
方法的参数列表冗长而混乱。相反,让我们使用类方法来返回一个新的对象。
例如,让我们创建一个AsciiArt
类。正如你在第 14 章看到的,ASCII 艺术画使用文本字符来形成图像。
class AsciiArt:
def __init__(self, characters):
self._characters = characters
@classmethod
def fromFile(cls, filename):
with open(filename面向对象是一种编程方式,此编程方式的实现是基于对 类 和 对象 的使用
类 是一个模板,模板中包装了多个“函数”供使用(可以讲多函数中公用的变量封装到对象中)
对象,根据模板创建的实例(即:对象),实例用于调用被包装在类中的函数
面向对象三大特性:封装、继承和多态
类的成员
类的成员可以分为三大类:字段、方法和属性
字段里面有普通字段和静态字段,方法里面有普通方法和静态方法和类方法
所有成员中,只有普通字段的内容保存对象中,即:根据此类创建了多少对象,在内存中就有多少个普通字段。而其他的成员,则都是保存在类中,即:无论对象的多少,在内存中只创建一份。
一、字段
字段包括:普通字段和静态字段,他们在定义和使用中有所区别,而最本质的区别是内存中保存的位置不同,
普通字段属于对象
静态字段属于类
字段的定义和使用
class Province(object):
‘‘‘
# 静态字段
‘‘‘
dizhi="广东"
def __init__(self,name):
‘‘‘
普通字段
:param name:
:return:
‘‘‘
self.name=name
# 直接访问普通字段
obj = Province(‘深圳市‘)
print(obj.name)
# 直接访问静态字段
x=Province.dizhi
print(x)
#可以看出【普通字段需要通过对象来访问】【静态字段通过类访问】,在使用上可以看出普通字段和静态字段的归属是不同的
静态字段在内存中只保存一份
普通字段在每个对象中都要保存一份
应用场景: 通过类创建对象时,如果每个对象都具有相同的字段,那么就使用静态字段
二、方法
方法包括:普通方法、静态方法和类方法,三种方法在内存中都归属于类,区别在于调用方式不同。
普通方法:由对象调用;至少一个self参数;执行普通方法时,自动将调用该方法的对象赋值给self;
类方法:由类调用; 至少一个cls参数;执行类方法时,自动将调用该方法的类复制给cls;
静态方法:由类调用;无默认参数;
方法的定义和使用
class foo(object):
def __init__(self,name):
self.name=name
def ord_func(self):
""" 定义普通方法,至少有一个self参数 """
# print self.name
print (‘普通方法‘)
@classmethod
def class_func(cls):
""" 定义类方法,至少有一个cls参数 """
print(‘类方法‘)
@staticmethod
def static_func():
""" 定义静态方法 ,无默认参数"""
print( ‘静态方法‘)
# 调用普通方法
f = foo("gaofushuai")
f.ord_func()
# 调用类方法
foo.class_func()
# 调用静态方法
foo.static_func()
相同点:对于所有的方法而言,均属于类(非对象)中,所以,在内存中也只保存一份。
不同点:方法调用者不同、调用方法时自动传入的参数不同。
三、属性
如果你已经了解Python类中的方法,那么属性就非常简单了,因为Python中的属性其实是普通方法的变种。
对于属性,有以下三个知识点:
属性的基本使用
属性的两种定义方式
1、属性的基本使用
# 定义
class foo(object):
def func(self):
pass
# 定义属性
@property
def prop(self):
pass
# 调用
obj=foo()
obj.func()
obj.prop#调用属性
由属性的定义和调用要注意一下几点:
定义时,在普通方法的基础上添加 @property 装饰器;
定义时,属性仅有一个self参数
调用时,无需括号
方法:obj.func()
属性:obj.prop
注意:属性存在意义是:访问属性时可以制造出和访问字段完全相同的假象
属性由方法变种而来,如果Python中没有属性,方法完全可以代替其功能。
实例:对于主机列表页面,每次请求不可能把数据库中的所有内容都显示到页面上,而是通过分页的功能局部显示,所以在向数据库中请求数据时就要显示的指定获取从第m条到第n条的所有数据(即:limit m,n),这个分页的功能包括:
根据用户请求的当前页和总数据条数计算出 m 和 n
根据m 和 n 去数据库中请求数据
class abc(object):
def __init__(self,dangqianye):
# 用户当前请求的页码(第一页、第二页...)
self.dangqianye=dangqianye
# 每页默认显示10条数据
self.per_items = 10
@property
def start(self):
val=(self.dangqianye-1)*self.per_items
return val
@property
def end(self):
val=self.dangqianye*self.per_items
return val
p=abc(1)
p.start#就是起始值
p.end#就是结束值
2、属性的两种定义方式
属性的定义有两种方式:
装饰器 即:在方法上应用装饰器
静态字段 即:在类中定义值为property对象的静态字段
装饰器方式:在类的普通方法上应用@property装饰器
经典类,具有一种@property装饰器
# ############### 定义 ###############
class Goods(object):
@property
def price(self):
return "wupeiqi"
# ############### 调用 ###############
obj = Goods()
result = obj.price # 自动执行 @property 修饰的 price 方法,并获取方法的返回值
新式类,具有三种@property装饰器
# ############### 定义 ###############
class Goods(object):
@property
def price(self):
print (‘@property‘)
@price.setter
def price(self, value):
print(‘@price.setter‘)
@price.deleter
def price(self):
print (‘@price.deleter‘)
# ############### 调用 ###############
obj = Goods()
obj.price # 自动执行 @property 修饰的 price 方法,并获取方法的返回值
obj.price = 123 # 自动执行 @price.setter 修饰的 price 方法,并将 123 赋值给方法的参数
del obj.price # 自动执行 @price.deleter 修饰的 price 方法
python的反射
#!/usr/bin/env python
# encoding: utf-8
"""
@version: ??
@author: phpergao
@license: Apache Licence
@file: oopfanshe.py
@time: 2016-05-22 17:04
"""
import sys
class webserver(object):
def __init__(self,host,port):
self.host=host
self.port=port
def start(self):
print("server is start")
def stop(self):
print("server is stop")
def restart(self):
self.start()
self.stop()
def testrun(self,name):
print("running.....",name,self.host)
if __name__=="__main__":
server=webserver("127.0.0.1",22)
server2=webserver("192.168.1.1",23)
if hasattr(server,sys.argv[1]):
func=getattr(server,sys.argv[1])#获取内存地址
func()
#delattr(webserver,"start")
#print(server.restart())
以上是关于Python 进阶指南(编程轻松进阶):十六面向对象编程和继承的主要内容,如果未能解决你的问题,请参考以下文章
Python进阶(十六)----面向对象之~封装,多态,鸭子模型,super原理(单继承原理,多继承原理)