python描述器介紹
簡介
描述器功能強大,應用廣泛,它可以控制我們訪問屬性、方法的行為,是@property、super、靜態方法、類方法、甚至屬性、例項背後的實現機制,是一種比較底層的設計,因此理解起來也會有一些困難。它們在 Python 內部被廣泛使用來實現自 2.2 版中引入的新式類
定義
一個描述器是一個包含 “繫結行為” 的物件,對其屬性的訪問被描述器協議中定義的方法覆蓋。這些方法有:__get__()
,__set__()
和 __delete__()
。如果某個物件中定義了這些方法中的任意一個,那麼這個物件就可以被稱為一個描述器。
型別
- 描述符有__get__和__set__2個方法
- 非資料描述符只有一個__get__方法,通常用於方法。非資料描述符的優先順序低於例項屬性。
簡單例項
class RevealAccess(object):
"""A data descriptor that sets and returns values
normally and prints a message logging their access.
"""
def __init__(self, initval=None, name='var'):
self.val = initval
self.name = name
def __get__(self, obj, objtype):
print('Retrieving', self.name)
return self.val
def __set__(self, obj, val):
print('Updating', self.name)
self.val = val
class MyClass(object):
x = RevealAccess(10, 'var "x"')
y = 5
m = MyClass()
print("="*20)
print(m.x)
print("="*20)
m.x = 20
print(m.x)
print("="*20)
print(m.y)
輸出:
====================
Retrieving var "x"
10
====================
Updating var "x"
Retrieving var "x"
20
====================
5
上面這個例子中:
- 建立m例項和普通類沒什麼區別,我們從m.x開始看
m.x
是m例項呼叫了x這個類屬性,然而這個類屬性不是普通的值,而是一個描述器,所以我們從訪問這個類屬性變成了訪問這個描述器- 如果呼叫時得到的是一個描述器,python內部就會自動觸發一套使用機制
- 訪問的話自動觸發描述器的
__get__
方法 - 修改設定的話就自動觸發描述器的
__set__
方法 - 這裡就是m
.x
觸發了__get__
方法,得到的是self.value
的值,在前面__init__
中定義的為10 m.x = 20
則觸發了__set__
方法,賦的值20傳到value
引數之中,改變了self.value
的值,所以下一次m.x呼叫的值也改變了
描述器的訪問
整個描述器的核心是__getattribute__()
,因為對像任何屬性的訪問都會呼叫到這個特殊的方法。這個方法被用來查詢屬性,同時也是你的一個代理,呼叫它可以進行屬性的訪問操作。
一般我們的類的__getattribute__()
方法都是繼承自object
,自己改寫__getattribute__()
是很危險的,也會阻止正常的描述器呼叫。__getattribute__()
的Python描述原型如下:
def __getattribute__(self, key):
"Emulate type_getattro() in Objects/typeobject.c"
v = object.__getattribute__(self, key)
if hasattr(v, '__get__'):
return v.__get__(None, self)
return v
如果通過例項ins
訪問描述器,由__getattribute__()
轉化為:type(ins).__dict__['attr'].__get__(ins, type(ins)
如果通過類Class訪問描述器,由__getattribute__()
轉化為:Class.__dict__['attr'].__get__(None, Class)
class Descriptor(object):
def __init__(self):
self.aaaa = 'anonymous'
def __get__(self, instance, owner):
print('instance: %s' % instance)
print('owner: %s' % owner)
print("Invoke __get__: %s" % self.aaaa)
return self.aaaa
def __set__(self, instance, name):
print("invoke __set__: %s" % name)
self.aaaa = name.title()
def __delete__(self, instance):
print("Invoke __delete__: %s" % self.aaaa)
del self.aaaa
class Person(object):
name = Descriptor()
# 通過類Person訪問
print(Person.name)
# instance: None
# owner: <class '__main__.Person'>
# Invoke __get__: anonymous
# anonymous
print(Person.__dict__['name'].__get__(None, Person))
# instance: None
# owner: <class '__main__.Person'>
# Invoke __get__: anonymous
# anonymous
user = Person()
# 通過例項user訪問, `owner`訪問描述器例項的物件。`instance`則是訪問描述器例項的例項
print(user.name)
# instance: <__main__.Person object at 0x7f88c5472dd0>
# owner: <class '__main__.Person'>
# Invoke __get__: anonymous
# anonymous
print(type(user).__dict__['name'].__get__(user, type(user)))
# instance: <__main__.Person object at 0x7f0873fb5d90>
# owner: <class '__main__.Person'>
# Invoke __get__: anonymous
# anonymous
user.name = 'jack'
# invoke __set__: jack
del user.name
# Invoke __delete__: Jack
另外通過super訪問,如SubPerson
是Person
的子類,super(SubPerson, subins).name)
訪問通過subins.__class__.__mro__
查詢到Person
類,然後呼叫:Person.__dict__['name'].__get__(subins, Person)
。
class SubPerson(Person):
pass
subins = SubPerson()
print(subins.__class__.__mro__)
# (<class '__main__.SubPerson'>, <class '__main__.Person'>, <class 'object'>)
# 通過super訪問
print(super(SubPerson, subins).name)
# instance: <__main__.SubPerson object at 0x7f30b1537f28>
# owner: <class '__main__.Person'>
# Invoke __get__: anonymous
# anonymous
print(Person.__dict__['name'].__get__(subins, Person))
# instance: <__main__.SubPerson object at 0x7f30b1537f28>
# owner: <class '__main__.Person'>
# Invoke __get__: anonymous
# anonymous
訪問的優先順序
例項
上面提到例項ins
訪問描述器,實際是由__getattribute__()
訪問: type(ins).__dict__['attr'].__get__(ins, type(ins)
。
具體實現是依據這樣的優先順序是:資料描述器 > 例項屬性 > 非資料描述符 -> __getter__() 方法
如下,我們user.name = 'andy'
我們通過例項對屬性name
賦值,但由於資料描述器優先順序高於例項屬性。賦值操作被資料描器中的__set__
方法截獲,我們在__set__
忽略了重新賦值(當然也可以在其中更新賦值,但實質不是通過例項屬性繫結的方式)。易見例項user
的屬性字典__dict__
還是空的。
class Descriptor(object):
def __init__(self, name):
self.aaaa = name
def __get__(self, instance, owner):
print("Invoke __get__: %s" % self.aaaa)
return self.aaaa
def __set__(self, instance, name):
print("invoke __set__, ignore assignment.")
def __delete__(self, instance):
print("Invoke __delete__: %s" % self.aaaa)
del self.aaaa
class Person(object):
name = Descriptor('jack')
user = Person()
print(user.name)
# Invoke __get__: jack
# jack
print(user.__dict__)
# {}
user.name = 'andy' # 例項屬性賦值
# invoke __set__, ignore assignment.
print(user.name)
# Invoke __get__: jack
# jack
print(user.__dict__)
# {}
再看非資料描述器和例項屬性比較。user.name = 'andy'
成功的把屬性name
繫結到user.__dict__
中。
class Descriptor(object):
def __init__(self, name):
self.aaaa = name
def __get__(self, instance, owner):
print("Invoke __get__: %s" % self.aaaa)
return self.aaaa
class Person(object):
name = Descriptor('jack')
user = Person()
print(user.name)
# Invoke __get__: jack
# jack
print(user.__dict__)
# {}
user.name = 'andy'
print(user.name)
# andy
print(user.__dict__)
# {'name': 'andy'}
類
如果通過類Class訪問描述器,由__getattribute__()
訪問:Class.__dict__['attr'].__get__(None, Class)
。
優先順序是:類屬性 > 描述器。
通過類物件Person.name = 'andy'
更新屬性name
,並沒有進入到描述器的__set__
方法中,而且Person.__dict__
中的屬性name
也由描述器<__main__.Descriptor object at 0x7f1a72df9710>
更新為字串'andy'
。可見類屬性的優先順序高於描述器。
class Descriptor(object):
def __init__(self, name):
self.aaaa = name
def __get__(self, instance, owner):
print("Invoke __get__: %s" % self.aaaa)
return self.aaaa
def __set__(self, instance, name):
print("invoke __set__, ignore assignment.")
def __delete__(self, instance):
print("Invoke __delete__: %s" % self.aaaa)
del self.aaaa
class Person(object):
name = Descriptor('jack')
print(Person.__dict__)
# {'__module__': '__main__', 'name': <__main__.Descriptor object at 0x7f1a72df9710>,
# '__dict__': <attribute '__dict__' of 'Person' objects>, '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Person' objects>}
# Invoke __get__: jack
print(Person.name)
# jack
Person.name = 'andy'
print(Person.__dict__)
# {'__module__': '__main__', 'name': 'andy', '__dict__': <attribute '__dict__' of 'Person' objects>,
# '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Person' objects>}
print(Person.name)
# andy
類屬性 > 資料描述器 > 例項屬性 > 非資料描述符 > __getter__() 方法
如果有__getattribute__
方法,當__getattribute__
出現異常時可能會呼叫__getter__()
。
函式都是非資料描述器
類字典將方法儲存為函式。在類定義中,方法是用 def
或 lambda
這兩個建立函式的常用工具編寫的。方法與常規函式的不同之處僅在於第一個引數是為物件例項保留的。按照 Python 約定,例項引用稱為 self ,但也可以稱為 this 或任何其他變數名稱。
為了支援方法呼叫,函式包含 __get__()
方法用於在訪問屬性時將其繫結成方法。這意味著所有函式都是非資料描述器,當從物件呼叫它們時,它們返回繫結方法。在純 Python 中,它的工作方式如下:
class Function(object):
. . .
def __get__(self, obj, objtype=None):
"Simulate func_descr_get() in Objects/funcobject.c"
if obj is None:
return self
return types.MethodType(self, obj)
描述器的使用
描述器就是屬性訪問的代理,通過描述器來訪問屬性,需要把描述器(例項)作為一個類的屬性(作為例項的屬性沒啥用),通過內部的__get__
,__set__
,__delete__
方法處理對一個屬性的操作。
常規類方法建立描述器
class Descriptor(object):
def __init__(self, name):
self.aaaa = name
def __get__(self, instance, owner):
print("Invoke __get__: %s" % self.aaaa)
return self.aaaa
def __set__(self, instance, name):
print("invoke __set__, ignore assignment.")
def __delete__(self, instance):
print("Invoke __delete__: %s" % self.aaaa)
del self.aaaa
class Person(object):
name = Descriptor('jack')
user = Person()
user.name = 'andy'
# invoke __set__, ignore assignment.
print(user.name)
# Invoke __get__: jack
# jack
del user.name
# Invoke __delete__: jack
使用property類建立描述器
class property(fget=None, fset=None, fdel=None, doc=None)
,fget
是獲取屬性的函式,fset
是設定屬性的函式,fdel
是刪除屬性的函式,doc
是這個屬性的文件字串。
class C:
def __init__(self):
self._x = None
def getx(self):
print('invoke getx')
return self._x
def setx(self, value):
print('invoke setx')
self._x = value
def delx(self):
print('invoke delx')
del self._x
x = property(getx, setx, delx, "I'm the 'x' property.")
ins = C()
ins.x = 'property'
# invoke setx
print(ins.x)
# invoke getx
# property
print(C.x.__doc__)
# I'm the 'x' property.
del ins.x
# invoke delx
使用 @property 裝飾器建立描述器
這種使用很廣泛,在python原始碼中經常遇見。
class C:
def __init__(self):
self._x = None
@property
def x(self):
"""I'm the 'x' property."""
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
ins = C()
ins.x = 'property'
print(ins.x)
# property
print(C.x.__doc__)
# I'm the 'x' property.
del ins.x
Property純Python的等價實現
class Property(object):
"Emulate PyProperty_Type() in Objects/descrobject.c"
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
self.__doc__ = doc
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError, "unreadable attribute"
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError, "can't set attribute"
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError, "can't delete attribute"
self.fdel(obj)
def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)
def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)
實現StaticMethod
非資料描述器 StaticMethod 的 Python版本:
class StaticMethod(object):
"Emulate PyStaticMethod_Type() in Objects/funcobject.c"
def __init__(self, f):
self.f = f
def __get__(self, obj, objtype=None):
return self.f
class E(object):
@staticmethod
def f(x):
print(x)
# f = staticmethod(f)
E.f(3)
# 3
E().f(3)
# 3
實現ClassMethod
非資料描述器 ClassMethod 的 Python版本:
class ClassMethod(object):
"Emulate PyClassMethod_Type() in Objects/funcobject.c"
def __init__(self, f):
self.f = f
def __get__(self, obj, klass=None):
if klass is None:
klass = type(obj)
def newfunc(*args):
return self.f(klass, *args)
return newfunc
class E(object):
def f(klass, x):
return klass.__name__, x
f = classmethod(f)
print(E.f(3))
# ('E', 3)
print(E().f(3))
# ('E', 3)
print(vars(E))
# {'__module__': '__main__', 'f': <classmethod object at 0x028DAAF0>,
# '__dict__': <attribute '__dict__' of 'E' objects>, '__weakref__':
# <attribute '__weakref__' of 'E' objects>, '__doc__': None}
print(vars(E()))
# {}
參考連結
毫無理想而又優柔寡斷是一種可悲的心理。 -----培根
相關文章
- python裝飾器介紹Python
- Python 描述符簡介Python
- NEO Python編譯器介紹Python編譯
- javascript屬性描述符詳細介紹JavaScript
- CGroup 介紹、應用例項及原理描述
- python中的裝飾器介紹Python
- Python 黑魔法 --- 描述器(descriptor)Python
- Python前景介紹Python
- Python Twisted 介紹Python
- Python字典介紹Python
- Python模組介紹Python
- Python Virtualenv 介紹Python
- Python 框架介紹Python框架
- 簡單介紹python迭代器和生成器Python
- python描述器的訪問順序Python
- Python簡單介紹Python
- python類的介紹Python
- python BeautifulSoup用法介紹Python
- Python BaseHTTPServer 介紹PythonHTTPServer
- python裝飾器和描述器的使用總結Python
- 『python入門:』 python的介紹Python
- DNS伺服器介紹DNS伺服器
- 伺服器的介紹伺服器
- MySQL觸發器介紹MySql觸發器
- 瀏覽器核心介紹瀏覽器
- python字典詳細介紹Python
- python等待方式的介紹Python
- Python JWT 介紹和使用PythonJWT
- 介紹Python的 迴圈Python
- Python 物件導向介紹Python物件
- 學習python前言介紹Python
- 【Python】supervisor 工具介紹Python
- Python 位元組碼介紹Python
- Python 序列通用操作介紹Python
- Python直譯器種類以及特點?詳細介紹!Python
- Python多版本管理器-pyenv 介紹及部署記錄Python
- python 中的迭代器和生成器簡單介紹Python
- 【Python】python內建函式介紹Python函式