python对象风格
对象表示形式
Python提供了两种方式。
repr()
以便于开发者理解的方式返回对象的字符串表示形式。
str()
以便于用户理解的方式返回对象的字符串表示形式。
正如你所知,我们要实现__repr__和__str__特殊方法,为repr()和str()提供支持。
再谈向量类
#示例1:Vector2d实例有多种表示形式
from array import array
import math
class Vector2d:
typecode = 'd'
def __init__(self, x, y):
self.x = float(x)
self.y = float(y)
def __iter__(self):
return (i for i in (self.x, self.y))
def __repr__(self):
class_name = type(self).__name__
return '{}({!r}, {!r})'.format(class_name, *self)
def __str__(self):
return str(tuple(self))
>>> v1 = Vector2d(3, 4)
>>> print(v1.x, v1.y) #➊
3.0 4.0
>>> x, y = v1 #➋
>>> x, y
(3.0, 4.0)
>>> v1 #➌
Vector2d(3.0, 4.0)
>>> v1_clone = eval(repr(v1)) #➍
>>>> v1 == v1_clone #➎
True
>>> print(v1) #➏
(3.0, 4.0)
>>> octets = bytes(v1) #➐
>>> octets
b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@'
>>> abs(v1) #➑
5.0
>>> bool(v1), bool(Vector2d(0, 0)) #➒
(True, False)
➊ Vector2d实例的分量可以直接通过属性访问(无需调用读值方法)。
➋ Vector2d实例可以拆包成变量元组。
➌ repr函数调用Vector2d实例,得到的结果类似于构建实例的源码。
➍ 这里使用eval函数,表明repr函数调用Vector2d实例得到的是对构造方法的准确表述。
➎ Vector2d实例支持使用==比较;这样便于测试。
➏ print函数会调用str函数,对Vector2d来说,输出的是一个有序对。
➐ bytes函数会调用__bytes__方法,生成实例的二进制表示形式。
➑ abs函数会调用__abs__方法,返回Vector2d实例的模。
➒ bool函数会调用__bool__方法,如果Vector2d实例的模为零,返回False,否则返回True。
#示例2:目前定义的都是特殊方法
from array import array
import math
class Vector2d:
typecode = 'd' #➊
def __init__(self, x, y):
self.x = float(x) #➋
self.y = float(y)
def __iter__(self):
return (i for i in (self.x, self.y)) #➌
def __repr__(self):
class_name = type(self).__name__
return '{}({!r}, {!r})'.format(class_name, *self) #➍
def __str__(self):
return str(tuple(self)) #➎
def __bytes__(self):
return (bytes([ord(self.typecode)]) + #➏
bytes(array(self.typecode, self))) #➐
def __eq__(self, other):
return tuple(self) == tuple(other) #➑
def __abs__(self):
return math.hypot(self.x, self.y) #➒
def __bool__(self):
return bool(abs(self)) #➓
➊ typecode是类属性,在Vector2d实例和字节序列之间转换时使用。
➋ 在__init__方法中把x和y转换成浮点数,尽早捕获错误,以防调用Vector2d函数时传入不当参数。
➌ 定义__iter__方法,把Vector2d实例变成可迭代的对象,这样才能拆包(例如,x, y = my_vector)。这个方法的实现方式很简单,直接调用生成器表达式一个接一个产出分量。
➍ __repr__方法使用{!r}获取各个分量的表示形式,然后插值,构成一个字符串;因为Vector2d实例是可迭代的对象,所以*self会把x和y分量提供给format函数。
➎ 从可迭代的Vector2d实例中可以轻松地得到一个元组,显示为一个有序对。
➏ 为了生成字节序列,我们把typecode转换成字节序列,然后……
➐ ……迭代Vector2d实例,得到一个数组,再把数组转换成字节序列。
➑ 为了快速比较所有分量,在操作数中构建元组。对Vector2d实例来说,可以这样做,不过仍有问题。参见下面的警告。
➒ 模是x和y分量构成的直角三角形的斜边长。
➓ __bool__方法使用abs(self)计算模,然后把结果转换成布尔值,因此,0.0是False,非零值是True。
备选构造方法
我们可以把Vector2d实例转换成字节序列了;同理,也应该能从字节序列转换成Vector2d实例。在标准库中探索一番之后,我们发现array.array有个类方法.frombytes正好符合需求。
#示例3:这段代码只列出了frombytes类方法
@classmethod #➊
def frombytes(cls, octets): #➋
typecode = chr(octets[0]) #➌
memv = memoryview(octets[1:]).cast(typecode) #➍
return cls(*memv) #➎
➊ 类方法使用classmethod装饰器修饰。
➋ 不用传入self参数;相反,要通过cls传入类本身。
➌ 从第一个字节中读取typecode。
➍ 使用传入的octets字节序列创建一个memoryview,然后使用typecode转换。
➎ 拆包转换后的memoryview,得到构造方法所需的一对参数。
classmethod与staticmethod
先来看classmethod。示例3展示了它的用法:定义操作类,而不是操作实例的方法。
classmethod改变了调用方法的方式,因此类方法的第一个参数是类本身,而不是实例。
classmethod最常见的用途是定义备选构造方法
staticmethod装饰器也会改变方法的调用方式,但是第一个参数不是特殊的值。其实,静态方法就是普通的函数,只是碰巧在类的定义体中,而不是在模块层定义。
示例4对classmethod和staticmethod的行为做了对比。
#示例4:比较classmethod和staticmethod的行为
>>> class Demo:
... @classmethod
... def klassmeth(*args):
... return args # ➊
... @staticmethod
... def statmeth(*args):
... return args # ➋
...
>>> Demo.klassmeth() # ➌
(<class '__main__.Demo'>,)
>>> Demo.klassmeth('spam')
(<class '__main__.Demo'>, 'spam')
>>> Demo.statmeth() # ➍
()
>>> Demo.statmeth('spam')
('spam',)
➊ klassmeth返回全部位置参数。
➋ statmeth也是。
➌ 不管怎样调用Demo.klassmeth,它的第一个参数始终是Demo类。
➍ Demo.statmeth的行为与普通的函数相似。