面向对象编程踏上了进化的阶梯,增强了架构化编程,实现了数据与动作的融合:数据层和逻辑层现在由一个可以创建这些对象的简单抽象层来描述。现实世界中的问题和实体完全暴露了本质,从中提供的一种抽象,可以用来进行相似编码,或者编入能与系统中对象进行交互的对象中。类提供了这样一些对象的定义,实例即是这些定义的实现。二者对面向对象设计(object-oriented design, OOD)来说都是重要的,OOD仅意味来创建你采用面向对象方式架构来创建系统。
面向对象设计(OOD)不会特别要求面向对象编程语言。OOD可以由纯结构化语言来实现,比如C。当一门语言内建OO特性,OO编程开会更加方便高效。
Python从一开始设计就是面向对象的,但并非日常编程所必须。Python面向对象编程(OOP)具有强大能力,在Python中使用OOP可以提高许多效率。
Python 类使用class关键字来创建。
class ClassNmae(bases):
'class documentation string' # '类文档字符串'
class_suite # 类体
bases 是基类。
对于Python,声明与定义没什么区别。
属性就是属于另一个对象的数据或者函数元素。
类数据属性仅仅是所定义类的变量。
>>> class C(object):
... foo = 100
...
>>> print(C.foo)
100
>>> C.foo = C.foo + 1
>>> print(C.foo)
101
上面的代码中,看不到任何类实例的引用。
方法,比如下面,类MyClass中的myNoActionMethod方法,仅仅是一个作为类定义一部分定义的函数(这使得方法成为类属性)。这表示myNoActionMethod仅应用在MyClass类型的对象(实例)上。这里,myNoActionMethod是通过句点属性标识法与它的实例绑定的。
>>> class MyClass(object):
... def myNoActionMethod(self):
... pass
...
>>> mc = MyClass()
>>> mc.myNoActionMethod()
任何像函数一样对myNoActionMethod自身调用都将失败:
>>> myNoAction()
Traceback (most recent call last):
File "" , line 1, in <module>
NameError: name 'myNoAction' is not defined
引发了NameError异常,因为全局名字空间中,没有这样的函数存在。
由类对象调用此方法也失败了:
>>> MyClass.myNoActionMethod()
Traceback (most recent call last):
File "" , line 1, in <module>
TypeError: myNoActionMethod() missing 1 required positional argument: 'self'
TypeError异常看起来让人困惑,因为这种方法是类的一个属性。
下面进行解释。
>>> class MyClass(object):
... 'MyClass class definition' # 类定义
... myVersion = '1.1' # 静态数据
... def showMyVersion(self): # 方法
... print(MyClass.myVersion)
...
查看类的属性,有两种方法。
最简单的是使用dir()内建函数。
>>> dir(MyClass)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'myVersion', 'showMyVersion']
另外通过访问类的字典属性__dict__,这是所有类都具备的特殊属性之一。
>>> MyClass.__dict__
mappingproxy({'__module__': '__main__', '__doc__': 'MyClass class definition', 'myVersion': '1.1', 'showMyVersion': <function MyClass.showMyVersion at 0x7fa5e37c5048>, '__dict__': <attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute '__weakref__' of 'MyClass' objects>})
>>> print(MyClass.__dict__)
{'__module__': '__main__', '__doc__': 'MyClass class definition', 'myVersion': '1.1', 'showMyVersion': <function MyClass.showMyVersion at 0x7fa5e37c5048>, '__dict__': <attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute '__weakref__' of 'MyClass' objects>}
可以看出,dir()返回的仅是对象的属性的一个名字列表,而__dict__返回的是一个字典,它的键(key)是属性名,键值(value)是相应的属性对象的数据值。
C.__name__ | 类C的名字(字符串) |
C.__doc__ | 类C的文档字符串 |
C.__bases__ | 类C的所有父类构成的元祖 |
C.__dict__ | 类C的属性 |
C.__module__ | 类C定义所在的模块 |
C.__class__ | 实例C对应的类(仅新式类) |
>>> MyClass.__name__
'MyClass'
>>> MyClass.__doc__
'MyClass class definition'
>>> MyClass.__bases__
(<class 'object'>,)
>>> print(MyClass.__dict__)
{'__module__': '__main__', '__doc__': 'MyClass class definition', 'myVersion': '1.1', 'showMyVersion': <function MyClass.showMyVersion at 0x7fa5e37c5048>, '__dict__': <attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute '__weakref__' of 'MyClass' objects>}
>>> MyClass.__module__
'__main__'
>>> MyClass.__class__
<class 'type'>
__name__是给定类的字符名字。
>>> stype = type('What is your quest?')
>>> stype
<class 'str'>
>>> stype.__name__
'str'
>>>
>>> type(3.14159265)
<class 'float'>
>>> type(3.14159265).__name__ # 得到类型名(字符串表示)
'float'
__doc__是类的文档字符串,与函数及模块的文档字符串相似,必须紧随头行(header line)后的字符串。文档字符串不能被派生类继承。派生类必须含有它们自己的文档字符串。
__bases__用来处理继承,它包含了一个所有父类组成的元祖。
__dict__包含一个字典,由类的数据属性组成。
Python支持模块间的类继承。
>>> class C(object):
... pass
...
>>> C
<class '__main__.C'>
>>> C.__module__
'__main__'
类C的全名是“__mian__.C
”。如果类C位于一个导入的模块中,如mymod,像下面的。
>>> from mymod import C
>>> C
<class 'mymod.C'>
SyntaxError: invalid syntax
>>> C.__module__
'mymod'
如果说类是一种数据结构定义类型,那么实例则声明了一个这样类型的量。
其它的OOP提供new关键字,用过new来创建类的实例。Python的方式更加简单,在定义类后,调用类就创建了一个实例。
>>> class MyClass(object):
... pass
...
>>> mc = MyClass()
当使用函数记法调用一个类时,解释器会实例化该对象,将这个实例返回。
__init__()
“构造器”方法当类被调用,实例化的第一步是创建实例对象。一旦对象创建了,Python检查是否实现了__init__()
方法。默认情况下,如果没有定义(或覆盖)特殊方法__init__()
,对实例不会施加任何特别的操作。任何所需的特定操作,都需要手动实现__init__()
,覆盖它的默认行为。如果__init__()
没有实现,则返回它的对象,实例化过程完毕。
然而,如果__init__()
已经被实现,那么它将被调用,实例对象作为第一个参数(self
)被传递进去,像标准方法调用一样。调用类时,传进的任何参数都交给了__init__()
。实际中,可以想象成这样,把创建实例的调用当成是对构造器的调用。
__new__()
“构造器”方法__del__()
“解构器”方法>>> class P():
... def __del__(self):
... pass
...
>>> class C(P): # 类声明
... def __init__(self): # 构造器
... print('initialized')
... def __del__(self): # 解构器
... P.__del__(self) # 调用父类解构器打印
... print('deleted')
...
>>> c1 = C() # 实例初始化
initialized
>>> c2 = c1 # 创建另外一个别名
>>> c3 = c1 # 创建第三个别名
>>> id(c1), id(c2), id(c3) # 同一对象所有引用
(140445373906056, 140445373906056, 140445373906056)
>>> del c1 # 清除一个引用
>>> del c2 # 清除另外一个引用
>>> del c3 # 清除最终引用
deleted # 解构器最后调用
实例仅拥有数据属性(方法严格来说是类属性)
构造器是最早可以设置实例属性的地方,因为__init__()是实例创建后第一个被调用的方法。再没有比这更早的可以设置实例属性的机会了。一旦__init__()执行完毕,返回实例对象,即完成了实例化过程。
在实际当中,带默认参数的__init__()提供一个有效的方式来初始化实例。
hotel.py
class HotelRoomCalc(object):
'Hotel room rate calculator'
def __init__(self, rt, sales=0.085, rm=0.1):
'''
HotelRoomCalc default arguments:
sales tax == 8.5%
room tax == 10%
'''
self.salesTax = sales
self.roomTax = rm
self.roomRate = rt
def calcTotal(self, days=1):
'Calculate total; default to daily rate'
daily = round((self.roomRate * (1 + self.roomTax + self.salesTax)), 2)
return float(days) * daily
>>> sfo = HotelRoomCalc(299)
>>> sfo.calcTotal()
354.31
>>> sfo.calcTotal(2)
708.62
>>> sea = HotelRoomCalc(189, 0.086, 0.058)
>>> sea.calcTotal()
216.22
>>> sea.calcTotal(4)
864.88
>>> wasWkDay = HotelRoomCalc(169, 0.045, 0.02)
>>> wasWkEnd = HotelRoomCalc(119, 0.045, 0.02)
>>> wasWkDay.calcTotal(5) + wasWkEnd.calcTotal()
1026.6299999999999
>>> class MyClass(object):
... pass
...
>>> mc = MyClass()
>>> mc
<__main__.MyClass object at 0x000000CA65386E80>
>>> class MyClass:
... def __init__(self):
... print('initialized')
... return 1
...
>>> mc = Myclass()
Traceback (most recent call last):
File "", line 1, in <module>
NameError: name 'Myclass' is not defined
>>> class C(object):
... pass
...
>>> c = C()
>>> c.foo = 'roger'
>>> c.bar = 'shrubber'
>>> dir(c)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'bar', 'foo']
>>> c.__dict__
{'foo': 'roger', 'bar': 'shrubber'}
实例仅有两个特殊属性(见下表)。对于任意对象 I:
I.__class__ | 实例化 I 的类 |
I.__dict__ | I 的属性 |
使用类 C 及其实例 c 来看看这些特殊实例属性:
>>> class C(object): # 定义类
... pass
...
>>> c = C() # 创建实例
>>> dir(c) # 返回基类属性,实例还没有属性
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
>>> c.__dict__ # 实例还没有属性
{}
>>> c.__class__ # 实例化 c 的类
<class '__main__.C'>
c 目前还没有数据属性,添加一些再检查 __dict__ 属性。
>>> c.foo = 1
>>> c.bar = 'SPAM'
>>> '%d can of %s please' % (c.foo, c.bar)
'1 can of SPAM please'
>>> c.__dict__
{'foo': 1, 'bar': 'SPAM'}
__dict__ 属性由一个字典组成,包含一个实例的所有属性。键是属性名,值是属性相应的数据值。字典中仅有实例属性,没有类属性或特殊属性。
内建类型也是类。用 dir() 考察它们有没有像类或实例一样的属性。
>>> x = 3 + 0.14j
>>> x.__class__
<class 'complex'>
>>> dir(x)
['__abs__', '__add__', '__bool__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__int__', '__le__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__pos__', '__pow__', '__radd__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rmod__', '__rmul__', '__rpow__', '__rsub__', '__rtruediv__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', 'conjugate', 'imag', 'real']
>>>
>>> [type(getattr(x, i)) for i in ('conjugate', 'imag', 'real')]
[<class 'builtin_function_or_method'>, <class 'float'>, <class 'float'>]
已知一个复数的属性,可以访问它的数据属性,调用它的方法:
>>> x.imag
0.14
>>> x.real
3.0
>>> x.conjugate()
(3-0.14j)
访问 __dict__ 会失败,因为在内建类型中,不存在这个属性:
>>> x.__dict__
Traceback (most recent call last):
File "", line 1, in <module>
AttributeError: 'complex' object has no attribute '__dict__'
>>> class C(object): # 定义类
... version = 1.2 # 静态成员
...
>>> c = C() # 实例化
>>> C.version # 通过类来访问
1.2
>>> c.version # 通过实例来访问
1.2
>>> C.version += 0.1 # 通过类(只能这样)来更新
>>> C.version # 类访问
1.3
>>> c.version # 实例访问
1.3
>>> class Foo(object):
... x = 1.5
...
>>> foo = Foo()
>>> foo.x
1.5
>>> foo.x = 1.7 # 更新类属性
>>> foo.x # 似乎已更新
1.7
>>> Foo.x # 类属性没有变,只是创建了一个新的实例属性
1.5
>>> foo.__dict__
{'x': 1.7}
>>> foo.__dict__['x']
1.7
>>> Foo.__dict__
mappingproxy({'__module__': '__main__', 'x': 1.5, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None})
>>> Foo.__dict__['x']
1.5
上述代码创建了名为 x 的新实例属性,它覆盖了对类属性的引用。
>>> del foo.x
>>> foo.x
1.5
>>> foo.x += .2
>>> foo.x
1.7
>>> Foo.x
1.5
同样创建了一个新的实例属性,类属性原封不动(深入理解 Python 相关知识:属性已存在于类字典 [__dict__] 中。通过赋值,其被加入到实例的 __dict__ 中了)。
在类属性可变的情况下,一切都不同了:
>>> class Foo(object):
... x = {2003: 'poe2'}
...
>>> foo Foo()
File "", line 1
foo Foo()
^
SyntaxError: invalid syntax
>>> foo = Foo()
>>> foo.x
{2003: 'poe2'}
>>> foo.x[2004] = 'valid path'
>>> foo.x
{2003: 'poe2', 2004: 'valid path'}
>>> Foo.x # 生效了!
{2003: 'poe2', 2004: 'valid path'}
>>> del foo.x # 没有遮蔽,所以不能删除掉
Traceback (most recent call last):
File "", line 1, in <module>
AttributeError: x
>>> class C(object):
... spam = 100
...
>>> c1 = C()
>>> c1.spam
100
>>> C.spam += 100
>>> C.spam
200
>>> c1.spam
200
>>> c2 = C()
>>> c2.spam
200
>>> del c1
>>> c2.spam
200
>>> C.spam
200
>>> C.spam += 200
>>> c2.spam
400
【提示】: 使用类属性来修改自身(不是实例属性)
Pythong 中绑定(binding)主要与方法调用相关连。
首先,方法仅仅是类内部定义的函数(这意味着方法是类属性而不是实例属性)。
其次,方法只有在其所属的类拥有实例时,才能被调用。当存在一个实例时,方法才被认为是绑定到那个实例了。没有实例时方法就是未绑定的。
最后,任何一个方法定义中的第一个参数都是变量 self
,它表示调用此方法的实例对象。
笔记:
self
是什么?
self
变量用于在实例方法中引用方法所绑定的实例。因为方法的实例在任何方法调用中总是作为第一个参数传递的,self
被选中用来代表实例。你必须在方法声明中放上self
(你可能已经注意到这点),但可以在方法中不适用实例(self
)。如果你的方法中没有用到self
,那么请考虑创建一个常规函数,除非你有特别的原因。毕竟,你的方法代码没有使用实例,没有与类关联其功能,这使得它看起来更像一个常规函数。在其他面向对象语言中,self
可能被称为this
。
通过实例调用类的方法,即调用绑定的方法。 所属 MyClass 类的方法 foo() 和 实例 mc,调用绑定的方法:mc.foo()。InstanceName.Method() (实例名.方法())即调用绑定的方法。调用绑定的方法 self
不需要明确地传入。
通过类调用类的方法,即调用非绑定的方法。所属 MyClass 类的方法 foo() 和实例 mc,调用非绑定的方法:MyClass.foo(self, …)。ClassName.Method(self, …) (类名.方法(self, …))即调用非绑定的方法。调用非绑定的方法必须传递 self
参数。
调用非绑定方法并不经常用到。需要调用一个还没有任何实例的类中的方法的一个主要场景是:你在派生一个子类,而且你需要覆盖父类的方法,这是你需要调用那个父类中想要覆盖掉的构造方法。这里给出一个例子:
class AddrBookEntry(object):
"""address book entry class"""
def __init__(self, nm, ph):
self.name = nm
self.phone = ph
print('Created instance for:', self.name)
def updatePhone(self, newph):
self.phone = newph
print('Updated phone# for:', self.name)
class EmpAddrBookEntry(AddrBookEntry):
"""Employee Address Book Entry class"""
def __init__(self, nm, ph, em):
AddrBookEntry.__init__(self, nm, ph)
self.empid = id
self.email = em
经典类和新式类(new-style)都可以使用静态方法和类方法。一对内建函数被引入,用于将作为类定义的一部分的某一方法声明“标记”(tag),“强制类型转换”(cast)或者“转换”(convert)为这两种类型的方法之一。
通常的方法需要一个实例(self
)作为第一个参数,并且对于(绑定的)方法调用来说,self
是自动传递给这个方法的。而对于类方法而言,需要类而不是实例作为第一个参数,它是由解释器传给方法。类不需要特别的命名,类似 self
,不过很多人使用 cls
作为变量名字。
在经典类中创建静态方法和类方法的例子(也可用于新式类中):
class TestStaticMethod:
def foo():
print('calling static method foo()')
foo = staticmethod(foo)
class TestClassMethod:
def foo(cls):
print('calling class method foo()')
print('foo() is part of class:', cls.__name__)
foo = classmethod(foo)
对应的内建函数被转换成它们相应的类型,并且重新赋值给了相同的变量名。如果没有调用这两个函数,二者都会在 Python 编译器中产生错误,显示需要带 self
的常规方法声明。现在,我们可以通过类或者实例调用这些函数,这没什么不同:
>>> tsm = TestStaticMethod()
>>> TestStaticMethod.foo()
calling static method foo()
>>> tsm.foo()
calling static method foo()
>>> tcm = TestClassMethod()
>>> TestClassMethod.foo()
calling class method foo()
foo() is part of class: TestClassMethod
>>> tcm.foo()
calling class method foo()
foo() is part of class: TestClassMethod
赋值给不同的变量名,Python 解释器会报错:
class TestStaticMethod:
def foo():
print('calling static method foo()')
faa = staticmethod(foo)
class TestClassMethod:
def foo(cls):
print('calling class method foo()')
print('foo() is part of class:', cls.__name__)
faa = classmethod(foo)
结果如下:
>>> tsm = TestStaticMethod()
>>> TestStaticMethod.foo()
calling static method foo()
>>> TestStaticMethod.faa()
calling static method foo()
>>> tsm.foo()
Traceback (most recent call last):
File "", line 1, in <module>
TypeError: foo() takes 0 positional arguments but 1 was given
>>> tsm.faa()
calling static method foo()
>>> tsm.faa
<function TestStaticMethod.foo at 0x000000170945D048>
>>> tcm = TestClassMethod()
>>> TestClassMethod.foo()
Traceback (most recent call last):
File "", line 1, in <module>
TypeError: foo() missing 1 required positional argument: 'cls'
>>> tcm.foo()
Traceback (most recent call last):
calling class method foo()
File "", line 1, in <module>
File "F:/A1_SLAM/03__projects/learn_python/staticmethod.py", line 11, in foo
print('foo() is part of class:', cls.__name__)
AttributeError: 'TestClassMethod' object has no attribute '__name__'
>>> TestClassMethod.faa()
calling class method foo()
foo() is part of class: TestClassMethod
>>> TestClassMethod.faa
<bound method TestClassMethod.foo of <class '__main__.TestClassMethod'>>
>>> tcm.faa()
calling class method foo()
foo() is part of class: TestClassMethod
>>> tcm.faa
<bound method TestClassMethod.foo of <class '__main__.TestClassMethod'>>
通过使用装饰器,我们可以避免像上面那样的重新赋值:
class TestStaticMethod:
@staticmethod
def foo():
print('calling static method foo()')
class TestClassMethod:
@classmethod
def foo(cls):
print('calling class method foo()')
print('foo() is part of class:', cls.__name__)
一个类被定义后,目标就是要把他当成一个模块使用,并把这些对象嵌入到代码中去,同其他类型及逻辑执行流混合使用。有两种方法可以在代码中利用类。第一种是组合(Composition)。就是让不同的类混合并加入到其他类中,类增加功能和代码的重用性。我可以在一个大点的类中创建我自己的类的实例,实现一些其他属性和方法来增强原来的类对象。另一种方法是通过派生,我们将在下一节中讨论。
举例来说,如果在设计的过程中,为Name,Phone创建了单独的类,那么最后我们可能想把这些工作继承到AddrBookEntry类中去,而不是重新设计每一个需要的类。这样就节省了时间和精力,而且最后的结果是容易维护的代码——块代码中的bug被修正,将反映到整个应用中。
这样的类可能包含以Name实例,再加一个Phone实例,以及其它如StreetAddress、Email(home、work等),还可能需要一些Date实例(birthday、wedding、anniversary等)。
下面给出一个例子
class Name(object):
def __init__(self, name):
self.first_name = name.split(' ')[0]
self.last_name = name.split(' ')[1]
self.full_name = name
class Phone(object):
def __init__(self, phone):
self.phone = phone
class NewAddBookEntry(object):
def __init__(self, nm, ph):
self.name = Name(nm) # 创建 Name 实例
self.full_name = self.name.full_name
self.first_name = self.name.first_name
self.last_name = self.name.last_name
self.phone = Phone(ph) # 创建 Phone 实例
print('Created instance for: Mr.', self.last_name, '--', self.full_name)
if __name__ == '__main__':
new_entry = NewAddBookEntry('James Harden', '18012345678')
输出结果
Created instance for: Mr. Harden -- James Harden
NewAddrBookEntry类由它自身和其它类组合而成。这就在一个类和其它组成类之间定义了一种“有一个”(has-a)的关系。比如,我们的NewAddrBookEntry类“有一个”Name类实例和一个Phone实例。
创建复合对象就可以实现这些附加的功能,并且很有意义,因为这些类都不相同。每一个类管理它们自己的名字空间和行为。不过当对象之间有更接近的关系时,派生的概念可能对我的程序来说更有意义,特别是当我需要一些相似的对象,但却有少许不同功能的时候。
当类之间有显著的不同,并且(较小的类)是较大的类所需要的组件时,组合表现得很好,但当我们设计“相同的类担忧一些不同的功能”时,派生就是一个更加合理的选择了。
OOP的更强大功能之一是能够使用一个已经定义好的类,扩展它或者对其进行修改,而不会影响系统中使用现存类的其他代码片段。OOD允许类特征在子孙类或子类中进行继承。这些子类从基类(或称祖先类、超类)继承它们的核心属性。而且,这些派生可能会扩展到多代。从同一个父类派生出来的这些类(或者是在类树图中水平相邻)是同胞关系。父类和所有高层类都被认为是祖先。
创建子类
创建子类的语法看起来与普通(新式)类没有区别,一个类名,后跟一个或多个需要从其中派生的父类:
class SubClassName(ParentClass1[, ParentClass2, ...]):
"""optional class documentation string"""
class_suite
如果你的类没有从任何祖先类派生,可以使用object作为父类的名字。经典类的声明唯一不同之处在于其没有从祖先类派生——此时,没有圆括号:
class ClassicClassWithouSuperclasses:
pass
至此,我们已经看到了一些类和子类的例子,下面还有一个简单的例子:
class Parent(object): # 调用父类
def parentMethod(self):
print('calling parent method')
class Child(Parent): # 调用子类
def childMethod(self):
print('calling parent method')
p = Parent() # 父类的实例
p.parentMethod()
calling parent method
c = Child()
c.childMethod()
calling parent method
c.parentMethod()
calling parent method
>>> p = Parent() # 父类的实例
>>> p.parentMethod()
calling parent method
>>> c = Child() # 子类的实例
>>> c.childMethod() # 子类调用它的方法
calling parent method
>>> c.parentMethod() # 子类调用父类的方法
calling parent method
继承描述了基类的属性如何“遗传”给派生类。一个子类可以继承它的基类的任何属性,不管是数据属性还是方法。
举个例子如下。P是一个没有属性的简单类。C从P继承而来(因此是它的子类),也没有属性:
class P(object):
pass
class C(P):
pass
>>> c = C()
>>> c.__class__
<class '__main__.C'>
>>> C.__bases__
(<class '__main__.P'>,)
因为P没有属性,C没有继承到什么。下面我们给P添加一些属性:
class P:
"""P class"""
def __init__(self):
print('Created an instance of', self.__class__.__name__)
class C(P):
pass
现在所创建的P有文档字符串(__doc__)和构造器,当我们实例化P时它被执行,如下面的交互会话所示:
>>> p = P()
Created an instance of P
>>> p.__class__
<class '__main__.P'>
>>> P.__bases__
(<class 'object'>,)
“created an instance”是由__init__()直接输出的。我们也可以显示更多关于父类的信息。我们现在来实例化C,展示__init__()(构造)方法在执行过程中是如何继承的:
>>> c = C()
Created an instance of C
>>> c.__class__
<class '__main__.C'>
>>> C.__bases__
(<class '__main__.P'>,)
>>> C.__doc__
>>> c.__doc__
C没有声明__init__()方法,然而在类C的实例c被创建时,还是会有输出信息。原因在于C继承了P的__init__()。__bases__元祖列出了其父类P。需要注意的是文档字符串对类,函数/方法,还有模块来说都是唯一的,所以特殊属性__doc__不会从基类中继承过来。
对任何(子)类,它是一个包含其父类(patent)的集合的元祖。注意,我们明确指出“父类”是相对所有基类(它包括了所有祖先类)而言的。
在Python2.x中,那些没有父类的类,它们的__bases__属性为空。
在Python3.x中,取消了经典类,仅保留新式类。
python2.x
# -*- coding:utf-8 -*-
# !/usr/bin/env python2
class A(object): # 声明新式类
pass
class B(): # 声明经典类
pass
class C: # 声明经典类
pass
>>> A.__bases__
(<type 'object'>,)
>>> B.__bases__
()
>>> C.__bases__
()
python3.x
# -*- coding:utf-8 -*-
# !/usr/bin/env python3
class A(object):
pass
class B():
pass
class C:
pass
>>> A.__bases__
(<class 'object'>,)
>>> B.__bases__
(<class 'object'>,)
>>> C.__bases__
(<class 'object'>,)
class A(object):
pass
class B(A):
pass
class C(B):
pass
class D(B, A):
pass
交互结果
>>> A.__bases__
(<class 'object'>,)
>>> C.__bases__
(<class '__main__.B'>,)
>>> D.__bases__
(<class '__main__.B'>, <class '__main__.A'>)
在上面的例子中,尽管C是A和B的子类(通过B传递继承关系),但C的父类是B,这从它的声明中可以看出,所以,只有B会在C.__bases__中显示出来。另一方面,D是从两个类A和B中继承而来的。
class E(A, B):
pass
TypeError: Cannot create a consistent method resolution
order (MRO) for bases A, B
我们在P中再写一个函数,然后在其子类中对它进行覆盖。
class P(object):
def foo(self):
print('Hi, Iam P-foo()')
>>> p = P()
>>> p.foo()
Hi, Iam P-foo()
现在来创建子类C,从父类P派生
class C(P):
def foo(self):
print('Hi, I am C-foo')
>>> c = C()
>>> c.foo()
Hi, I am C-foo
尽管C继承了P的foo()方法,但因为C定义了它自己的foo()方法,所以P中的foo()(Override)。覆盖方法的原因之一是,你的子类可能需要这个方法具有特定或不同的功能。所以,你接下来的问题肯定是:“我还能否调用那个被我覆盖的基类方法呢?”
答案是肯定的,但是这时就需要你去调用一个未绑定的基类方法,明确给出子类的实例,例如下边:
>>> P.foo(c)
Hi, Iam P-foo()
注意,我们上面已经有了一个P的实例p,但上面的这个例子并没有用它。我们不需要P的实例调用P的方法,因为已经有一个P的子类的实例c可用。典型情况下,你不会以这种方式调用父类方法,你会在子类的重写方法里显示地调用基类方法。
class C(P):
def foo(self):
P.foo(self)
print('Hi, I am C-foo()')
注意,在这个(未绑定)方法调用中我们显示地传递了 self
。一个更好的办法是使用 super()内建方法:
class C(P):
def foo(self):
super(C, self).foo()
print('Hi, I am C-foo()')
super()不但能找到基类方法,而且还为我们传进 self
,这样我们就不需要做这些事了。现在我们只要调用子类的方法,它会帮你完成一切:
>>> c = C()
>>> c.foo()
Hi, Iam P-foo()
Hi, I am C-foo()
重写__init__不会自动调用基类的__init__
类似于上面的覆盖非特殊方法,当从一个带构造器 __init()__ 的类派生,如果你不去覆盖 __init__(),它将会被继承并自动调用。但如果你在子类中覆盖了 __init__(),子类被实例化时,基类的 __init__() 就不会被自动调用。
class P(object):
def __init__(self):
print("Calling Ps constructor")
class C(P):
def __init__(self):
print("Calling C's consructor")
class P(object):
def __init__(self):
print("Calling Ps constructor")
class C(P):
def __init__(self):
print("Calling C's consructor")
>>> c = C()
Calling C's consructor
如果你还想调用基类的 __init__(),你需要像上边我们刚说的那样,明确指出,使用一个子类的实例去调用基类(未绑定)方法。相应地更新类C,会出现线面预期的执行结果:
class C(P):
def __init__(self):
P.__init__(self)
print("Calling C's constructor")
>>> c = C()
Calling P's constructor
Calling C's constructor
上面的例子中,子类的 __init__() 方法首先调用了基类的 __init__() 方法。这是相当普遍(不是强制)的做法,用来设置初始化基类,然后可以执行子类内部的设置。这个负责之所以有意义的原因是,你希望被继承的类的对象在子类构造器运行前能够很好地被初始化或作好准备工作,因为它(子类)可能需要或设置继承属性。
Python使用基类名来调用类方法,对应在Java中,是关键词super来实现,这就是super()内建函数引入到Python中的原因,这样你就可以“依葫芦画瓢”了:
class C(P):
def __init__(self):
super(C, self).__init__()
print("Calling C's constructor")
使用 super() 的漂亮之处在于,你不需要明确给出任何基类名字……“跑腿儿”的事,它帮你干了!使用 super() 的重点,使你不需要明确提供父类。这意味着如果你改变了类继承关系,你只需要改一行代码(class 语句本身)而不必再大量代码中去查找所有被修改的那个类的名字。
经典类中,一个最大的问题是,不能对标准类型进行子类化。幸运的是,在2.2以后的版本中,随着类型(types)和类(class)的统一和新式类的引入,这一点已经被修正。下面,介绍两个子类化 Python 类型的相关例子。其中一个是可变类型,另一个是不可变类型
1. 不可变类型的例子
假定你想在金融应用中,应用一个处理浮点数的子类。每次你得到一个贷币值(浮点数给出的),你都需要通过四舍五入,变为带两位小数位的数值。(当然,Decimal 类比起标准浮点类型来说是个用来精确保存浮点值的更佳方案,但你还是需要[有时候]对其进行舍入操作!)你的类开始可以这样写:
class RoundFloat(float):
def __new__(cls, val)
return float.__new__(cls, round(val, 2))
我们覆盖了__new__()特殊方法来定制我们的对象,使之和标准Python 浮点数(float)有一些区别:我们使用round()内建函数对原浮点数进行舍入操作,然后实例化我们的float,RoundFloat。我们是通过调用父类的构造器来创建真实的对象的,float.new()。注意,所有的__new()方法都是类方法,我们要显式传入类传为第一个参数,这类似于常见的方法如__init()中需要的self。
现在的例子还非常简单,比如,我们知道有一个float,我们仅仅是从一种类型中派生而来等等.通常情况下,最好是使用super()内建函数去捕获对应的父类以调用它的__new()__方法,下面,对它进行这方面的修改:
class RoundFloat(float):
def __new__(cls, val):
return super(RoundFloat, cls).__new__(cls, round(val, 2))
这个例子还远不够完整,所以,请留意本章我们将使它有更好的表现。下面是一些样例输出:
>>> RoundFloat(1.5955)
1.6
>>> RoundFloat(1.5945)
1.59
>>> RoundFloat(-1.9955)
-2.0
2. 可变类型的例子
子类化一个可变类型与此类似,你可能不需要使用__new__() (或甚至__init__()),因为通常设置不多。一般情况下,你所继承到的类型的默认行为就是你想要的。下例中,我们简单地创建一个新的字典类型,它的keys()方法会自动排序结果:
class SortedKeyDict(dict):
def keys(self):
return sorted(super(SortedKeyDict, self).keys())
回忆一下,字典(dictionary)可以由dict(),dict(mapping),dict(sequence_of_2_tuples),或者dict(**kwargs)来创建,看看下面使用新类的例子:
d = SortedKeyDict((('zheng-cai', 67), ('hui-jun', 68),('xin-yi', 2)))
print 'By iterator:'.ljust(12), [key for key in d]
print 'By keys():'.ljust(12), d.keys()
把上面的代码全部加到一个脚本中,然后运行,可以得到下面的输出:
By iterator: ['zheng-cai', 'xin-yi', 'hui-jun']
By keys(): ['xin-yi', 'hui-jun', 'zheng-cai']
在上例中,通过keys 迭代过程是以散列顺序的形式,而使用我们(重写的)keys()方法则将keys 变为字母排序方式了。
一定要谨慎,而且要意识到你正在干什么。如果你说,“你的方法调用super()过于复杂”,取而代之的是,你更喜欢keys()简简单单(也容易理解)…,像这样:
def keys(self):
return sorted(self.keys())
同C++一样,Python 允许子类继承多个基类。这种特性就是通常所说的多重继承。概念容易,但最难的工作是,如何正确找到没有在当前(子)类定义的属性。当使用多重继承时,有两个不同的方面要记住。首先,还是要找到合适的属性。另一个就是当你重写方法时,如何调用对应父类方法以“发挥他们的作用”,同时,在子类中处理好自己的义务。我们将讨论两个方面,但侧重后者,讨论方法解析顺序。
方法解释顺序(MRO)
在Python 2.2 以前的版本中,算法非常简单:深度优先,从左至右进行搜索,取得在子类中使用的属性。其它Python 算法只是覆盖被找到的名字,多重继承则取找到的第一个名字。
由于类,类型和内建类型的子类,都经过全新改造, 有了新的结构,这种算法不再可行. 这样一种新的MRO 算法被开发出来,在2.2 版本中初次登场,是一个好的尝试,但有一个缺陷(看下面的核心笔记)。这在2.3 版本中立即被修改,也就是今天还在使用的版本。
精确顺序解释很复杂,超出了本文的范畴,但你可以去阅读本节后面的参考书目提到的有关内
容。这里提一下,新的查询方法是采用广度优先,而不是深度优先。
核心笔记:
Python 2.2 使用一种唯一但不完善的MROPython 2.2 是首个使用新式MRO 的版本,它必须取代经典类中的算法,原因在上面已谈到过。在2.2 版本中,算法基本思想是根据每个祖先类的继承结构,编译出一张列表,包括搜索到的类,按策略删除重复的。然而,在Python 核心开发人员邮件列表中,有人指出,在维护单调性方面失败过(顺序保存),必须使用新的C3 算法替换,也就是从2.3 版开始使用的新算法。
下面的示例,展示经典类和新式类中,方法解释顺序有什么不同。
简单属性查找示例
下面这个例子将对两种类的方案不同处做一展示。脚本由一组父类,一组子类,还有一个子孙类组成。
class P1: #(object): # parent class 1 父类1
def foo(self):
print 'called P1-foo()'
class P2: #(object): # parent class 2 父类2
def foo(self):
print 'called P2-foo()'
def bar(self):
print 'called P2-bar()'
class C1(P1, P2): # child 1 der. from P1, P2 #子类1,从P1,P2 派生
pass
class C2(P1, P2): # child 2 der. from P1, P2 #子类2,从P1,P2 派生
def bar(self):
print 'called C2-bar()'
class GC(C1, C2): # define grandchild class #定义子孙类
pass # derived from C1 and C2 #从C1,C2 派生
图13-2 父类,子类及子孙类的关系图,还有它们各自定义的方法
在图13-2 中,我们看到父类,子类及子孙类的关系。P1 中定义了foo(),P2 定义了foo()和bar(),C2 定义了bar()。下面举例说明一下经典类和新式类的行为。
经典类
首先来使用经典类。通过在交互式解释器中执行上面的声明,我们可以验证经典类使用的解释顺序,深度优先,从左至右:
>>> gc = GC()
>>> gc.foo() # GC ==> C1 ==> P1
called P1-foo()
>>> gc.bar() # GC ==> C1 ==> P1 ==> P2
called P2-bar()
当调用foo()时,它首先在当前类(GC)中查找。如果没找到,就向上查找最亲的父类,C1。查找未遂,就继续沿树上访到父类P1,foo()被找到。
同样,对bar()来说,它通过搜索GC,C1,P1 然后在P2 中找到。因为使用这种解释顺序的缘故,C2.bar()根本就不会被搜索了。
现在,你可能在想,“我更愿意调用C2 的bar()方法,因为它在继承树上和我更亲近些,这样才会更合适。”在这种情况下,你当然还可以使用它,但你必须调用它的合法的全名,采用典型的非绑定方式去调用,并且提供一个合法的实例:
>>> C2.bar(gc)
called C2-bar()
新式类
取消类P1 和类P2 声明中的对(object)的注释,重新执行一下。新式方法的查询有一些不同:
>>> gc = GC()
>>> gc.foo() # GC ==> C1 ==> C2 ==> P1
called P1-foo()
>>> gc.bar() # GC ==> C1 ==> C2
called C2-bar()
与沿着继承树一步一步上溯不同,它首先查找同胞兄弟,采用一种广度优先的方式。当查找foo(),它检查GC,然后是C1 和C2,然后在P1 中找到。如果P1 中没有,查找将会到达P2。foo()的底线是,包括经典类和新式类都会在P1 中找到它,然而它们虽然是同归,但殊途!
然而,bar()的结果是不同的。它搜索GC 和C1,紧接着在C2 中找到了。这样,就不会再继续搜索到祖父P1 和P2。这种情况下,新的解释方式更适合那种要求查找GC 更亲近的bar()的方案。当然,如果你还需要调用上一级,只要按前述方法,使用非绑定的方式去做,即可。
>>> P2.bar(gc)
called P2-bar()
新式类也有一个__mro__属性,告诉你查找顺序是怎样的:
>>> GC.__mro__
(<class '__main__.GC'>, <class '__main__.C1'>, <class
'__main__.C2'>, <class '__main__.P1'>, <class
'__main__.P2'>, <type 'object'>)
菱形效应为难MRO
经典类方法解释不会带来很多问题。它很容易解释,并理解。大部分类都是单继承的,多重继承只限用在对两个完全不相关的类进行联合。这就是术语mixin 类(或者“mix-ins”)的由来。
为什么经典类MRO 会失败
在版本2.2 中,类型与类的统一,带来了一个新的“问题”,波及所有从object(所有类型的祖先类)派生出来的(根)类,一个简单的继承结构变成了一个菱形。从Guido van Rossum 的文章中得到下面的灵感,打个比方,你有经典类B 和C,C 覆盖了构造器,B 没有,D 从B 和C 继承而来:
class B:
pass
class C:
def __init__(self):
print "the default constructor"
class D(B, C):
pass
当我们实例化D,得到:
>>> d = D()
the default constructor
图13.3 为B,C 和D 的类继承结构,现在把代码改为采用新式类的方式,问题也就产生了:
class B(object):
pass
class C(object):
def __init__(self):
print "the default constructor"
图13.3 继承的问题是由于在新式类中,需要出现基类,这样就在继承结构中,形成了一个菱形。D 的实例上溯时,不应当错过C,但不能两次上溯到A(因为B 和C 都从A 派生)。去读读Guidovan Rossum 的文章中有关"协作方法"的部分,可以得到更深地理解。
代码中仅仅是在两个类声明中加入了(object),对吗?没错,但从图中,你可以看出,继承结构已变成了一个菱形;真正的问题就存在于MRO 了。如果我们使用经典类的MRO,当实例化D 时,不再得到C.__init__()之结果…而是得到object.__init__()!这就是为什么MRO 需要修改的真正原因。
尽管我们看到了,在上面的例子中,类GC 的属性查找路径被改变了,但你不需要担心会有大量的代码崩溃。经典类将沿用老式MRO,而新式类将使用它自己的MRO。还有,如果你不需要用到新式类中的所有特性,可以继续使用经典类进行开发,不会有问题的。
总结
经典类,使用深度优先算法。因为新式类继承自object,新的菱形类继承结构出现,问题也就接着而来了,所以必须新建一个MRO。
你可以在下面的链接中读在更多有关新式类、MRO 的文章:
Guido van Rossum 的有关类型和类统一的文章:
http://www.python.org/download/releases/2.2.3/descrintro
PEP 252:使类型看起来更像类
http://www.python.org/doc/peps/pep-0252
“Python 2.2 新亮点” 文档
http://www.python.org/doc/2.2.3/whatsnew
论文:Python 2.3 方法解释顺序
http://python.org/download/releases/2.3/mro/
issubclass() 布尔函数判断一个类是另一个类的子类或子孙类。它有如下语法:
issubclass(sub, sup)
issubclass() 返回True 的情况:给出的子类sub 确实是父类sup 的一个子类(反之,则为False)。这个函数也允许“不严格”的子类,意味着,一个类可视为其自身的子类,所以,这个函数如果当sub 就是sup,或者从sup 派生而来,则返回True。(一个“严格的”子类是严格意义上的从一个类派生而来的子类。)
从Python 2.3 开始,issubclass()的第二个参数可以是可能的父类组成的tuple(元组),这时,只要第一个参数是给定元组中任何一个候选类的子类时,就会返回True。
isinstance() 布尔函数在判定一个对象是否是另一个给定类的实例时,非常有用。它有如下语法:
isinstance(obj1, obj2)
isinstance()在obj1 是类obj2 的一个实例,或者是obj2 的子类的一个实例时,返回True(反之,则为False),看下面的例子:
>>> class C1(object):pass
...
>>> class C2(object):pass
...
>>> c1 = C1()
>>> c2 = C2()
>>> isinstance(c1, C1)
True
>>> isinstance(c2, C1)
False
>>> isinstance(c1, C2)
False
>>> isinstance(c2, C2)
True
>>> isinstance(C2, c2)
Traceback (most recent call last):
File "", line 1, in <module>
TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types
注意:第二个参数应当是类;不然,你会得到一个TypeError。但如果第二个参数是一个类型对象,则不会出现异常。这是允许的,因为你也可以使用isinstance()来检查一个对象obj1 是否是obj2 的类型,比如:
>>> isinstance(4, int)
True
>>> isinstance(4, str)
False
>>> isinstance('4', str)
True
如果你对Java 有一定的了解,那么你可能知道Java 中有个等价函数叫instanceof(),但由于性能上的原因,instanceof()并不被推荐使用。调用Python 的isinstance()不会有性能上的问题,主要是因为它只用来来快速搜索类族集成结构,以确定调用者是哪个类的实例,还有更重要的是,它是用C 写的!
同issubclass()一样,isinstance()也可以使用一个元组(tuple)作为第二个参数。这个特性是从Python 2.2 版本中引进的。如果第一个参数是第二个参数中给定元组的任何一个候选类型或类的实例时,就会返回True。你还可以在595 页,第13.16.1 节中了解到更多有isinstance()的内容。
*attr()系列函数可以在各种对象下工作,不限于类(class)和实例(instances)。然而,因为在类和实例中使用极其频繁,就在这里列出来了。需要说明的是,当使用这些函数时,你传入你正在处理的对象作为第一个参数,但属性名,也就是这些函数的第二个参数,是这些属性的字符串名字。换句话说,在操作obj.attr 时,就相当于调用*attr(obj,‘attr’…)系列函数------下面的例子讲得很清楚。
hasattr()函数是Boolean 型的,它的目的就是为了决定一个对象是否有一个特定的属性,一般用于访问某属性前先作一下检查。getattr()和setattr()函数相应地取得和赋值给对象的属性,getattr()会在你试图读取一个不存在的属性时,引发AttributeError 异常,除非给出那个可选的默认参数。setattr()将要么加入一个新的属性,要么取代一个已存在的属性。而delattr()函数会从一个对象中删除属性。
下面一些例子使用到了*attr()系列函数:
>>> class myClass(object):
... def __init__(self):
... self.foo = 100
...
>>> myInst = myClass()
>>> hasattr(myInst, 'foo')
True
>>> getattr(myInst, 'foo')
100
>>> hasattr(myInst, 'bar')
False
>>> getattr(myInst, 'bar')
Traceback (most recent call last):
File "", line 1, in <module>
AttributeError: 'myClass' object has no attribute 'bar'
>>> getattr(c, 'bar', 'oops!')
Traceback (most recent call last):
File "", line 1, in <module>
NameError: name 'c' is not defined
>>> getattr(myInst, 'bar', 'oops!')
'oops!'
>>> setattr(myInst, 'bar', 'my attr')
>>> dir(myInst)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'bar', 'foo']
>>> getattr(myInst, 'bar') # same as myInst.bar
'my attr'
>>> delattr(myInst, 'foo')
>>> dir(myInst)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'bar']
>>> hasattr(myInst, 'foo')
False
我们用dir()列出一个模块所有属性的信息。现在你应该知道dir()还可以用在对象上。
在Python 2.2 中, dir()得到了重要的更新。因为这些改变,那些\ __members__和__methods__数据属性已经被宣告即将不支持。dir()提供的信息比以前更加详尽。根据文档,“除了实例变量名和常用方法外,它还显示那些通过特殊标记来调用的方法,像__iadd__(+=),__len__(len()),__ne__(!=)。” 在Python 文档中有详细说明。
super()函数在Python2.2 版本新式类中引入。这个函数的目的就是帮助程序员找出相应的父类,然后方便调用相关的属性。一般情况下,程序员可能仅仅采用非绑定方式调用祖先类方法。使用super()可以简化搜索一个合适祖先的任务,并且在调用它时,替你传入实例或类型对象。
在第13.11.4 节中,我们描述了文档解释顺序(MRO),用于在祖先类中查找属性。对于每个定义的类,都有一个名为__mro__的属性,它是一个元组,按照他们被搜索时的顺序,列出了备搜索的类。语法如下:
super(type[, obj])
给出type,super()“返回此type 的父类”。如果你希望父类被绑定,你可以传入obj 参数(obj必须是type 类型的).否则父类不会被绑定。obj 参数也可以是一个类型,但它应当是type 的一个子类。通常,当给出obj 时:
事实上,super()是一个工厂函数,它创造了一个super object,为一个给定的类使用__mro__去查找相应的父类。很明显,它从当前所找到的类开始搜索MRO。更多详情,请再看一下Guido vanRossum 有关统一类型和类的文章,他甚至给出了一个super()的纯Python 实现,这样,你可以加深其印象,知道它是如何工作的!
最后想到… super() 的主要用途, 是来查找父类的属性, 比如,super(MyClass,self).__init__()。如果你没有执行这样的查找,你可能不需要使用super()。
有很多如何使用super()的例子分散在本章中。记得阅读一下第13.11.2 节中有关super()的重要提示,尤其是那节中的核心笔记。
vars()内建函数与dir()相似,只是给定的对象参数都必须有一个__dict__属性。vars()返回一个字典,它包含了对象存储于其__dict__中的属性(键)及值。如果提供的对象没有这样一个属性,则会引发一个TypeError 异常。如果没有提供对象作为vars()的一个参数,它将显示一个包含本地名字空间的属性(键)及其值的字典,也就是,locals()。我们来看一下例子,使用类实例调用vars():
>>> class C(object):
... pass
...
>>> c = C()
>>> c.foo = 100
>>> c.bar = 'Python'
>>> c.__dict__
{'foo': 100, 'bar': 'Python'}
>>> vars(c)
{'foo': 100, 'bar': 'Python'}
表13.3 概括了类和类实例的内建函数。
表13.3 类,实例及其它对象的内建函数
内建函数 | 描述 |
---|---|
issubclass(sub, sup) | 如果类sub 是类sup 的子类,则返回True,反之,为False。 |
isinstance(obj1, obj2) | 如果实例obj1 是类obj2 或者obj2 子类的一个实例;或者如果obj1是obj2 的类型,则返回True;反之,为False。 |
hasattr(obj, attr) | 如果obj 有属性attr(用字符串给出),返回True,反之,返回表13.3 类,实例及其它对象的内建函数 |
getattr(obj, attr[, default]) | 获取obj 的attr 属性;与返回obj.attr 类似;如果attr不是obj 的属性,如果提供了默认值,则返回默认值;不然,就会引发一个AttributeError 异常。 |
setattr(obj, attr, val) | 设置obj 的attr 属性值为val,替换任何已存在的属性值;不然,就创建属性;类似于obj.attr=val |
delattr(obj, attr) | 从obj 中删除属性attr(以字符串给出);类似于delobj.attr。 |
dir(obj=None) | 返回obj 的属性的一个列表;如果没有给定obj,dir()则显示局部名字空间空间中的属性,也就是locals().keys() |
super(type, obj=None) a | 返回一个表示父类类型的代理对象;如果没有传入obj,则返 回的super 对象是非绑定的;反之,如果obj 是一个type , issubclass(obj,type) 必为True ; 否则,isinstance(obj,type)就必为True。 |
vars(obj=None) | 返回obj 的属性及其值的一个字典;如果没有给出obj,vars()显示局部名字空间字典(属性及其值),也就是locals()。 |
a. Python2.2 中新增;仅对新式类有效
默认情况下,属性在Python 中都是“public”,类所在模块和导入了类所在模块的其他模块的代码都可以访问到。很多OO 语言给数据加上一些可见性,只提供访问函数来访问其值。这就是熟知的实现隐藏,是对象封装中的一个关键部分。
大多数OO 语言提供“访问控制符”来限定成员函数的访问。
双下划线(__)
Python 为类元素(属性和方法)的私有性提供初步的形式。由双下划线开始的属性在运行时被“混淆”,所以直接访问是不允许的。实际上,会在名字前面加上下划线和类名。比如,以例13.6(numstr.py)中的self.__num 属性为例,被“混淆”后,用于访问这个数据值的标识就变成了self._NumStr__num。把类名加上后形成的新的“混淆”结果将可以防止在祖先类或子孙类中的同名
冲突。
尽管这样做提供了某种层次上的私有化,但算法处于公共域中并且很容易被“击败”。这更多的是一种对导入源代码无法获得的模块或对同一模块中的其他代码的保护机制.
这种名字混淆的另一个目的,是为了保护__XXX 变量不与父类名字空间相冲突。如果在类中有一个__XXX 属性,它将不会被其子类中的__XXX 属性覆盖。(回忆一下,如果父类仅有一个XXX 属性,子类也定义了这个,这时,子类的XXX 就是覆盖了父类的XXX,这就是为什么你必须使用PARENT.XXX来调用父类的同名方法。) 使用__XXX,子类的代码就可以安全地使用__XXX,而不必担心它会影响到父类中的__XXX。
单下划线(_)
与我们在第十二章发现的那样,简单的模块级私有化只需要在属性名前使用一个单下划线字符。这就防止模块的属性用“from mymodule import *”来加载。这是严格基于作用域的,所以这同样适合于函数。
在Python 2.2 中引进的新式类,增加了一套全新的特征,让程序员在类及实例属性提供保护的多少上拥有大量重要的控制权。尽管Python 没有在语法上把private,protected,friend 或protected friend 等特征内建于语言中,但是可以按你的需要严格地定制访问权。我们不可能涵盖所有的内容,但会在本章后面给你一些有关新式类属性访问的建议。