学习资料:《Python核心编程(第二版)》
第13章 面向对象编程
13.4 类属性
- 数据属性、函数属性
- 类属性仅与其被定义的类相绑定,而实例数据属性用得更多;类数据属性仅当需要有更加“静态”数据类型时才变得有用
13.4.1 类的数据属性
class C(object):
foo = 100
13.4.2 Methods
- 方法:直接调用
methodname()
会引发NameError
异常,因为它不在全局名字空间中;用类对象调用此方法会引发TypeError
异常 - 绑定:方法必须绑定到一个实例才能直接被调用
13.4.3 决定类的属性
dir()
,__dict__
,vars()
13.4.4 特殊的类属性
C.__name__
C.__doc__
C.__bases__
C.__dict__
C.__module__
C.__class__
__name__
是给定类的字符名字,它适用于那种只需要字符串(类对象的名字),而非类对象本身的情况。內建的类型也有这个属性。
type()
返回被调用对象的类型。'123'
是str类型对象,str是type对象
>>> type('123')
... <type 'str'>
>>> type('123').__name__
... 'str'
>>> type(type('123'))
... <type 'type'>
13.5 实例
13.5.1 初始化:通过调用类对象来创建实例
>>> class MyClass(object):
... pass
...
>>> mc = MyClass()
13.5.2 __init__()
“构造器”方法
13.5.3 __new__()
“构造器”方法
13.5.4 __del__()
“解构器”方法
- 由于Python具有垃圾对象回收机制(靠引用计数),这个方法要直到该实例对象所有的引用都被清除掉后才会执行。
- Python中的解构器是在实例释放前提供特殊处理功能的方法,通常没有被实现,因为实例很少被显式释放
- 如果有一个循环引用或其他原因,让一个实例的引用逗留下去,该对象的
__del__()
可能永远不会执行 - 在
__del__()
未捕获的异常会被忽略掉
跟踪实例:跟踪一个类有多少个实例被创建了,最好的方式是使用静态成员记录实例个数,并显式加入一些代码到
__init__()
和__del()__
中去
class InstCt(object):
count = 0 # count 是一个类属性
def __init__(self):
InstCt.count += 1 # 增加count
def __del__(self):
InstCt.count -= 1 # 减少count
def howMany(self):
return InstCt.count # 返回count
13.6 实例属性
13.6.1 “实例化”实例属性
设置实例的属性可以在实例创建后任意时间进行,也可以在能够访问实例的代码中进行。(体现Python动态语言特性)- 在构造器中首先设置实例属性
__init__
- 默认参数提供默认的实例安装:默认参数应当是不变的对象
__init__()
应当返回None
:不返回None
会导致TypeError
异常
13.6.2 查看实例属性
dir()
可以显示类属性,也可以打印所有实例属性- 与类相似,实例也有
__dict__
特殊属性(可以调用vars(instance)
来获取)
13.6.3 特殊的实例属性
- 实例仅有两个特殊属性:
__class__
,__dict__
__dict__
仅有实例属性,没有类属性或特殊属性;而类的__dict__
仅有类属性和特殊属性- 建议不要修改
__dict__
属性
13.6.4 内建类型属性
- 内建类型的实例也可以通过
dir()
得到它的属性名字列表 - 内建类型中没有
__dict__
属性
13.6.5 实例属性 vs 类属性
- 访问类属性:类属性(比如C.version)可以通过类或实例来访问(通过c.version访问类属性:Python会在实例中搜索名字,然后是类,再就是继承树中的基类),但只能通过类来设定或更新;尝试用实例来更新或设定类属性,会创建同名的实例属性,从而阻止对类属性的访问。
>>> c.version # 通过实例对类属性的访问
1.2
>>> c.version += 0.2 # 相当于实例c创建了version的实例属性,遮蔽了类属性的访问,不会更新类属性
>>> C.version += 0.1 # 类属性不变
>>> c.version # 实例再也不能访问version类属性了,因为已经存在同名的实例属性
1.4
- 从实例中访问类属性需谨慎:同上;但在类属性是可变对象的情况下,也要注意:
>>> class Foo(object):
... x = {2003: 'poe2'}
...
>>> foo = Foo()
>>> foo.x
{2003: 'poe2'}
>>> foo.x[2004] = 'valid path'
>>> Foo.x # 实例访问类属性又可以改变类属性了
{2003: 'poe2', 2004: 'valid path'}
- 类属性持久性:静态成员,独立于实例;尽量使用类名修改类属性
13.7 绑定和方法调用
回顾:1. 方法是类内部定义的函数(类属性);2. 方法只有在其所属的类用于实例时,才能被调用。3. 任何一个方法定义中的第一个参数都是变量self,表示调用此方法的实例对象。
13.7.1 调用绑定方法
实例调用一个绑定方法时,self不需要明确地传入了。而没有实例时又需要调用一个非绑定方法时,必须传递self参数(比如是该方法属于类或子类的实例)
# C 和 T 两个类, C 有foo(self)方法
>>> c = C()
>>> c.foo() # 不需要传递self参数
>>> C.foo(c) # 非绑定方法需要传递self参数
>>> t = T()
>>> C.foo(t) # self不能是除C或C子类的实例外的实例
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unbound method foo() must be called with C instance as first argument (got T instance instead)
13.7.2 调用非绑定方法
- 调用一个还没有任何实例的类中的方法的一个主要场景:派生一个子类,需要覆盖父类的方法,可能需要调用父类中想要覆盖的构造方法
class C(object):
def __init__(self, id):
self.id = id
class child(C):
def __init__(self, id, age):
C.__init__(self, id) # 调用父类的非绑定方法,此时未创造C实例
self.age = age
13.8 静态方法和类方法
- 静态方法:类中的函数,不需要实例,也不需要传入self的参数
- 类方法:需要类而不是实例作为第一个参数,一般用
cls
参数
13.8.1 staticmethod()
和 classmethod()
内建函数
- 这两个内建函数用于将作为类定义的一部分的某一方法声明“标记”,“强制类型转换”或者“转换”为这两种类型的方法之一
# 静态方法
class TestStaticMethod(object):
def foo():
print 'calling static method foo()'
foo = staticmethod(foo)
# 测试
tsm = TestStaticMethod()
TestStaticMethod.foo()
tsm.foo()
结果为:
calling static method foo()
calling static method foo()
# 类方法
class TestClassMethod(object):
def foo(cls, id):
print 'calling class method foo()'
print id, ': foo() is part of class:', cls.__name__
foo = classmethod(foo)
# 测试
tcm = TestClassMethod()
TestClassMethod.foo('001')
tcm.foo('001')
结果为:
calling class method foo()
001 : foo() is part of class: TestClassMethod
calling class method foo()
001 : foo() is part of class: TestClassMethod
13.8.2 使用函数修饰符
- 它可以把一个函数应用到另个函数对象上,而且新函数对象依然绑定在原来的变量
# 静态方法
class TestStaticMethod(object):
@staticmethod
def foo():
print 'calling static method foo()'
# 类方法
class TestClassMethod(object):
@classmethod
def foo(cls, id):
print 'calling class method foo()'
print id, ': foo() is part of class:', cls.__name__
13.9 组合
- 类之间的关系:组合关系(has a),派生(继承)关系(is a)
13.10 子类和派生
- 创建子类
class SubClassName(ParentClass1[, ParentClass2,...]):
'optional class documentation string'
class_suite
13.11 继承
- 继承描述了基类的属性如何“遗传”给派生类。
13.11.1 __bases__
类属性
13.11.2 通过继承覆盖方法
标记={P:父类,C:子类,p:父类实例,c:子类实例}
- 重写父类方法
- 子类实例调用被覆盖的父类方法:
P.foo(c)
- 可以直接写进子类的覆盖方法中:
def foo(self): P.foo(self) ...
- 更好的办法是使用
super()
内建方法
class C(P):
def foo(self):
super(C, self).foo()
print 'Hi, I am C-foo()'
- 重写
__init__
不会自动调用基类的__init__
- Python 使用基类名来调用类方法,而super()内建函数,使不需要明确提供父类名。这在改变继承关系时,只用改class语句那一行,而不用在大量代码中寻找被修改的那类的名字
13.11.3 从标准类型派生
- 不可变类型:所有的
__new__
方法都是类方法,需要显式地传入类作为第一个参数
# 派生基本类型(不可变类型)
class RoundFloat(float):
def __new__(cls, val):
return super(RoundFloat, cls).__new__(cls, round(val, 2))
# 测试
x = RoundFloat(1.5945)
print x
print type(x)
- 可变类型
# 派生可变类型
# 可能不需要使用__new__方法
class SortedKeyDict(dict):
def keys(self):
return sorted(super(SortedKeyDict, self).keys())
# 测试
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(): ['hui-jun', 'xin-yi', 'zheng-cai']
13.11.4 多重继承
使用多重继承,要考虑如何正确找到没有在当前(子)类定义的属性。方法解析顺序
- 方式解释顺序(MRO)
- 2.2 以前,算法:深度优先
- 2.3 以后,算法:广度优先
- 新式类也有一个
__mro__
属性,告诉我们查找顺序 - 菱形效应引起MRO问题
- 大部分类都是单继承的,多重继承只限用在对两个完全不相关的类进行联合,即mixin类
- 版本2.2 中,类和类型的统一,一个简单的继承结构变成了一个菱形。
13.12 类、实例和其他对象的内建函数
13.12.1 issubclass()
- 语法
issubclass(sub, sup)
- 允许不严格的子类(一个类视为自身的子类)
- 第二个参数可以是可能的父类组成的元组:只要第一个参数是给定元组中任何一个候选子类时,就返回True
13.12.2 isinstance()
- 语法:
isinstance(obj1, obj2)
第二个参数可以接受元组
>>> isinstance(int, (type,))
True
>>> isinstance(1, (int, object))
True
13.12.3 hasattr()
, getattr()
, setattr()
, delattr()
- *attr()系列可以在各种对象中工作,不限于类和实例;第一个参数是对象,第二个参数是属性名
- 在操作obj.attr时,就相当于调用*attr(obj, ‘attr’)系列函数
- getattr()会在试图读取一个不存在的属性时,引发AttributeError异常,除非给出那个可选的默认参数
getattr(myInst, 'bar', 'oops')
- setattr() 要么加入一个新的属性,要么取代一个已存在的属性
13.12.4 dir()
13.12.5 super()
- 使用super()简化搜素一个合适祖先的任务,并且在调用它时,替你传入实例或类型对象
- 对于每个定义的类,都有一个
__mro__
的属性,按照他们被搜索时的顺序,列出了备搜索的类 - 语法:
super(type[, obj])
- 如果obj是一个实例,isinstance(obj, type) 就必须返回True
- 如果obj是一个类或类型,issubclass(obj, type) 就必须返回True
13.12.6 vars()
没有提供参数,则将显示一个包含本地名字空间的属性,即locals()
13.13 用特殊方法定制类
- 模拟标准类型
- 重载操作符