第042講:魔法方法:算術運算1
課題筆記:
啥是工廠函式?
len()是一個BIF,作用是返回引數的長度。type(len)的話返回。
如果說type(int)則他是返回一個class “type”。就如同
C定義完了後就是一個類物件。所以所謂的“工廠函式”就是類物件。
int(‘123’),在以前就是呼叫int函式,將引數轉化為123這個整形。而現在不一樣了,它是例項化int的這個物件,然後返回一個它示例後的物件。例如:
>>> a = int('123') >>> b = int('456') >>> a +b579
原來物件是可以進行計算的。
Python的魔法方法還提供了讓你自定義物件的數值處理,通過對我們這些魔法方法的一個重寫,你可以自定義任何物件間的算術運算。
例子:
>>> class New_int(int): def __add__(self, other): return int.__sub__(self, other) def __sub__(self, other): return int.__add__(self, other) >>> a = New_int(3) >>> b = New_int(5)>>> a + b -2 >>> a - b 8
我這邊 可以不return int的魔法方法嗎?
>>> class Try_int(int): def __add__(self, other): return self + other def __sub__(self, other): return self - other >>> a = Try_int(3) >>> b = Try_int(5) >>> a + b Traceback (most recent call last): File"<pyshell#34>", line 1, in <module> a + b File "<pyshell#31>", line 3, in __add__ return self + other File "<pyshell#31>", line 3, in __add__ return self + other File "<pyshell#31>", line 3, in __add__ return self + other [Previous line repeated 1022 more times] RecursionError: maximum recursion depth exceeded >>>
結果出現了無限遞迴!去到了遞迴的最高層次就出來了,自動退出了。為什麼會這樣子呢?
當a+b的時候出現加法,自動先呼叫前者a的一個add,先呼叫一個add 的一個魔法方法;那呼叫add 的話返回的是什麼?返回的是self加上other,self是示例物件繫結的一個方式(繫結進來的方式),那麼這個self就是綁定了這個a進來,other就是加法右邊的數(也就是b);那return的又是a+b,這樣就出現無限遞迴。
後面會講到關於屬性的魔法方法,更要注意這些情況了。
可以這樣改:
>>> class Try_int(int): def __add__(self, other): return int(self) + int(other) #也可以不用int這個other def __sub__(self, other): return int(self) - int(other) >>> a = Try_int(3) >>> b = Try_int(5) >>> a + b 8 >>>
測試題:
0. 自 Python2.2 以後,對類和型別進行了統一,做法就是將 int()、float()、str()、list()、tuple() 這些 BIF 轉換為工廠函式。請問所謂的工廠函式,其實是什麼原理?
工廠函式就是將引數例項化成物件,然後呼叫其中的魔法方法
答:工廠函式,其實就是一個類物件。當你呼叫他們的時候,事實上就是建立一個相應的例項物件。
# a 和 b 是工廠函式(類物件) int 的例項物件 >>> a = int('123') >>> b = int('345') >>> a + b 468
1. 當例項物件進行加法操作時,會自動呼叫什麼魔法方法?
自動呼叫__add__(self, other)
答:物件 a 和 b 相加時(a + b),Python 會自動根據物件 a 的 __add__ 魔法方法進行加法操作。
2. 下邊程式碼有問題嗎?(執行起來似乎沒出錯的說^_^)
class Foo: def foo(self): self.foo = "I love FishC.com!" return self.foo >>> foo = Foo() >>> foo.foo() 'I love FishC.com!'
foo是Foo示例化的一個物件,foo.foo()就是呼叫Foo類裡面的foo這個函式? self就是foo, self.foo就是物件foo的foo屬性等於一串字串,foo函式最後返回這個屬性
答:這絕對是一個溫柔的陷阱,這種BUG比較難以排查,所以一定要注意:類的屬性名和方法名絕對不能相同!如果程式碼這麼寫,就會有一個難以排查的BUG出現了:
class Foo: def __init__(self): self.foo = "I love FishC.com!" def foo(self): return self.foo >>> foo = Foo() >>> foo.foo() Traceback (most recent call last): File "<pyshell#21>", line 1, in <module> foo.foo() TypeError: 'str' object is not callable
3. 寫出下列算術運算子對應的魔法方法:
運算子 | 對應的魔法方法 |
+ | __add__(self, other) |
- | __sub__(self, other) |
* | __mul__(self, other) |
/ | __truediv__(self, other) |
// | __floordiv__(self, other) |
% | __mod__(self, other) |
divmod(a, b) | __divmod__(self, other) |
** | __pow__(self, other[,modulo]) |
<< | __lshift__(self, other) |
>> | __rshift__(self, other) |
& | __and___(self, other) |
^ | __xor___(self, other) |
| | __or___(self, other) |
4. 以下程式碼說明 Python 支援什麼風格?
def calc(a, b, c): return (a + b) * c >>> a = calc(1, 2, 3) >>> b = calc([1, 2, 3], [4, 5, 6], 2) >>> c = calc('love', 'FishC', 3) >>> print(a) 9 >>> print(b) [1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6] >>> print(c) loveFishCloveFishCloveFishC
物件間可以進行相加
答:說明 Python 支援鴨子型別(duck typing)風格。詳見:【擴充套件閱讀】鴨子型別(duck typing)
動動手:
0. 我們都知道在 Python 中,兩個字串相加會自動拼接字串,但遺憾的是兩個字串相減卻丟擲異常。因此,現在我們要求定義一個 Nstr 類,支援字串的相減操作:A – B,從 A 中去除所有 B 的子字串。
示例:
>>> a = Nstr('I love FishC.com!iiiiiiii') >>> b = Nstr('i') >>> a - b 'I love FshC.com!'
自己寫的:
class Nstr(str): def __sub__(self, other): return str.split(self,other)
答案:
class Nstr(str): def __sub__(self, other): return self.replace(other, '')
1. 移位操作符是應用於二進位制運算元的,現在需要你定義一個新的類 Nstr,也支援移位操作符的運算:
>>> a = Nstr('I love FishC.com!') >>> a << 3 'ove FishC.com!I l' >>> a >> 3 'om!I love FishC.c'
答:只需要過載 __lshift__ 和 __rshift__ 魔法方法即可。
class Nstr(str): def __lshift__(self, other): return self[other:] + self[:other] def __rshift__(self, other): return self[-other:] + self[:-other]
2. 定義一個類 Nstr,當該類的例項物件間發生的加、減、乘、除運算時,將該物件的所有字串的 ASCII 碼之和進行計算:
>>> a = Nstr('FishC') >>> b = Nstr('love') >>> a + b 899 >>> a - b 23 >>> a * b 201918 >>> a / b 1.052511415525114 >>> a // b 1
程式碼清單:
class Nstr: def __init__(self, arg=''): if isinstance(arg, str): self.total = 0 for each in arg: self.total += ord(each) else: print("引數錯誤!") def __add__(self, other): return self.total + other.total def __sub__(self, other): return self.total - other.total def __mul__(self, other): return self.total * other.total def __truediv__(self, other): return self.total / other.total def __floordiv__(self, other): return self.total // other.total
也可以這樣寫:
class Nstr(int): def __new__(cls, arg=0): if isinstance(arg, str): total = 0 for each in arg: total += ord(each) arg = total return int.__new__(cls, arg)
第二種方法是用了繼承了int 的屬性,利用new在之前就把字串先轉成了ASCII碼,然後用int的屬性,int型別是有加減乘除這些的。