Python的魔術方法是Python中那些預定義的像__XXX__
型別的函式。
使用Python的魔術方法的最大優勢在於python提供了簡單的方法讓物件可以表現得像內建型別一樣。
__str__函式
__str__
函式用於處理列印例項本身的時候的輸出內容。如果沒有覆寫該函式,則預設輸出一個物件名稱和記憶體地址。
例如:
1 2 3 4 5 |
>>> class Student(object): ... def __init__(self,name): ... self._name = name ... >>> print Student() |
輸出:
1 |
<__main__.Student object at 0x0000000002A929E8>. |
那麼我們如何讓輸出的結果可讀性更高一點呢?我們可以覆寫__str__
函式。例如
1 2 3 4 5 6 7 |
>>> class Student(object): ... def __init__(self, name): ... self._name = name ... def __str__(self): ... return "I'm a student, named %s" % self._name ... >>> print Student("Charlie") |
輸出結果就是:I'm a student, named Charlie
.
我們將str()
函式作用於該物件的時候,其實是呼叫了該物件的__str__
函式。
_repr_ 函式
__repr__
也是將物件序列化,但是__repr__
更多的是給python編譯器看的。__str__
更多的是可讀性(readable)。
我們將repr()
函式作用於摸某一個物件的時候,呼叫的其實就是該函式的__repr__
函式。
與repr()
成對的是eval()
函式。eval()
函式是將序列化後的物件重新轉為物件。前提是該物件實現了__repr__
函式。
上面這一段話基於自己的理解,不知道對錯。
1 2 3 4 5 6 |
>>> item = [1,2,3] >>> repr(item) '[1, 2, 3]' >>> other_item = eval(repr(item)) >>> other_item[1] 2 |
__iter__函式
我們經常對list或者tuple使用for…in…來迭代。那是list繼承自Iterable。Iterable實現了__iter__函式。
要想將一個自定義的物件變成一個可迭代的物件,那麼必須要實現兩個方法:__iter__
和next
.
__iter__
函式返回一個物件。迭代的時候則會不斷地呼叫next
函式拿到下一個值,直到捕獲到StopIteration
停止。
廖雪峰老師教程裡寫的是__next__
方法,不知道為啥。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Fib(object): def __init__(self): self.a, self.b = 0, 1 def __iter__(self): return self def next(self): self.a, self.b = self.b, self.a + self.b if self.a > 10000: raise StopIteration return self.a for i in Fib(): print i |
__getitem__函式
上面通過實現__iter__
函式實現物件的迭代。
那麼如何實現物件按下標取出元素呢。
這是通過實現物件的__getitem__
方法。
我們來舉一個?子。我們新建了一個類MyList,我們要辦它實現普通list的一些功能,比如(1)根據下標獲取值;(2)正數順序單步長切片 (3)任意步長切片
1 2 3 4 5 6 7 8 9 10 |
class MyList(object): def __init__(self, *args): self.numbers = args def __getitem__(self, item): return self.numbers[item] my_list = MyList(1, 2, 3, 4, 6, 5, 3) print my_list[2] |
當然,上面實現了根據下標獲取值。但是這還不夠。我們還需要實現切片功能。例如my_list[1:3]
.
我們對物件進行切片操作的時候,呼叫的氣勢也是__getitem__
函式。此時,該函式獲取到的並不是int
物件,而是slice
物件。
例如下面的程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class MyList(object): def __init__(self, *args): self.numbers = args def __getitem__(self, item): if isinstance(item, int): return self.numbers[item] elif isinstance(item, slice): # 寫習慣了其他語言,差點忘記了三元運算子的格式了,吼吼吼。 # 下面句三元運算子的意思是,若為空,則為切片從0開始。 start = item.start if item.start is not None else 0 # 下面句三元運算子的意思是,若為空,則為切片到最末端結束。 stop = item.stop if item.stop is not None else len(self.numbers) return self.numbers[start:stop] my_list = MyList(1, 2, 3, 4, 6, 5, 3) print my_list[2:5] |
上面的程式碼終於實現了切片功能,但是還沒考慮負數呢。那麼我們加一把勁再來改一下。程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class MyList(object): def __init__(self, *args): self.numbers = args def __getitem__(self, item): if isinstance(item, int): return self.numbers[item] elif isinstance(item, slice): start = item.start if item.start is not None else 0 stop = item.stop if item.stop is not None else len(self.numbers) length = len(self.numbers) start = length + start + 1 if start < 0 else start stop = length + stop + 1 if stop < 0 else stop return self.numbers[start:stop] my_list = MyList(1, 2, 3, 4, 6, 5, 3) print my_list[1:-1] |
哇塞,寫完了,棒棒棒
_getattar_
在呼叫某一個物件不存在的屬性或者方法的時候,會丟擲一個一個AttributeError
錯誤。
但是如果我們實現了類中的魔術方法__getattar__
,那麼在呼叫不存在的屬性或者方法的時候,就會呼叫該魔術方法。
1 2 3 4 5 6 7 8 9 10 11 |
class Apple(object): def __getattr__(self, item): if item == "attar1": return "print" if item == "method1": return lambda x: "hello %s" % x apple = Apple() print apple.attar1 print apple.method1 |
__getattar__
函式一個重要的適用場景就是實現鏈式呼叫。例如我們在呼叫某一個api的時候:
1 |
GET users/articles/index |
那麼我們就希望我們的程式碼可以實現`Api.users.articles.index這麼呼叫。
思考一下,要實現鏈式呼叫,最重要的就是每一個呼叫都是返回一個例項~~。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# coding=utf-8 class Api(object): def __init__(self, path=''): self._path = path def __getattr__(self, name): return Api("%s/%s" % (self._path, name)) # 定義一個Post方法來傳送請求 def post(self): print self._path api = Api() api.user.articles.index.post() |
廖雪峰在他的教程中給我們出了一個題目:
例如呼叫github的api:users/:user/repos
一樣,中間的user名需要動態替換。
我們希望能api.users("charlie").repos
這麼呼叫。那麼程式碼該如何實現呢?這可能需要用到另一個方法__call__
_call_ 函式
一個物件既有屬性,又有方法。我們在呼叫一個例項的方法的時候,我們可以使用instance.method()
的形式呼叫。
其實也可以將例項本身看成一個函式用來呼叫,我們需要做的就是實現__call__
函式本身。
1 2 3 4 5 6 7 |
class Apple(object): def __call__(self, *args, **kwargs): return args apple = Apple() print apple("yes", "no") |
此時我們再來看一下上面提到的實現api.users("charlie").repos
鏈式呼叫的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# coding=utf-8 class Api(object): def __init__(self, path=''): self._path = path def __getattr__(self, name): return Api("%s/%s" % (self._path, name)) def __call__(self, args): self._path = "%s/%s" % (self._path, args) return Api(self._path) # 定義一個Post方法來傳送請求 def post(self): print self._path api = Api() api.users("Charlie").index.post() |