符合python风格的对象
先来看一个向量类的例子
class Vector2d:
typecode = 'd'
"""使用两个或者一个前导下划线,把属性标记为私有的"""
def __init__(self, x, y):
self.__x = float(x)
self.__y = float(y)
"""@property 装饰器把读值方法标记为特性"""
@property
def x(self):
return self.__x
@property
def y(self):
return self.__y
"""定义__iter__ 方法,把Vector2d实例变成可迭代的对象,这样才能拆包(例如,x, y = my_vector)"""
def __iter__(self):
return (i for i in (self.x, self.y))
"""repr 函数调用Vector2d 实例,得到的结果类似于构建实例的源码"""
def __repr__(self):
class_name = type(self).__name__
return '{}({!r}, {!r})'.format(class_name, *self)
"""print 函数会调用str 函数,对Vector2d 来说,输出的是一个有序对"""
def __str__(self):
return str(tuple(self))
"""bytes 函数会调用__bytes__ 方法,生成实例的二进制表示形式"""
def __bytes__(self):
return (bytes([ord(self.typecode)]) + bytes(array(self.typecode, self)))
"""Vector2d 实例支持使用== 比较;这样便于测试"""
def __eq__(self, other):
return tuple(self) == tuple(other)
"""定义hash, 让实例成为可散列对象"""
def __hash__(self):
return hash(self.x) ^ hash(self.y)
"""abs 函数会调用__abs__ 方法,返回Vector2d 实例的模"""
def __abs__(self):
return math.hypot(self.x, self.y)
"""bool 函数会调用__bool__ 方法,如果Vector2d 实例的模为零,返回False,否则返回True"""
def __bool__(self):
return bool(abs(self))
def angle(self):
return math.atan2(self.y, self.x)
"""内置的format() 函数和str.format() 方法把各个类型的格式化方式委托给相应的
.__format__(format_spec) 方法"""
def __format__(self, fmt_spec=''):
if fmt_spec.endswith('p'):
fmt_spec = fmt_spec[:-1]
coords = (abs(self), self.angle())
outer_fmt = '<{}, {}>'
else:
coords = self
outer_fmt = '({}, {})'
components = (format(c, fmt_spec) for c in coords)
return outer_fmt.format(*components)
"""类方法使用classmethod 装饰器修饰,不用传入self 参数;相反,要通过cls 传入类本身 """
@classmethod
def frombytes(cls, octets):
typecode = chr(octets[0])
memv = memoryview(octets[1:]).cast(typecode)
return cls(*memv)
classmethod与staticmethod
- classmethod: 类方法定义操作类,而不是操作实例的方法,第一个参数是类本身
- staticmethod: staticmethod装饰器也会改变方法的调用方式,但是第一个参数不是特殊的值。其实,静态方法就是普通的函数,只是碰巧在类的定义体中,而不是在模块层定义
覆盖类属性
- python有个很独特的特性:类属性可用于为实例属性提供默认值
- 假如我们为类实例属性复制,那么同名类属性不受影响
序列的修改、散列和切片
协议和鸭子类型
在面向对象编程中,协议是非正式的接口,只在文档中定义,在代码中不定义。例如,Python 的序列协议只需要__len__ 和__getitem__ 两个方法。任何类,只要使用标准的签名和语义实现了这两个方法,就能用在任何期待序列的地方。
import collections
Card = collections.namedtuple('Card', ['rank', 'suit'])
class FrenchDeck:
ranks = [str(n) for n in range(2, 11)] + list('JQKA')
suits = 'spades diamonds clubs hearts'.split()
def __init__(self):
self._cards = [Card(rank, suit) for suit in self.suits
for rank in self.ranks]
def __len__(self):
return len(self._cards)
def __getitem__(self, position):
return self._cards[position]
切片原理
用一个例子来看看切片的原理
>>> class MySeq:
... def __getitem__(self, index):
... return index
...
>>> s = MySeq()
>>> s[1]
1
>>> s[1:4]
slice(1, 4, None)
>>> s[1:4:2] # 从1开始,到4结束,步幅为2
slice(1, 4, 2)
>>> s[1:4:2, 9] # 如果[]中有逗号,__getitem__收到的是元组
(slice(1, 4, 2), 9)
>>> s[1:4:2, 7:9] # 返回多个切片对象
(slice(1, 4, 2), slice(7, 9, None))
>>> dir(slice) # 查看slice类的属性
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__',
'__format__', '__ge__', '__getattribute__', '__gt__',
'__hash__', '__init__', '__le__', '__lt__', '__ne__',
'__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__setattr__', '__sizeof__', '__str__', '__subclasshook__',
'indices', 'start', 'step', 'stop']
>>> slice(None, 10, 2).indices(5)
(0, 5, 2)
>>> slice(-3, None, None).indices(5)
(2, 5, 1)
>>> help(slice.indices) # 查看slice.indices方法的信息
可切片的序列
来看看,可切片的Vector类如何实现, Vector实例的切片仍然是Vector实例
from array import array
import math
import operator
class Vector:
typecode = 'd'
def __init__(self, components):
# self._components 是“受保护的”实例属性,把Vector 的分量保存在一个数组中
self._components = array(self.typecode, components)
# 支持序列属性
def __len__(self):
return len(self._compoents)
def __getitem__(self, index):
# 获取实例所属的类
cls = type(self)
# 如果参数使slice对象
if isinstance(index, slice):
return cls(self._components[index])
# 如果index是整数类型
elif isinstance(index, numbers.Integral):
return self._components[index]
# 否则抛出异常
else:
msg = '{cls.__name__} indices must be integers'
raise TypeError(msg.format(cls=cls))
def __iter__(self):
return iter(self._components)
# 将Vector实例变成可散列的对象
def __hash__(self):
hashs = map(hash, self._compoents)
return functools.reduce(operator.xor, hashes)
def __eq__(self, other):
# 对于有几千个分量来说,效率十分低下
# return tuple(self) == tuple(other)
if len(self) != len(other):
return False
for a, b in zip(self, other):
if a != b:
return False
return True
def __repr__(self):
components = reprlib.repr(self._components)
components = components[components.find('['):-1]
return 'Vector({})'.format(components)
def __str__(self):
return str(tuple(self))
def __bytes__(self):
return (bytes([ord(self.typecode)]) + bytes(self._components))
def __abs__(self):
return math.sqrt(sum(x * x for x in self))
def __bool__(self):
return bool(abs(self))
@classmethod
def frombytes(cls, octets):
typecode = chr(octets[0])
memv = memoryview(octets[1:]).cast(typecode)
return cls(memv)
# 单个整数索引只获取分量,值为浮点数
>>> v7 = Vector(range(7))
>>> v7[-1]
6.0
# 切片索引返回一个新Vector实例
>>> v7[1:4]
Vector([1.0, 2.0, 3.0])
>>> v7[-1:]
Vector([6.0])
>>> v7[1,2]
Traceback (most recent call last):
...
TypeError: Vector indices must be integers
动态属性存取*
使用@property 装饰器可以把x 和y 标记为只读特性)。我们可以在Vector 中编写这样的特性,但这样太麻烦。特殊方法__getattr__ 提供了更好的方式。属性查找失败后,解释器会调用__getattr__方法。
简单来说,对my_obj.x 表达式,Python会检查my_obj实例有没有名为x的属性;如果没有,到类(my_obj.__class__)中查找;如果还没有,顺着继承树继续查找。如果依旧找不到,调用my_obj所属类中定义的__getattr__方法,传入self 和属性名称的字符串形式
shortcut_names = 'xyzt'
def __getattr__(self, name):
# 获取Vector类
cls = type(self)
# 如果只有一个字母,找到所在的位置
if len(name) == 1:
pos = cls.shortcut_names.find(name)
if 0 <= pos < len(self._components):
# 返回对应位置的元素
return self._components[pos]
msg = '{.__name__!r} object has no attribute
raise AttributeError(msg.format(cls, name))
>>> v = Vector(range(5))
>>> v
Vector([0.0, 1.0, 2.0, 3.0, 4.0])
# 获取第一个元素
>>> v.x
0.0
# 为v.x赋值,这个操作应该抛出异常
>>> v.x = 10
>>> v.x #
10
# 向量的分量没变
>>> v
Vector([0.0, 1.0, 2.0, 3.0, 4.0])
示例之所以前后矛盾,是__getattr__的运作方式导致的:仅当对象没有指定名称的属性时,Python 才会调用那个方法,这是一种后备机制。
可是,像v.x = 10 这样赋值之后,v 对象有x 属性了,因此使用v.x 获取x 属性的值时不会调用_getattr_ 方法了,解释器直接返回绑定到v.x 上的值,即10。另一方面,_getattr_ 方法的实现没有考虑到self._components 之外的实例属性,而是从这个属性中获取shortcut_names 中所列的“虚拟属性”。
我们改写Vector类中的属性设置逻辑,如果为单个小写字母属性赋值,我们想抛出异常,实现__setattr__方法
def __setattr__(self, name, value):
cls = type(self)
if len(name) == 1:
# 如果name是xyzt中的一个,设置错误消息
if name in cls.shortcut_names:
error = 'readonly attribute {attr_name!r}'
elif name.islower():
error = "can't set attributes 'a' to 'z' in {cls_name!r}"
else:
error = ''
if error:
msg = error.format(cls_name=cls.__name__, attr_name=name)
raise AttributeError(msg)
# 默认情况下,在超类上调用__setattr__方法,提供标准行为
super().__setattr__(name, value)