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}