很多pythonic的代碼都會用到內置方法,根據自己的經驗,羅列一下自己知道的內置方法。
__getitem__ __setitem__ __delitem__
這三個方法是字典類的內置方法,分別對應於查找、設置、刪除操作,以一個簡單的例子說明:
class A(dict):
def __getitem__(self, key):
print '__getitem__'
return super(A, self).__getitem__(key)
def __setitem__(self, key, value):
print '__setitem__'
return super(A, self).__setitem__(key, value)
def __delitem__(self, key):
print '__delitem__'
return super(A, self).__delitem__(key)
a = A()
a[1] = 1
b = a[1]
del a[1]
a.get(1)
上面的代碼中a[1] = 1
實際上調用的就是a.__setitem__(1, 1)
, a[1]
調用的是a.__get__item__(1)
, del a[1]
調用的是a.__setitem__(1)
。需要注意的是,字典示例的get方法和__getitem__方法不存在調用關系,兩者互不影響。
__getattribute__ __getattr__ __setattr__ __delattr__
__getattribute__和__getattr__都是從python對象示例中獲取成員變量的方法,差別在於__getattribute__在任何時候都會調用,而__getattr__只有在__getattribute__執行完成之后並且沒有找到成員變量的時候才會執行。__setattr__在給成員變量賦值的時候調用,__delattr__在回收成員變量的時候調用,一下面的例子說明:
class A(object):
x = []
def __getattribute__(self, name):
print '__getattribute__'
return super(A, self).__getattribute__(name)
def __setattr__(self, key, value):
print '__setattr__'
return super(A, self).__setattr__(key, value)
def __getattr__(self, item):
print '__getattr__'
def __delattr__(self, item):
print '__delattr__'
return super(A, self).__delattr__(item)
a = A()
a.x
a.y
b = getattr(a, 'x')
b = getattr(a, 'y')
a.x = 1
a.y = 1
setattr(a, 'x', 1)
setattr(a, 'y', 1)
del a.x
del a.y
因為x是a的成員變量,a.x會調用a.__getattribue__('x')
,而y不是a的成員變量,在調用a.__getattribue__('y')
之后還會調用a.__getattr__('y')
,內置方法getattr也是按照此順序調用,唯一的區別在於getattr在成員變量不存在的時候不會拋出異常,而是給一個默認值。a.x = 1
和setattr(a, 'x', 1)
都會調用a.__setattr__('x', 1)
,如果原來的成員變量不存在,__setattr__會給實例增加一個成員變量,而不是拋出異常。
__call__
如果重載了__call__方法,則實例對象就可以像方法一樣調用了。如果實例a的類實現了這個方法,那么a(*args, **kwargs)
就會調用a.__call__(*args, **kwargs)
。用這種方法可以簡單方便的實現裝飾器,如下所示:
class A(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print '__call__'
return self.func(*args, **kwargs)
@A
def foo(x):
return x
foo(1)
裝飾器語法糖相當於foo = A(foo)
, 和閉包不同,foo已經不是一個函數而是類A的實例。foo(1)
會執行foo.__call__(1)
。當然這個例子實現的裝飾器並不好,會改變foo的函數簽名,而且也不能裝飾類方法。
此外元類的__call__也可以控制實例的創建過程,因為當類創建對象時,元類的__call__函數就被調用,進而調用type.__call__創建對象,type.__call__回依次調用類的__new__和__init__生成實例。以單例模式為例:
class Singleton(type):
def __call__(cls, *args):
print "Singleton call"
if not hasattr(cls, 'instance'):
cls.instance = super(Singleton, cls).__call__(*args)
return cls.instance
class Cache(object):
__metaclass__ = Singleton
def __new__(cls, *args):
print "Cache new"
return object.__new__(cls, *args)
def __init__(cls, *args, **kwargs):
print "Cache init"
a = Cache()
b = Cache()
print a is b
__get__ __set__
這三個方法分別對應於描述器的查詢、設置、刪除函數,還是以一個簡單的例子來說明:
class A(object):
def __get__(self, instance, owner):
print '__get__'
def __set__(self, instance, value):
print '__set__'
class B(object):
a = A()
b = B()
c = b.a
b.a = 1
類A的示例a作為b的成員變量時候,訪問示例b中成員變量a的時候會調用a.__get__(b, B)
,修改b中成員變量a的時候會調用a.__set__(b, 1)
。需要注意的是,當用B.a訪問的時候參數instance為None。實際上像classmethod,staticmethod這樣的裝飾器都是通過裝飾器來實現的,通過裝飾器就可以解決上面不能作為類方法裝飾器的問題。
import types
class Profiled(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
return self.func(*args, **kwargs)
def __get__(self, instance, owner):
if instance is None:
return self
else:
return types.MethodType(self, instance)
class Spam(object):
@Profiled
def bar(self, x):
return x + 1
s = Spam()
print s.bar(5)
在__get__方法中,不是Spam.bar調用,則instance不為None的情況下,會用types.MethodType把bar和s做一個綁定,s.bar(5)等價於執行了bar.__get__(s, Spam).__call__(5)
,看起來很繞,但實際上用起來很方便。