轉載: https://www.cnblogs.com/zhouyixian/p/11129347.html
構造和初始化
構造函數
__init__
我們很熟悉了,它在對象初始化的時候調用,我們一般將它理解為"構造函數".
實際上, 當我們調用x = SomeClass()
的時候調用,__init__
并不是第一個執行的,__new__
才是。所以準確來說,是__new__
和__init__
共同構成了"構造函數".
__new__
是用來創建類并返回這個類的實例, 而__init__
只是將傳入的參數來初始化該實例.
__new__
在創建一個實例的過程中必定會被調用,但__init__
就不一定,比如通過pickle.load
的方式反序列化一個實例時就不會調用__init__
。
__new__
方法總是需要返回該類的一個實例,而__init__
不能返回除了None的任何值。比如下面例子:
classFoo(object):
def__init__(self):
print 'foo __init__'
return None # 必須返回None,否則拋TypeError
def__del__(self):
print 'foo __del__'
實際中,你很少會用到
__new__
,除非你希望能夠控制類的創建。
如果要講解__new__
,往往需要牽扯到metaclass
(元類)的介紹。
對于__new__
的重載,Python文檔中也有了詳細的介紹。析構函數
當使用del 刪除對象時,會調用他本身的析構函數,另外當對象在某個作用域中調用完畢,在跳出其作用域的同時析構函數也會被調用一次,這樣可以用來釋放內存空間。
在對象的生命周期結束時,__del__
會被調用,可以將__del__
理解為"析構函數".
__del__
定義的是當一個對象進行垃圾回收時候的行為。
有一點容易被人誤解, 實際上,x.__del__()
并不是對于del x
的實現,但是往往執行del x
時會調用x.__del__()
.
怎么來理解這句話呢? 繼續用上面的Foo類的代碼為例:
foo = Foo()
foo.__del__()
print foo
del foo
print foo # NameError, foo is not defined
結果調用了
foo.__del__()
,對象本身仍然存在. 但是調用了del foo
, 就再也沒有foo這個對象了.
請注意,如果解釋器退出的時候對象還存在,就不能保證__del__
被確切的執行了。所以__del__
并不能替代良好的編程習慣。
比如,在處理socket時,及時關閉結束的連接。
屬性訪問控制
總有人要吐槽Python缺少對于類的封裝,比如希望Python能夠定義私有屬性,然后提供公共可訪問的getter和 setter。Python其實可以通過魔術方法來實現封裝。
__getattr__(self, name)
該方法定義了你試圖訪問一個不存在的屬性時的行為。因此,重載該方法可以實現捕獲錯誤拼寫然后進行重定向, 或者對一些廢棄的屬性進行警告。
__setattr__(self, name, value)
__setattr__
是實現封裝的解決方案,它定義了你對屬性進行賦值和修改操作時的行為。
不管對象的某個屬性是否存在,它都允許你為該屬性進行賦值,因此你可以為屬性的值進行自定義操作。有一點需要注意,實現__setattr__
時要避免"無限遞歸"的錯誤,下面的代碼示例中會提到。__delattr__(self, name)
__delattr__
與__setattr__
很像,只是它定義的是你刪除屬性時的行為。實現__delattr__
是同時要避免"無限遞歸"的錯誤。__getattribute__(self, name)
__getattribute__
定義了你的屬性被訪問時的行為,相比較,__getattr__
只有該屬性不存在時才會起作用。
因此,在支持__getattribute__
的Python版本,調用__getattr__
前必定會調用__getattribute__
。__getattribute__
同樣要避免"無限遞歸"的錯誤。
需要提醒的是,最好不要嘗試去實現__getattribute__
,因為很少見到這種做法,而且很容易出bug。例子說明
__setattr__
的無限遞歸錯誤:def __setattr__(self, name, value):
· · · · self.name = value
每一次屬性賦值時,__setattr__
都會被調用,因此不斷調用自身導致無限遞歸了。
因此正確的寫法應該是:
def __setattr__(self, name, value):
· · · · self.__dict__[name] = value
__delattr__
如果在其實現中出現del self.name
這樣的代碼也會出現"無限遞歸"錯誤,這是一樣的原因。下面的例子很好的說明了上面介紹的4個魔術方法的調用情況:
class Access(object):
def __getattr__(self, name):
print '__getattr__'
return super(Access, self).__getattr__(name)
def __setattr__(self, name, value):
print '__setattr__'
return super(Access, self).__setattr__(name, value)
def __delattr__(self, name):
print '__delattr__'
return super(Access, self).__delattr__(name)
def __getattribute__(self, name):
print '__getattribute__'
return super(Access, self).__getattribute__(name)
access = Access()
access.attr1 = True # __setattr__調用
access.attr1 # 屬性存在,只有__getattribute__調用
try:
access.attr2 # 屬性不存在, 先調用__getattribute__, 后調用__getattr__
except AttributeError:
pass
del access.attr1 # __delattr__調用
描述器對象
我們從一個例子來入手,介紹什么是描述符,并介紹
__get__, __set__, __delete__
的使用。
我們知道,距離既可以用單位"米"表示,也可以用單位"英尺"表示。
現在我們定義一個類來表示距離,它有兩個屬性: 米和英尺。
class Meter(object):
'''Descriptor for a meter.'''
def __init__(self, value=0.0):
self.value = float(value)
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
self.value = float(value)
class Foot(object):
'''Descriptor for a foot.'''
def __get__(self, instance, owner):
return instance.meter * 3.2808
def __set__(self, instance, value):
instance.meter = float(value) / 3.2808
class Distance(object):
meter = Meter()
foot = Foot()
d = Distance()
print d.meter, d.foot # 0.0, 0.0
d.meter = 1
print d.meter, d.foot # 1.0 3.2808
d.meter = 2
print d.meter, d.foot # 2.0 6.5616
在上面例子中,在還沒有對Distance的實例賦值前, 我們認為meter和foot應該是各自類的實例對象, 但是輸出卻是數值。這是因為
__get__
發揮了作用.
d.meter 實際調用的是meter.__get__,返回的是self.value,所以雖然d.meter調用的是對象返回的卻是數字.
我們只是修改了meter,并且將其賦值成為int,但foot也修改了。這是__set__
發揮了作用.
描述器對象(Meter、Foot)不能獨立存在, 它需要被另一個所有者類(Distance)所持有。
描述器對象可以訪問到其擁有者實例的屬性,比如例子中Foot的instance.meter。
在面向對象編程時,如果一個類的屬性有相互依賴的關系時,使用描述器來編寫代碼可以很巧妙的組織邏輯。
在Django的ORM中, models.Model中的IntegerField等, 就是通過描述器來實現功能的。
一個類要成為描述器,必須實現__get__, __set__, __delete__
中的至少一個方法。
下面簡單介紹下:__get__(self, instance, owner)
參數instance是擁有者類的實例。參數owner是擁有者類本身。
__get__
在其擁有者對其讀值的時候調用。__set__(self, instance, value)
__set__
在其擁有者對其進行修改值的時候調用。__delete__(self, instance)
__delete__
在其擁有者對其進行刪除的時候調用。
構造自定義容器(Container)
在Python中,常見的容器類型有: dict, tuple, list, string。
其中tuple, string是不可變容器,dict, list是可變容器。
可變容器和不可變容器的區別在于,不可變容器一旦賦值后,不可對其中的某個元素進行修改。
比如定義了l = [1, 2, 3]和t = (1, 2, 3)后, 執行l[0] = 0是可以的,但執行t[0] = 0則會報錯。
如果我們要自定義一些數據結構,使之能夠跟以上的容器類型表現一樣,那就需要去實現某些協議。
這里的協議跟其他語言中所謂的"接口"概念很像,一樣的需要你去實現才行,只不過沒那么正式而已。
如果要自定義不可變容器類型,只需要定義__len__
和__getitem__
方法;
如果要自定義可變容器類型,還需要在不可變容器類型的基礎上增加定義__setitem__
和__delitem__
。
如果你希望你的自定義數據結構還支持"可迭代", 那就還需要定義__iter__
。__len__(self)
需要返回數值類型,以表示容器的長度。該方法在可變容器和不可變容器中必須實現。
__getitem__(self, key)
當你執行self[key]的時候,調用的就是該方法。該方法在可變容器和不可變容器中也都必須實現。
調用的時候,如果key的類型錯誤,該方法應該拋出TypeError;
如果沒法返回key對應的數值時,該方法應該拋出ValueError。__setitem__(self, key, value)
當你執行self[key] = value時,調用的是該方法。
__delitem__(self, key)
當你執行del self[key]的時候,調用的是該方法。
__iter__(self)
該方法需要返回一個迭代器(iterator)。當你執行for x in container: 或者使用iter(container)時,該方法被調用。
__reversed__(self)
如果想要該數據結構被內建函數reversed()支持,就還需要實現該方法。
__contains__(self, item)
如果定義了該方法,那么在執行item in container 或者 item not in container時該方法就會被調用。
如果沒有定義,那么Python會迭代容器中的元素來一個一個比較,從而決定返回True或者False。__missing__(self, key)
dict字典類型會有該方法,它定義了key如果在容器中找不到時觸發的行為。
比如d = {'a': 1}, 當你執行d[notexist]時,d.__missing__('notexist')
就會被調用。
class FunctionalList:
""" 實現了內置類型list的功能,并豐富了一些其他方法: head, tail, init, last, drop, take"""
def __init__(self, values=None):
if values is None:
self.values = []
else:
self.values = values
def __len__(self):
return len(self.values)
def __getitem__(self, key):
return self.values[key]
def __setitem__(self, key, value):
self.values[key] = value
def __delitem__(self, key):
del self.values[key]
def __iter__(self):
return iter(self.values)
def __reversed__(self):
return FunctionalList(reversed(self.values))
def append(self, value):
self.values.append(value)
def head(self):
# 獲取第一個元素
return self.values[0]
def tail(self):
# 獲取第一個元素之后的所有元素
return self.values[1:]
def init(self):
# 獲取最后一個元素之前的所有元素
return self.values[:-1]
def last(self):
# 獲取最后一個元素
return self.values[-1]
def drop(self, n):
# 獲取所有元素,除了前N個
return self.values[n:]
def take(self, n):
# 獲取前N個元素
return self.values[:n]
上下文管理
with
聲明是從Python2.5開始引進的關鍵詞。你應該遇過這樣子的代碼:
with open('foo.txt') as bar:
· · ·# do something with bar
在·with·聲明的代碼段中,我們可以做一些對象的開始操作和清除操作,還能對異常進行處理。
這需要實現兩個魔術方法:__enter__
和__exit__
。__enter__(self)
__enter__
會返回一個值,并賦值給as關鍵詞之后的變量。在這里,你可以定義代碼段開始的一些操作。
def __exit__(self, exception_type, exception_value, traceback):
__exit__
定義了代碼段結束后的一些操作,可以這里執行一些清除操作,或者做一些代碼段結束后需要立即執行的命令,比如文件的關閉,socket斷開等。如果代碼段成功結束,那么exception_type, exception_value, traceback 三個參數傳進來時都將為None。如果代碼段拋出異常,那么傳進來的三個參數將分別為: 異常的類型,異常的值,異常的追蹤棧。
如果__exit__
返回True, 那么with聲明下的代碼段的一切異常將會被屏蔽。
如果__exit__
返回None, 那么如果有異常,異常將正常拋出,這時候with的作用將不會顯現出來。
舉例說明:
這該示例中,IndexError始終會被隱藏,而TypeError始終會拋出。
class DemoManager(object):
def __enter__(self):
pass
def __exit__(self, ex_type, ex_value, ex_tb):
if ex_type is IndexError:
print ex_value.__class__
return True
if ex_type is TypeError:
print ex_value.__class__
return # return None
with DemoManager() as nothing:
data = [1, 2, 3]
data[4] # raise IndexError, 該異常被__exit__處理了
with DemoManager() as nothing:
data = [1, 2, 3]
data['a'] # raise TypeError, 該異常沒有被__exit__處理
'''
輸出:
<type 'exceptions.IndexError'>
<type 'exceptions.TypeError'>
Traceback (most recent call last):
...
'''
對象的序列化
Python對象的序列化操作是pickling進行的。pickling非常的重要,以至于Python對此有單獨的模塊pickle,還有一些相關的魔術方法。使用pickling, 你可以將數據存儲在文件中,之后又從文件中進行恢復。
下面舉例來描述pickle的操作。從該例子中也可以看出,如果通過pickle.load 初始化一個對象, 并不會調用__init__
方法。
# -*- coding: utf-8 -*-
from datetime import datetime
import pickle
class Distance(object):
def __init__(self, meter):
print 'distance __init__'
self.meter = meter
data = {
'foo': [1, 2, 3],
'bar': ('Hello', 'world!'),
'baz': True,
'dt': datetime(2016, 10, 01),
'distance': Distance(1.78),
}
print 'before dump:', data
with open('data.pkl', 'wb') as jar:
pickle.dump(data, jar) # 將數據存儲在文件中
del data
print 'data is deleted!'
with open('data.pkl', 'rb') as jar:
data = pickle.load(jar) # 從文件中恢復數據
print 'after load:', data
值得一提,從其他文件進行pickle.load操作時,需要注意有惡意代碼的可能性。另外,Python的各個版本之間,pickle文件可能是互不兼容的。
pickling并不是Python的內建類型,它支持所有實現pickle協議(可理解為接口)的類。pickle協議有以下幾個可選方法來自定義Python對象的行為。__getinitargs__(self)
如果你希望unpickle時,
__init__
方法能夠調用,那么就需要定義__getinitargs__
, 該方法需要返回一系列參數的元組,這些參數就是傳給__init__
的參數。該方法只對old-style class有效。所謂old-style class,指的是不繼承自任何對象的類,往往定義時這樣表示: class A:, 而非class A(object):__getnewargs__(self)
跟
__getinitargs__
很類似,只不過返回的參數元組將傳值給__new__
__getstate__(self)
在調用pickle.dump時,默認是對象的
__dict__
屬性被存儲,如果你要修改這種行為,可以在__getstate__
方法中返回一個state。state將在調用pickle.load時傳值給__setstate__
__setstate__(self, state)
一般來說,定義了
__getstate__
,就需要相應地定義__setstate__
來對__getstate__
返回的state進行處理。__reduce__(self)
如果pickle的數據包含了自定義的擴展類(比如使用C語言實現的Python擴展類)時,就需要通過實現
__reduce__
方法來控制行為了。由于使用過于生僻,這里就不展開繼續講解了。
令人容易混淆的是,我們知道, reduce()是Python的一個內建函數, 需要指出__reduce__
并非定義了reduce()的行為,二者沒有關系。__reduce_ex__(self)
__reduce_ex__
是為了兼容性而存在的, 如果定義了__reduce_ex__
, 它將代替__reduce__
執行。
下面的代碼示例很有意思,我們定義了一個類Slate(中文是板巖的意思)。這個類能夠記錄歷史上每次寫入給它的值,但每次pickle.dump時當前值就會被清空,僅保留了歷史。
# -*- coding: utf-8 -*-
import pickle
import time
class Slate:
'''Class to store a string and a changelog, and forget its value when pickled.'''
def __init__(self, value):
self.value = value
self.last_change = time.time()
self.history = []
def change(self, new_value):
# 修改value, 將上次的valeu記錄在history
self.history.append((self.last_change, self.value))
self.value = new_value
self.last_change = time.time()
def print_changes(self):
print 'Changelog for Slate object:'
for k, v in self.history:
print '%s %s' % (k, v)
def __getstate__(self):
# 故意不返回self.value和self.last_change,
# 以便每次unpickle時清空當前的狀態,僅僅保留history
return self.history
def __setstate__(self, state):
self.history = state
self.value, self.last_change = None, None
slate = Slate(0)
time.sleep(0.5)
slate.change(100)
time.sleep(0.5)
slate.change(200)
slate.change(300)
slate.print_changes() # 與下面的輸出歷史對比
with open('slate.pkl', 'wb') as jar:
pickle.dump(slate, jar)
del slate # delete it
with open('slate.pkl', 'rb') as jar:
slate = pickle.load(jar)
print 'current value:', slate.value # None
print slate.print_changes() # 輸出歷史記錄與上面一致
運算符相關的魔術方法
比較運算符
__eq__(self, other)
定義了比較操作符==的行為.
__ne__(self, other)
定義了比較操作符!=的行為.
__lt__(self, other)
定義了比較操作符<的行為.
__gt__(self, other)
定義了比較操作符>的行為.
__le__(self, other)
定義了比較操作符<=的行為.
__ ge__(self, other
定義了比較操作符>=的行為.
下面我們定義一種類型Word, 它會使用單詞的長度來進行大小的比較, 而不是采用str的比較方式。
但是為了避免 Word('bar') == Word('foo') 這種違背直覺的情況出現,并沒有定義__eq__
, 因此Word會使用它的父類(str)中的__eq__
來進行比較。
# -*- coding: utf-8 -*-
class Word(str):
'''存儲單詞的類,定義比較單詞的幾種方法'''
def __new__(cls, word):
# 注意我們必須要用到__new__方法,因為str是不可變類型
# 所以我們必須在創建的時候將它初始化
if ' ' in word:
print "Value contains spaces. Truncating to first space."
word = word[:word.index(' ')] # 單詞是第一個空格之前的所有字符
return str.__new__(cls, word)
def __gt__(self, other):
return len(self) > len(other)
def __lt__(self, other):
return len(self) < len(other)
def __ge__(self, other):
return len(self) >= len(other)
def __le__(self, other):
return len(self) <= len(other)
print 'foo < fool:', Word('foo') < Word('fool') # True
print 'foolish > fool:', Word('foolish') > Word('fool') # True
print 'bar >= foo:', Word('bar') >= Word('foo') # True
print 'bar <= foo:', Word('bar') <= Word('foo') # True
print 'bar == foo:', Word('bar') == Word('foo') # False, 用了str內置的比較方法來進行比較
print 'bar != foo:', Word('bar') != Word('foo') # True
一元運算符和函數
__pos__(self)
實現了'+'號一元運算符(比如+some_object)
__neg__(self)
實現了'-'號一元運算符(比如-some_object)
__invert__(self)
實現了~號(波浪號)一元運算符(比如~some_object)
__abs__(self)
實現了abs()內建函數.
__round__(self, n)
實現了round()內建函數. 參數n表示四舍五進的精度.
__floor__(self)
實現了math.floor(), 向下取整.
__ceil__(self)
實現了math.ceil(), 向上取整.
__trunc__(self)
實現了math.trunc(), 向0取整.
算術運算符
__add__(self, other)
實現了加號運算.
__sub__(self, other)
實現了減號運算.
__mul__(self, other)
實現了乘法運算.
__floordiv__(self, other)
實現了//運算符.
__truediv__(self, other)
實現了true division. 只有你聲明了
from __future__ import division
該方法才會生效.__mod__(self, other)
實現了%運算符, 取余運算.
__divmod__(self, other)
實現了divmod()內建函數.
__pow__(self, other)
實現了**操作. N次方操作.
__lshift__(self, other)
實現了位操作<<.
__rshift__(self, other)
實現了位操作>>.
__and__(self, other)
實現了位操作&.
__or__(self, other)
實現了位操作|
__xor__(self, other)
實現了位操作^
反算術運算符
這里只需要解釋一下概念即可。
假設針對some_object這個對象:
some_object + other
上面的代碼非常正常地實現了some_object的__add__
方法。那么如果遇到相反的情況呢?
other + some_object
這時候,如果other沒有定義__add__
方法,但是some_object定義了__radd__
, 那么上面的代碼照樣可以運行。這里的__radd__(self, other)
就是__add__(self, other)
的反算術運算符。
所以,類比的,我們就知道了更多的反算術運算符, 就不一一展開了:__rsub__(self, other)
__rmul__(self, other)
__rmul__(self, other)
__rfloordiv__(self, other)
__rdiv__(self, other)
__rtruediv__(self, other)
__rmod__(self, other)
__rdivmod__(self, other)
__rpow__(self, other)
__rlshift__(self, other)
__rrshift__(self, other)
__rand__(self, other)
__ror__(self, other)
__rxor__(self, other)
增量賦值
這也是只要理解了概念就容易掌握的運算。舉個例子:
x = 5
x += 1 # 這里的+=就是增量賦值,將x+1賦值給了x,
因此對于a += b,__iadd__
將返回a + b, 并賦值給a。
所以很容易理解下面的魔術方法了:__iadd__(self, other)
__isub__(self, other)
__imul__(self, other)
__ifloordiv__(self, other)
__idiv__(self, other)
__itruediv__(self, other)
__imod__(self, other)
__ipow__(self, other)
__ilshift__(self, other)
__irshift__(self, other)
__iand__(self, other)
__ior__(self, other)
__ixor__(self, other)
類型轉化
__int__(self)
實現了類型轉化為int的行為.
__long__(self)
實現了類型轉化為long的行為.
__float__(self)
實現了類型轉化為float的行為.
__complex__(self)
實現了類型轉化為complex(復數, 也即1+2j這樣的虛數)的行為.
__oct__(self)
實現了類型轉化為八進制數的行為.
__hex__(self)
實現了類型轉化為十六進制數的行為.
__index__(self)
在切片運算中將對象轉化為int, 因此該方法的返回值必須是int。
用一個例子來解釋這個用法。
class Thing(object):
def __index__(self):
return 1
thing = Thing()
list_ = ['a', 'b', 'c']
print list_[thing] # 'b'
print list_[thing:thing] # []
上面例子中, list_[thing]的表現跟list_[1]一致,正是因為Thing實現了
__index__
方法。
可能有的人會想,list_[thing]為什么不是相當于list_[int(thing)]呢? 通過實現Thing的__int__
方法能否達到這個目的呢?
顯然不能。如果真的是這樣的話,那么list_[1.1:2.2]這樣的寫法也應該是通過的。
而實際上,該寫法會拋出TypeError: slice indices must be integers or None or have an __index__ method
下面我們再做個例子,如果對一個dict對象執行dict_[thing]會怎么樣呢?
dict_ = {1: 'apple', 2: 'banana', 3: 'cat'}
print dict_[thing] # raise KeyError
這個時候就不是調用
__index__
了。雖然list和dict都實現了__getitem__
方法, 但是它們的實現方式是不一樣的。
如果希望上面例子能夠正常執行, 需要實現Thing的__hash__
和__eq__
方法.
class Thing(object):
def __hash__(self):
return 1
def __eq__(self, other):
return hash(self) == hash(other)
dict_ = {1: 'apple', 2: 'banana', 3: 'cat'}
print dict_[thing] # apple
其他魔術方法
__str__(self)
對實例使用str()時調用。
__repr__(self)
對實例使用repr()時調用。str()和repr()都是返回一個代表該實例的字符串,
主要區別在于: str()的返回值要方便人來看,而repr()的返回值要方便計算機看。__unicode__(self)
對實例使用unicode()時調用。unicode()與str()的區別在于: 前者返回值是unicode, 后者返回值是str。unicode和str都是basestring的子類。
當你對一個類只定義了__str__
但沒定義__unicode__
時,__unicode__
會根據__str__
的返回值自動實現,即return unicode(self.__str__())
;但返回來則不成立。
class StrDemo2:
def __str__(self):
return 'StrDemo2'
class StrDemo3:
def __unicode__(self):
return u'StrDemo3'
demo2 = StrDemo2()
print str(demo2) # StrDemo2
print unicode(demo2) # StrDemo2
demo3 = StrDemo3()
print str(demo3) # <__main__.StrDemo3 instance>
print unicode(demo3) # StrDemo3
__format__(self, formatstr)
"Hello, {0:abc}".format(a)等價于format(a, "abc"), 等價于
a.__format__("abc")
。
這在需要格式化展示對象的時候非常有用,比如格式化時間對象。__hash__(self)
對實例使用hash()時調用, 返回值是數值類型。
__bool__(self)
對實例使用bool()時調用, 返回True或者False。
__dir__(self)
對實例使用dir()時調用,返回模塊的屬性列表。通常實現該方法是沒必要的。
__sizeof__(self)
對實例使用sys.getsizeof()時調用。返回對象的大小,單位是bytes。
__instancecheck__(self, instance)
對實例調用isinstance(instance, class)時調用。 返回值是布爾值。它會判斷instance是否是該類的實例。
__subclasscheck__(self, subclass)
對實例使用issubclass(subclass, class)時調用。返回值是布爾值。它會判斷subclass否是該類的子類。
__copy__(self)
對實例使用copy.copy()時調用。返回"淺復制"的對象。
__deepcopy__(self, memodict={})
對實例使用copy.deepcopy()時調用。返回"深復制"的對象。
__call__(self, [args...])
使得類實例對象可以像調用普通函數那樣,以“對象名()”的形式使用。
class CLanguage:
# 定義__call__方法
def __call__(self,name,add):
print("調用__call__()方法",name,add)
clangs = CLanguage()
clangs("C語言中文網","http://c.biancheng.net")
>> 調用__call__()方法 C語言中文網 http://c.biancheng.net