Python 面向对象编程
Contents
Python 面向对象编程
作者:最西瓜 来源:《Python核心编程》13章. 面向对象编程
Python 面向对象编程的最大优点就是你不必使用面向对象。Python 的类没有什么访问控制,可以说都是公共的。Python 类没有抽象方法或纯虚方法。Python 类中继承自 object 的称之为新式类,否则就是旧式类,推荐使用的是新式类。下面会详述新式类有什么优点。类的模板如下:
|
|
创建对象就像调用函数一样的。对象的数据属性是动态的,随时加入,但预先声明或者记录下来是一个好的习惯。定义类的实例方法,第一个参数永远是 self。__init__
是特殊的实例方法,是在实例化对象后返回此对象引用前的调用的第一个方法,传给它的参数需要在创建对象时传入,此方法只能返回 None 值。类可以有自己的数据属性,推荐将数据属性初始化写在类的起始部分。
Python 中的继承和别的语言中没什么两样,子类会继承父类的各种方法和属性。如果定义了子类 __init__
方法,父类的就不会自动调用,需要在第一行显式调用,而且需要显式传递 self 参数。从 Python 2.2 之后,可以从内建类中派生子类。Python 类支持多重继承。参考如下:
|
|
以上 __init__
的调用成为非绑定调用,而用实例直接调用成为绑定调用。非绑定调用,必须要给出实例作为第一个参数。可以用 dir() 内建函数和类的特殊属性 __dict__
来展示类具有哪些属性,同样实例也可以用 dir() 内建函数和特殊属性 __dict__
来访问实例的数据属性,从本质上说方法属于类的属性,如果是內建类型则只能使用 dir() 来访问,它们没有 __dict__
属性。类具有一些特殊属性:
__name__
类的名字__doc__
类的文档字符串__bases__
类的所有父类构成的元组__dict__
类的属性__module__
类所在的模块__class__
实例对应的类,在经典对象中没有此属性,因为经典类被认为与类型不统一
实例属性和类属性是两种不同的事物,但是当对应的实例属性不存在时,通过实例可以访问类属性,但是假如通过实例去更新这个属性,并不是真正的更新,而是创建一个实例属性。示例如下:
|
|
Python 除了以上方法外还有类方法和静态方法。类方法是默认传入的第一个参数是类本身,静态方法则所有参数都需要显式传入。类方法用 @classmethod 标记,静态方法用 @staticmethod 标记。类方法和静态方法都可以被子类继承或者覆盖。
|
|
显式调用父类方法的优雅做法是用 super() 如下:
|
|
多重继承很复杂,目前最佳的应用场景是 mixin 类,就是将几个完全不同的行为的类合并到一个类中。目前新式类的方法查找类似于广度优先查找,先在父类中查找,如果都找不到再到祖先类中查找。所有类都有一个 __mro__
元组属性来告知方法被搜索的顺序。
Python 还提供我认为不会经常用到的类方法 __new__
,这个方法以类本身作为第一个参数,必须返回一个类实例。通常的用法是当我们继承一个不变的内置类型如数字类型时会用到。但更好的方法是组合而不是继承。以下我们继承 float 类型:
|
|
调用父类 new 方法的推荐用法是:super(currentclass, cls).__new__(cls[, ...])
,new 特殊方法的参数与 init 特殊方法是一样的,非常值得一提的是在类的内部推荐用 self.__class__()
来调用所属类的构造函数。
内建函数
- issubclass(sub, sup) 判断 sub 是否为 sup 的子类,如果 sub 和 sup 是同一个类也是成立的,sup 可以是父类组成的元组,sub 是要是 sup 中任意一个类的子类就返回 True
- isinstance(obj1, cls) 判断一个对象是否是一个类的实例,cls 必须是类对象或者内建类型,cls 可以是类对象组成的元组
- hasattr() getattr() setattr() delattr() 分别用来判断是否有属性、获取属性、设置属性、删除属性
特殊方法
Python 的类有非常的特殊方法,而且还在不断增加,它们的作用在于模拟操作符或者标准类型的行为,有些则是内建函数会隐式调用,通常我们不会显式去调用它们。它们均为实例方法。
__str__
被 str() 及 print() 调用,__repr__
被 repr() 隐式调用,__call__
当将对象当做函数调用时会调用此方法,__nonzero__
bool() 调用,__len__
len() 调用,__cmp__
cmp() 调用
__lt__
__le__
__gt__
__ge__
__eq__
__ne__
分别小于、小于等于、大于、大于等于、等于、不等于对应关系操作符
迭代器特殊方法
__iter__
被 iter() 或 for 循环隐式调用,__next__
被 next() 或 for 隐式调用,当没有下一个元素时应当抛出 StopIteration 异常,__reversed__
被 reversed() 隐式调用,这个比较少见。
属性特殊方法
__getattr__
以句点方式或者内建函数 getattr() 获取属性时,当属性不存在时调用,__setattr__
总是调用来设置属性,__delattr__
总是调用来删除属性,__getattribute__
总是调用来获取属性。属性包括数据属性和方法。当你定义了以上方法时最好能够定义 __dir__
供 dir() 函数使用。当定义了 __getattribute__
时,最好定义 __setattr__
否则新设置的属性将无法取得,并且由于 __getattribute__
会拦截方法的查找,需要仔细实现这个方法。
序列映射特殊方法
__contains__
被 in 调用,__getitem__
得到单个元素,__setitem__
__delitem__
设置和删除单个元素,__hash__
hash() 调用获取散列值,__getslice__
__setslice__
__delslice__
用来得到、设置、删除切片,__missing__
用于当映射中没有对应 key 时提供默认值
数字方法
__add__
__sub__
__mul__
__truediv__
__floordiv__
__mod__
__pow__
__lshift__
__rshift__
__and__
__xor__
__or__
这些方法不一一解释,以上方法都是当对象位于左边时调用,如果对象位于右边,将调用 __radd__
等。 __iadd__
用于模拟 += 操作符,i 方法的实现是可选的,因为没有定义的话,Python 会翻译成前面两种形式的隐式调用。
__neg__
__pos__
__abs__
__int__
__float__
__round__
__ceil__
__floor__
__trunc__
同样用户执行数学计算。
序列化特殊方法
__copy__
__deepcopy__
用于对象复制 __getstate__
用于在序列化前获得对象状态,__reduce_ex__(protocol_version)
__reduce__
是真正的序列化对象, __getnewargs__
控制对象反序列化时的创建,__setstate__
控制在反序列化之后的状态设置
with 资源协议特殊方法
__enter__
__exit__(exc_type, exc_value, traceback)
进入和退出资源协议时调用的特殊方法。
私有化
在 Python 中以双下划线开始的属性或方法,在被外界访问时会被混淆。核心编程这本书里说,__num
这种会被混淆成 _ClassName__num
形式,相当于提供了一种很弱的私有性。需要混淆的原因在于子类同名的属性或方法可能会覆盖父类的。
新式类的高级特性
新式类带来的最大优点就是将用户定义类与 Python 内建类型统一,而且用户定义类可以继承内建类型。以下特性只存在于新式类中。
__slots__
是一个类属性,类型必须是字符串的元组,此元组中的元素才被允许作为实例的属性,这个特性的好处在于将实例的 __dict__
给去除了,节约了内存。
|
|
新式类还给出了描述符的概念:当我们访问属性时,我们会去调用它的代理方法。描述符协议要求代理对象实现 __get__
__set__
__delete__
方法来响应访问、设置和删除属性事件。我们不必所有都实现,事实上 delete 方法很少实现,如果只实现 get 方法则成为非数据描述符,实现了 get set 称为数据描述符。描述符必须定义为类的属性。
|
|
描述符系统和 __getattribute__
方法相辅相成,获取属性会执行如下属性的搜索:
- 类属性
- 数据描述符
- 实例属性
- 非数据描述符
__getattr__
方法
除了用以上方式创建描述符,我们还可以用 property 达到同样的效果:
|
|
还有很多别的方式来创建描述符,参考:Python 描述符简介
元类和 __metaclass__
Python 区别于别的 OOP 语言就在于它的类也是对象,可以动态的生成、修改。其实你应该明白,在 Python 中一切都是对象。方法 type(name, bases, dict) 可以创建类名为 name,基类为 bases 以及属性为 dict 的动态类,跟在代码中定义是一样的。元类的概念就是创建类对象的类。所有的新式类都是 type 的实例,恰恰 type 是所有用户定义类型的元类。元类使用的频率很低,但我们需要了解这种特性。
metaclass 可以是函数也可以是类,创建类时会去调用 __metaclass__
然后返回一个新的类,给了元类去修改类的机会。
|
|
UpperAttrMetaclass 元类改变了类 Foo 的属性名大小。通常如果需要改写类,我们需要实现 new 特殊方法,而 init 将传入的参数初始化给对象。当然,UpperAttrMetaclass 完全可以是一个函数。
查找 metaclass
在运用 metaclass 前需要在环境中查找到它,查找顺序是现在本类中查找特殊属性 metaclass,如果没有就向上查找父类中的 __metaclass__
,还是找不到将查找全局变量 __metaclass__
,否则就用 type 作为元类。
关于元类更多的信息请参考 Python 文档中 __metaclass__
词条,那里会有完整的解释!
Author zoro.wang
LastMod 2017-09-26