Python 规定所有对象都应该产生两种不同的字符串表示形式:一种是人类可解释的文本,另一种是 Python 可解释的表达式。字符串的构造函数 str 返回一个人类可读的字符串。在可能的情况下,repr 函数会返回一个计算结果相等的 Python 表达式。repr 的文档字符串解释了这个属性:
repr(object) -> stringReturn the canonical string representation of the object.
For most object types, eval(repr(object)) == object.</span></span>
对表达式的值调用 repr 的结果是 Python 在交互式会话中打印的结果。
>>> 12e12
12000000000000.0
>>> print(repr(12e12))
12000000000000.0
如果不存在计算结果为原始值的表示形式,Python 通常会生成一个用尖括号括起来的描述。
>>> repr(min) '<built-in function min>' str 构造函数通常与 repr 重合,但在某些情况下提供更易解释的文本表示形式。例如,我们看到 str 和 repr 之间的日期存在差异。
>>> from datetime import date
>>> tues = date(2011, 9, 12)
>>> repr(tues)
'datetime.date(2011, 9, 12)'
>>> str(tues)
'2011-09-12'
repr 函数总是在其参数上调用一个名为 __repr__ 的方法。
>>> tues.__repr__()
'datetime.date(2011, 9, 12)'
str 构造函数的实现方式与此类似:它对其参数调用名为 __str__ 的方法。
>>> tues.__str__()
'2011-09-12'
True 和 false 值。我们之前看到 Python 中的数字有一个真值;更具体地说,0 是 false 值,所有其他数字都是 true 值。事实上,Python 中的所有对象都有一个 truth 值。默认情况下,用户定义类的对象被视为 true,但可以使用特殊的 __bool__ 方法来覆盖此行为。如果对象定义了 __bool__ 方法,则 Python 会调用该方法来确定其真值。
例如,假设我们希望余额为 0 的银行账户为 false。我们可以向 Account 类添加一个 __bool__ 方法来创建此行为。
>>> Account.__bool__ = lambda self: self.balance != 0 我们可以调用 bool 构造函数来查看对象的真值,并且可以在布尔上下文中使用任何对象。
>>> bool(Account('Jack'))
False
>>> if not Account('Jack'):print('Jack has nothing')
Jack has nothing
序列作。我们已经看到,我们可以调用 len 函数来确定序列的长度。
>>> len('Go Bears!')
9
len 函数调用其参数的 __len__ 方法来确定其长度。所有内置序列类型都实现了此方法。
>>> 'Go Bears!'.__len__()
9
__getitem__ 方法由元素选择运算符调用,但也可以直接调用。
>>> 'Go Bears!'[3]
'B'
>>> 'Go Bears!'.__getitem__(3)
'B'
Callable 对象。在 Python 中,函数是一等对象,因此它们可以作为数据传递,并且具有与任何其他对象一样的属性。Python 还允许我们通过包含 __call__ 方法来定义可以像函数一样“调用”的对象。使用此方法,我们可以定义一个行为类似于高阶函数的类。
>>> def make_adder(n):def adder(k):return n + kreturn adder
>>> add_three = make_adder(3) >>> add_three(4) 7
>>> class Adder(object):def __init__(self, n):self.n = ndef __call__(self, k):return self.n + k
>>> add_three_obj = Adder(3) >>> add_three_obj(4) 7
复数可以用两种几乎等效的方式表示:矩形形式(实部和虚部)和极坐标形式(大小和角度)。有时矩形形式更合适,有时极性形式更合适。事实上,完全可以想象一个系统,其中复数以两种方式表示,并且用于作复数的函数与任何一种表示形式一起工作。我们在下面实现这样的系统。顺便说一句,我们正在开发一个对复数执行算术运算的系统,作为使用泛型运算的程序的一个简单但不切实际的示例。复数类型实际上内置于 Python 中,但在此示例中,我们将实现自己的类型。
>>> class Number:def __add__(self, other):return self.add(other)def __mul__(self, other):return self.mul(other)
>>> class Complex(Number):def add(self, other):return ComplexRI(self.real + other.real, self.imag + other.imag)def mul(self, other):magnitude = self.magnitude * other.magnitudereturn ComplexMA(magnitude, self.angle + other.angle)
- ComplexRI constructs a complex number from real and imaginary parts.
ComplexRI 从实部和虚部构造一个复数。 - ComplexMA constructs a complex number from a magnitude and angle.
ComplexMA 从大小和角度构造一个复数。
>>> from math import atan2 >>> class ComplexRI(Complex):def __init__(self, real, imag):self.real = realself.imag = imag@propertydef magnitude(self):return (self.real ** 2 + self.imag ** 2) ** 0.5@propertydef angle(self):return atan2(self.imag, self.real)def __repr__(self):return 'ComplexRI({0:g}, {1:g})'.format(self.real, self.imag)
Python 具有一个简单的功能,用于从零参数函数动态计算属性。@property 修饰器允许在没有 call 表达式语法(表达式后面的括号)的情况下调用函数。ComplexRI 类存储 real 和 imag 属性,并按需计算大小和角度。
同样,ComplexMA 类存储 magnitude 和 angle,但每当查找这些属性时,都会计算 real 和 imag。
>>> from math import sin, cos, pi
>>> class ComplexMA(Complex):def __init__(self, magnitude, angle):self.magnitude = magnitudeself.angle = angle@propertydef real(self):return self.magnitude * cos(self.angle)@propertydef imag(self):return self.magnitude * sin(self.angle)def __repr__(self):return 'ComplexMA({0:g}, {1:g} * pi)'.format(self.magnitude, self.angle/pi)
对幅值或角度的更改会立即反映在 real 和 imag 属性中。
>>> ma = ComplexMA(2, pi/2)
>>> ma.imag
2.0
>>> ma.angle = pi
>>> ma.real
-2.0
泛型函数是适用于不同类型参数的方法或函数。我们已经看到了很多例子。Complex.add 方法是通用的,因为它可以将 ComplexRI 或 ComplexMA 作为 other 的值。这种灵活性是通过确保 ComplexRI 和 ComplexMA 共享一个接口获得的。使用接口和消息传递只是用于实现泛型函数的几种方法之一。在本节中,我们将考虑另外两个:类型调度和类型强制。
假设,除了复数类之外,我们还实现了一个 Rational 类来精确表示分数。add 和 mul 方法表示与本章前面的 add_rational 和 mul_rational 函数相同的计算。
>>> from fractions import gcd
>>> class Rational(Number):def __init__(self, numer, denom):g = gcd(numer, denom)self.numer = numer // gself.denom = denom // gdef __repr__(self):return 'Rational({0}, {1})'.format(self.numer, self.denom)def add(self, other):nx, dx = self.numer, self.denomny, dy = other.numer, other.denomreturn Rational(nx * dy + ny * dx, dx * dy)def mul(self, other):numer = self.numer * other.numerdenom = self.denom * other.denomreturn Rational(numer, denom)
>>> from math import pi >>> ComplexRI(1, 2) + ComplexMA(2, pi/2) ComplexRI(1, 4) >>> ComplexRI(0, 1) * ComplexRI(0, 1) ComplexMA(1, 1 * pi) 能够直接用运算符进行操作的原因是在number class中,就已经进行了__add__ , __mul__的运算符重载
内置函数 isinstance 接受一个对象和一个类。如果对象具有一个类,则该类是给定类或继承自给定类,则返回 true。
>>> c = ComplexRI(1, 1)
>>> isinstance(c, ComplexRI)
True
>>> isinstance(c, Complex)
True
>>> isinstance(c, ComplexMA)
False
类型调度的一个简单示例是 is_real 函数,该函数对每种类型的复数使用不同的实现。
>>> def is_real(c):"""Return whether c is a real number with no imaginary part."""if isinstance(c, ComplexRI):return c.imag == 0elif isinstance(c, ComplexMA):return c.angle % pi == 0
>>> is_real(ComplexRI(1, 1))
False
>>> is_real(ComplexMA(2, pi))
True
类型调度并不总是使用 isinstance 执行。对于算术,我们将为 Rational 和 Complex 实例提供一个具有字符串值的 type_tag 属性。当两个值 x 和 y 具有相同的type_tag时,我们可以直接用 x.add(y) 将它们组合在一起。如果没有,我们需要一个 cross-type作。
>>> Rational.type_tag = 'rat'
>>> Complex.type_tag = 'com'
>>> Rational(2, 5).type_tag == Rational(1, 2).type_tag
True
>>> ComplexRI(1, 1).type_tag == ComplexMA(2, pi/2).type_tag
True
>>> Rational(2, 5).type_tag == ComplexRI(1, 1).type_tag
False
为了组合复数和有理数,我们编写了同时依赖于它们的两种表示形式的函数。下面,我们依赖于一个事实,即 Rational 可以近似地转换为一个实数的 float 值。结果可以与复数组合。
>>> def add_complex_and_rational(c, r):return ComplexRI(c.real + r.numer/r.denom, c.imag)
乘法涉及类似的转换。在极坐标形式中,复平面中的实数总是具有正大小。角度 0 表示正数。角度 pi 表示负数。
>>> def mul_complex_and_rational(c, r):r_magnitude, r_angle = r.numer/r.denom, 0if r_magnitude < 0:r_magnitude, r_angle = -r_magnitude, pireturn ComplexMA(c.magnitude * r_magnitude, c.angle + r_angle)
加法和乘法都是可交换的,因此交换参数 order 可以使用这些跨类型运算的相同实现。
>>> def add_rational_and_complex(r, c):return add_complex_and_rational(c, r)
>>> def mul_rational_and_complex(r, c):return mul_complex_and_rational(c, r)
我们使用 type_tag 属性来区分参数的类型。也可以直接使用内置的 isinstance 方法,但 tags 简化了实现。使用类型标签还说明了类型调度不一定链接到 Python 对象系统,而是一种在异构域上创建泛型函数的通用技术。
__add__ 方法考虑两种情况。首先,如果两个参数具有相同的 type 标签,则它假定第一个参数的 add 方法可以将第二个参数作为参数。否则,它会检查名为 adders 的跨类型实现字典是否包含可以添加这些类型标签的参数的函数。如果存在这样的函数,cross_apply 方法会查找并应用它。__mul__ 方法具有类似的结构。
>>> class Number:def __add__(self, other):if self.type_tag == other.type_tag:return self.add(other)elif (self.type_tag, other.type_tag) in self.adders:return self.cross_apply(other, self.adders)def __mul__(self, other):if self.type_tag == other.type_tag:return self.mul(other)elif (self.type_tag, other.type_tag) in self.multipliers:return self.cross_apply(other, self.multipliers)def cross_apply(self, other, cross_fns):cross_fn = cross_fns[(self.type_tag, other.type_tag)]return cross_fn(self, other)adders = {("com", "rat"): add_complex_and_rational,("rat", "com"): add_rational_and_complex}multipliers = {("com", "rat"): mul_complex_and_rational,("rat", "com"): mul_rational_and_complex}