这章描述了一些常见的 dundle function,通过 Python 的 Data Model 实现一个 Pythonic Object。
Object Representations
Python 3 有几个用来做 object representation 的函数:
repr()
<=>__repr__
:给开发者读的,返回str
str()
<=>__str__
:给用户读的,返回str
bytes()
<=>__bytes__
:表示一个 object 的二进制形式,返回bytes
format()
<=>__format__
:自定义的输出函数,主要用在str.format()
和format()
内置函数上
An Alternative Constructor
区别于 __init__
,用 classmethod 来初始化一个函数,以提供多种初始化的形式:
class Vector2d:
def __init__(self, x, y):
self.x = float(x)
self.y = float(y)
@classmethod
def frombytes(cls, octets):
typecode = chr(octets[0])
memv = memoryview(octets[1:]).cast(typecode)
return cls(*memv)
注意,这里的 frombytes()
函数的命名,是参照标准库中 array.array.frombytes()
命名的。我也观察到一些第三方的库使用了 fromxxx
的命名方式。这可以说是一种 Pythonic 的命名方式。
classmethod Versus staticmethod
classmethod 有个非常实用的场景,是给一个类提供额外的构造能力(如上面的 frombytes
)。作者认为 staticmethod 没有应用场景,因为跟普通函数一模一样;如果你的函数跟某个类非常相关,那么在这个类附近写这个函数就好了,没必要用 staticmethod。我有点赞同。
Formatted Display
定义 __format__
函数,可以在使用 built-in format()
, str.format()
这些函数时,对你的类进行自定义的字符串输出。
A Hashable Vector2d
如果想把一个类放进 set 或作为 dict 的 key,需要让它 hashable。官方推荐的方式是将各个属性的 hash 值异或(bitwise XOR)起来:
def __hash__(self):
return hash(self.x) ^ hash(self.y)
一个实例的 hash 值不应该被改变(想想你已经把它用在 set 里面后,再改变 x, y 会引起的问题)。所以 x
, y
也不应该被改变。可以用 @property
实现对类属性的只读控制,从而实现 immutable:
class Vector2d:
typecode = 'd'
def __init__(self, x, y):
self.__x = float(x)
self.__y = float(y)
@property
def x(self):
return self.__x
@property
def y(self):
return self.__y
def __iter__(self):
return (i for i in (self.x, self.y))
使用 immutable 来达到 hashable 并不是语言强制的,但是它通常是一个好的实践。
Private and “Protected” Attributes in Python
Python 并没有提供类属性的 private / protected 机制。但是它在 name mangling 上做了一些事情,来避免一些错误的访问。
class A:
def __init__(self):
self.__a = 1
self._a = 2
def say(self):
print(self.__a)
print(self._a)
>>> a = A()
>>> a.__dict__
{'_a': 2, '_A__a': 1}
>>> a.say()
1
2
可以看到带有双下划线的 __a
被装饰成 _A__a
。在 A
类外部(甚至是子类)访问 __a
是会报错的。这样提供了一个机制来 避免 (而非 限制)错误的访问(你仍然可以用 _A__a
来访问到它)。
但是 __a
这种并不被推崇,因为引入了隐式的规则。不要使用它。用单下划线这种即可。
Saving Space with the __slots__
Class Attribute
class Vector2d:
__slots__ = ('__x', '__y')
typecode = 'd'
__slots__
变量定义了一个类的所有属性。存在 __slots__
后,你无法为一个类实例动态地新增属性。所以 Python 解释器会使用 tuple
结构来保存类属性,而不再用 dict
。这可以节约 很多 内存开销。但是 __slots__
还带来一些不便,比如子类需要重复声明 __slots__
,__dict__
属性消失,__weakref__
也需要特别对待等。
大多数情况下你不需要使用它。一般情况下它适用于类似数据库纪录的数据结构。如果你要用它,注意多看看文档。
Overriding Class Attributes
Python 类中有 class attributes 和 instance attributes 之分:
class Demo:
a = 1 # class attribute
def __init__(self):
self.b = 2 # instance attributes
>>> d1 = Demo()
>>> d2 = Demo()
>>> print(d1.a)
1
>>> d1.a = 3 # create instance attributes a, shadow class attribute a
>>> print(d1.a)
3
>>> print(d2.a)
1
Instance attributes 会覆盖同名的 class attributes。同时你在子类中以 class attribute 形式声明父类的同名 class attribute,也会覆盖掉父类的。这个机制可以用于继承和扩展已有的类。