Python 高手编程系列二十六:最佳实践

为了避免前面提到的所有问题,在 Python 在这个领域取得进展之前,我们需要考虑以
下几点。
• 应该避免多重继承:可以采用第 14 章介绍的一些设计模式来代替它。
• super 的使用必须一致:在类的层次结构中,要么全部用 super,要么全不用。
混用 super 和传统调用是一种混乱的做法。人们往往会避免使用 super,这样代
码会更清晰。
• 如果代码的使用范围包括 Python 2,在 Python 3 中也应该显式地继承自 object:
在 Python 2 中,没有指定任何祖先的类被认为是旧式类。在 Python 2 中应避免混
用旧式类和新式类。
• 调用父类时必须查看类的层次结构:为了避免出现任何问题,每次调用父类时,必
须快速查看有关的 MRO(使用__mro__)。
高级属性访问模式
许多 C++和 Java 程序员第一次学习 Python 时,他们会对 Python 没有 private 关键
字感到惊讶。与之最接近的概念是名称修饰(name mangling)。每当在一个属性前面加上
__前缀,解释器就会立刻将其重命名:
class MyClass:
__secret_value = 1
利用原始名称访问__secret_value 属性,将会引发 AttributeError 异常:

instance_ of = MyClass()
instance
_of. _secret _value
Traceback (most recent call last):
File “”, line 1, in
AttributeError: ‘MyClass’ object has no attribute ’ _secret value’
dir(MyClass)
[’
_MyClass _secret _value’, ’ __class __‘, ’ __delattr __’, ’ __dict __‘, ’ __
dir __’, ’ __doc __‘, ’ __eq __’, ’ __format __‘, ’ __ge __’, ’ __getattribute __‘,
’ __gt __’, ’ __hash __‘, ’ __init __’, ’ __le __‘, ’ __lt __’, ’ __module __‘,
’ __ne __’, ’ __new __‘, ’ __reduce __’, ’ _reduce _ex __‘, ’ __repr __’, ’ __
setattr __‘, ’ __sizeof ', ’ str ', ’ subclasshook ', ’ weakref ']
instance
of. MyClass secret value
1
Python 提供这一特性是为了避免继承中的名称冲突,因为属性被重命名为以类名为前
缀的名称。这并不是真正的锁定(real lock),因为可以通过其组合名称来访问该属性。这
一特性可用于保护某些属性的访问,但在实践中,永远不应使用
。如果一个属性不是公
有的,约定使用_前缀。这不会调用任何名称修饰的算法,而只是说明这个属性是该类的私
有元素,这是流行的写法。
Python 中还有其他可用的机制来构建类的公有部分和私有代码。应该使用描述符和
property 这些 OOP 设计的关键特性来设计一个清晰的 API。
描述符
描述符(descriptor)允许你自定义在引用一个对象的属性时应该完成的事情。
描述符是 Python 中复杂属性访问的基础。它在内部被用于实现 property、方法、类
方法、静态方法和 super 类型。它是一个类,定义了另一个类的属性的访问方式。换句话
说,一个类可以将属性管理委托给另一个类。
描述符类基于 3 个特殊方法,这 3 个方法组成了描述符协议(descriptor protocol):
set(self, obj, type=None):在设置属性时将调用这一方法。在下面的
示例中,我们将其称为 setter。
get(self, obj, value):在读取属性时将调用这一方法(被称为 getter)。
delete(self, obj):对属性调用 del 时将调用这一方法。
实现了__get
()和__set
()的描述符被称为数据描述符(data descriptor)。如果只
实现了__get
(),那么就被称为非数据描述符(non-data descriptor)。
在每次属性查找中,这个协议的方法实际上由对象的特殊方法__getattribute
()
调用(不要与__getattr
()弄混,后者用于其他目的)。每次通过点号(形式为
instance.attribute)或者 getattr(instance, ‘attribute’)函数调用来执行
这样的查找时,都会隐式地调用__getattribute
(),它按下列顺序查找该属性:
1.验证该属性是否为实例的类对象的数据描述符。
2.如果不是,就查看该属性是否能在实例对象的__dict__中找到。
3.最后,查看该属性是否为实例的类对象的非数据描述符。
换句话说,数据描述符优先于__dict__查找,而__dict__查找优先于非数据描述符。
为了表达得更清楚,下面是 Python 官方文档中的示例,给出了描述符在真实代码中的
工作方式:
class RevealAccess(object):
“”“一个数据描述符,正常设定值并返回值,同时打印出记录访问的信息。
“””
def init(self, initval=None, name=‘var’):
self.val = initval
self.name = name
def get(self, obj, objtype):
print(‘Retrieving’, self.name)
return self.val
def set(self, obj, val):
print(‘Updating’, self.name)
self.val = val
class MyClass(object):
x = RevealAccess(10, ‘var “x”’)
y = 5
下面是在交互式会话中的使用示例:
m = MyClass()
m.x
Retrieving var “x”
10
m.x = 20
Updating var “x”
m.x
Retrieving var “x”
20
m.y
5
前一个例子清楚地表明,如果一个类的某个属性有数据描述符,那么每次查找这个属
性时,都会调用描述符的__get
()方法并返回它的值,每次对这个属性赋值时都会调用
set()。虽然前一个例子没有给出描述符__del__方法的例子,但现在也应该清楚了:
每次通过 del instance.attribute 语句或 delattr(instance, ‘attribute’)
调用删除一个实例属性时都会调用它。
由于上述原因,数据描述符和非数据描述符的区别很重要。Python 已经使用描述符协
议将类函数绑定为实例方法。它还支持了 classmethod 和 staticmethod 装饰器背后
的机制。事实上,这是因为函数对象也是非数据描述符,如下所示:
def function(): pass
hasattr(function, ’ __get __’)
True
hasattr(function, ’ __set __‘)
False
对于 lambda 表达式创建的函数也是如此:
hasattr(lambda: None, ’ __get __’)
True
hasattr(lambda: None, ’ __set __')
False
因此,如果没有__dict__优先于非数据描述符,我们将不可能在运行时在已经构建好
的实例上动态覆写特定的方法。幸运的是,多亏了 Python 描述符的工作方式。由于这一工
作方法,使得开发人员可以使用一种叫作猴子补丁(monkey-patching)的流行技术来改变
实例的工作方式,而不需要子类化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值