Python黒魔術Descriptor記述子の実例解析

16743 ワード

Pythonでは、1つの属性にアクセスする優先順位は以下の順序である。
1:クラスの属性
2:データ記述子
3:インスタンスのプロパティ
4:非データ記述子
5:ウウgetattr_()方法  この方法の完全な定義は以下の通りである。

def __getattr(self,attr) :#attr self       
 pass; 
まず、データディスクリプタとは何かを説明します。
データ・ディスクリプタとは、__uを実現したということです。get_,_set_,_del_方法のクラス属性(Pythonではすべて対象ですので、すべての属性を対象としてもかまいません。)
PS:個人的にはここが一番いいと思います。データ記述子を定義しました。get_,_set_,_del_三つの方法のインターフェース。
同前get_,_set_,_del_
この3つの方法を説明します。
 __ゲットするの標準定義は_u_u u uですget_.self、obj、type=None)は、JavaBeanのgetに非常に近いです。
最初の関数はその例を呼び出すもので、objは属性の所在にアクセスする方法を指し、最後のtypeはオプションのパラメータであり、通常はNoneである(これはさらなる研究が必要である)。
例えば、クラスXとインスタンスxを指定して、X.fooを呼び出して、呼び出しと等価です。

type(x).__dict__['foo'].__get__(x,type(x)) 
X.fooを呼び出します。呼び出しと等価です。

type(x).__dict__['foo'].__get__(None,type(x)) 
 
二番目の関数set_の標準定義は_u_u u uですset_self、obj、val)は、JavaBeanのset方法に非常に近いです。最後のパラメータは与えられる値です。
三つ目の関数del_の標準定義は_u_u u uですdel_(u)self,obj,それはJavaのObjectのFinailize()の方法に非常に近いです。Pythonはこのごみの対象を回収する時に呼び出されたコンストラクションを指します。ただし、この関数は永遠に異常を投げません。このオブジェクトはすでに参照されていませんので、例外を投げても意味がありません。
 
優先度
次に、これらの優先度を比較します。
まずクラスの属性を見ます。

class A(object): 
 foo=1.3; 
  
print str(A.__dict__); 

出力: 

{'__dict__': <attribute '__dict__' of 'A' objects>, '__module__': '__main__', 

'foo': 1.3, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None} 

上の図からfoo属性はクラスの_u u u u u u u udict_.属性はここでA.fooを使って直接見つけられます。ここではまずデータディスクリプタを越えて、実例のプロパティを直接見に行きます。

class A(object): 
 foo=1.3; 
 
a=A(); 
print a.foo; 
a.foo=15; 
print a.foo; 
ここでa.fooが先に1.3を出力してから15を出力すると、クラス属性の優先度がインスタンス属性の優先度より高いということではないですか?道理ではa.fooは変わらないはずです。ここでは単なる仮象にすぎません。本当の理由はここでa.fooという参照対象を、任意のデータタイプを指すことができるポインタとして捉えて、15のintオブジェクトを指しています。
信じません。続けて見てもいいです。

class A(object): 
 foo=1.3; 
 
a=A(); 
print a.foo; 
a.foo=15; 
print a.foo; 
del a.foo; 
print a.foo; 
 今回は1.3を出力して、15後に最後にまた1.3を出力しました。なぜならばa.fooが最後に優先順位順に直接クラスの属性A.fooを見つけたからです。
説明器とオブジェクトの属性
OOPの理論では、クラスのメンバー変数は属性と方法を含む。では、Pythonの属性は何ですか?上のPythonSite類を修正すると次のようになります。

class PythonSite(object):

 webframework = WebFramework()

 version = 0.01

 def __init__(self, site):
 self.site = site

ここでは、クラス属性のversionと、実例的な属性siteが追加されます。クラスとインスタンスオブジェクトの属性をそれぞれ確認します。

In [1]: pysite = PythonSite('ghost')

In [2]: vars(PythonSite).items()
Out[2]:
[('__module__', '__main__'),
 ('version', 0.01),
 ('__dict__', <attribute '__dict__' of 'PythonSite' objects>),
 ('webframework', <__main__.WebFramework at 0x10d55be90>),
 ('__weakref__', <attribute '__weakref__' of 'PythonSite' objects>),
 ('__doc__', None),
 ('__init__', <function __main__.__init__>)]

In [3]: vars(pysite)
Out[3]: {'site': 'ghost'}
In [4]: PythonSite.__dict__
Out[4]:
<dictproxy {'__dict__': <attribute '__dict__' of 'PythonSite' objects>,
 '__doc__': None,
 '__init__': <function __main__.__init__>,
 '__module__': '__main__',
 '__weakref__': <attribute '__weakref__' of 'PythonSite' objects>,
 'version': 0.01,
 'webframework': <__main__.WebFramework at 0x10d55be90>}>

vars方法は対象の属性を調べるために用いられ、対象の__dict_.内容です。上記の表示結果から、クラスPythonSiteとインスタンスpysiteの属性の違いは、前者がwebframe ebook、versionの2つの属性と、_u u uinit_方法は後者のsite属性のみである。
クラスとインスタンスの属性
クラスの属性は、オブジェクトとクラスのアクセスを使用して、複数のインスタンスオブジェクトがクラス変数を共有します。しかし、クラスだけが修正できます。

In [6]: pysite1 = PythonSite('ghost')

In [7]: pysite2 = PythonSite('admin')

In [8]: PythonSite.version
Out[8]: 0.01

In [9]: pysite1.version
Out[9]: 0.01

In [10]: pysite2.version
Out[10]: 0.01

In [11]: pysite1.version is pysite2.version
Out[11]: True

In [12]: pysite1.version = 'pysite1'

In [13]: vars(pysite1)
Out[13]: {'site': 'ghost', 'version': 'pysite1'}

In [14]: vars(pysite2)
Out[14]: {'site': 'admin'}

In [15]: PythonSite.version = 0.02

In [16]: pysite1.version
Out[16]: 'pysite1'

In [17]: pysite2.version
Out[17]: 0.02

上のコードが示すように、両インスタンスオブジェクトはいずれもversionクラスの属性にアクセスでき、同じクラスの属性です。pysite 1がversionを修正した時、実際には自分にversion属性を追加しました。クラスの属性は変更されていません。PythonSiteがversion属性を変更した場合、pysite 2のこの属性も対応して変更されます。
属性アクセスの原理と説明器
属性アクセスの結果が分かりました。この結果はすべてPythonの記述器に基づいて達成された。通常、クラスまたはインスタンスは、オペレータによって属性にアクセスする。例えば、pysite 1.siteとpysite 1.versionのアクセスです。まず対象の__uを訪問しますdict_,再訪類(または父類、元類を除く)の___dict_。最後にこれがdict_.のオブジェクトが記述器である場合は、説明器の__u u uゲットする方法。

In [21]: pysite1.site
Out[21]: 'ghost'

In [22]: pysite1.__dict__['site']
Out[22]: 'ghost'

In [23]: pysite2.version
Out[23]: 0.02

In [24]: pysite2.__dict__['version']
---------------------------------------------------------------------------
KeyError     Traceback (most recent call last)
<ipython-input-24-73ef6aeba259> in <module>()
----> 1 pysite2.__dict__['version']

KeyError: 'version'

In [25]: type(pysite2).__dict__['version']
Out[25]: 0.02

In [32]: type(pysite1).__dict__['webframework']
Out[32]: <__main__.WebFramework at 0x103426e90>

In [38]: type(pysite1).__dict__['webframework'].__get__(None, PythonSite)
Out[38]: 'Flask'

例の方法、クラスの方法、静的な方法と説明器
説明器を呼び出すと、実際にobject.u_getattribute()これは、その器を呼び出して説明するのが対象かクラスかによって異なり、対象のobj.xであれば、type(obj).u(uu u)を呼び出します。dict_[]x._u.get_.obj,type(obj)。クラスであれば、クラス.xであれば、タイプタイプ(クラス)を呼びます。dict_[]x._u.get_.None、type。
このように言うとやはり抽象的です。Pythonの方法、静的方法、および種類の方法を分析します。PythonSiteを再構築してください。

class PythonSite(object):
 webframework = WebFramework()

 version = 0.01

 def __init__(self, site):
 self.site = site

 def get_site(self):
 return self.site

 @classmethod
 def get_version(cls):
 return cls.version

 @staticmethod
 def find_version():
 return PythonSite.version

クラスメソッド、@classimethod装飾器
まず種類の方法を見て、種類の方法は@classmethod装飾器を使って定義します。この装飾器を通過する方法は説明器である。クラスおよびインスタンスは、クラスの方法を呼び出すことができます。

In [1]: ps = PythonSite('ghost')

In [2]: ps.get_version
Out[2]: <bound method type.get_version of <class '__main__.PythonSite'>>

In [3]: ps.get_version()
Out[3]: 0.01

In [4]: PythonSite.get_version
Out[4]: <bound method type.get_version of <class '__main__.PythonSite'>>

In [5]: PythonSite.get_version()
Out[5]: 0.01

ゲットするversionはbound方法です。次にps.get_を見ます。versionという呼び方は、まずそれ・の_u u u u u u u udict_.get_がありますかversionという属性がない場合は、そのクラスを検索します。

In [6]: vars(ps)
Out[6]: {'site': 'ghost'}

In [7]: type(ps).__dict__['get_version']
Out[7]: <classmethod at 0x108952e18>

In [8]: type(ps).__dict__['get_version'].__get__(ps, type(ps))
Out[8]: <bound method type.get_version of <class '__main__.PythonSite'>>

In [9]: type(ps).__dict__['get_version'].__get__(ps, type(ps)) == ps.get_version
Out[9]: True

そしてvars(ps)の中、_udict_.ゲットしたわけではないversionという属性は、説明器プロトコルに従い、type(ps).__dict_[]ゲットするversion's記述器の_u uゲットする方法は、psが実例なので、object.u_getattribute()こう呼びますget_.obj,type(obj)。
クラス方法の呼び出しを確認します。

In [10]: PythonSite.__dict__['get_version']
Out[10]: <classmethod at 0x108952e18>

In [11]: PythonSite.__dict__['get_version'].__get__(None, PythonSite)
Out[11]: <bound method type.get_version of <class '__main__.PythonSite'>>

In [12]: PythonSite.__dict__['get_version'].__get__(None, PythonSite) == PythonSite.get_version
Out[12]: True

今回はget_を呼び出しますので。versionはインスタンスオブジェクトではなくクラスオブジェクトですので、object.ugetattribute()こう呼びますget_.ノン・クラス
静的方法、@staticmethod
インスタンスおよびクラスは、静的方法を呼び出すこともできる。

In [13]: ps.find_version
Out[13]: <function __main__.find_version>

In [14]: ps.find_version()
Out[14]: 0.01

In [15]: vars(ps)
Out[15]: {'site': 'ghost'}

In [16]: type(ps).__dict__['find_version']
Out[16]: <staticmethod at 0x108952d70>

In [17]: type(ps).__dict__['find_version'].__get__(ps, type(ps))
Out[17]: <function __main__.find_version>

In [18]: type(ps).__dict__['find_version'].__get__(ps, type(ps)) == ps.find_version
Out[18]: True

In [19]: PythonSite.find_version()
Out[19]: 0.01

In [20]: PythonSite.find_version
Out[20]: <function __main__.find_version>

In [21]: type(ps).__dict__['find_version'].__get__(None, type(ps))
Out[21]: <function __main__.find_version>

In [22]: type(ps).__dict__['find_version'].__get__(None, type(ps)) == PythonSite.find_version
Out[22]: True

クラスの方法とはあまり違いません。クラスの方法の内部ではクラスの引用があります。静的なアクセスはありません。静的な方法でクラスの変数を使いたいなら、クラス名を無理に符号化するしかありません。
実例的な方法
実例的な方法は最も複雑で、具体的な例に属しています。クラスコールを使用する場合、unbound方法になります。

In [2]: ps.get_site
Out[2]: <bound method PythonSite.get_site of <__main__.PythonSite object at 0x1054ae2d0>>

In [3]: ps.get_site()
Out[3]: 'ghost'

In [4]: type(ps).__dict__['get_site']
Out[4]: <function __main__.get_site>

In [5]: type(ps).__dict__['get_site'].__get__(ps, type(ps))
Out[5]: <bound method PythonSite.get_site of <__main__.PythonSite object at 0x1054ae2d0>>

In [6]: type(ps).__dict__['get_site'].__get__(ps, type(ps)) == ps.get_site
Out[6]: True

すべての動作が正常で、例示的な方法もクラスの属性ですが、クラスの場合、説明器はそれをunbound方法に変えました。

In [7]: PythonSite.get_site
Out[7]: <unbound method PythonSite.get_site>

In [8]: PythonSite.get_site()
---------------------------------------------------------------------------
TypeError     Traceback (most recent call last)
<ipython-input-8-99c7d7607137> in <module>()
----> 1 PythonSite.get_site()

TypeError: unbound method get_site() must be called with PythonSite instance as first argument (got nothing instead)

In [9]: PythonSite.get_site(ps)
Out[9]: 'ghost'

In [10]: PythonSite.__dict__['get_site']
Out[10]: <function __main__.get_site>

In [11]: PythonSite.__dict__['get_site'].__get__(None, PythonSite)
Out[11]: <unbound method PythonSite.get_site>

In [12]: PythonSite.__dict__['get_site'].__get__(None, PythonSite) == PythonSite.get_site
Out[12]: True

In [14]: PythonSite.__dict__['get_site'].__get__(ps, PythonSite)
Out[14]: <bound method PythonSite.get_site of <__main__.PythonSite object at 0x1054ae2d0>>

In [15]: PythonSite.__dict__['get_site'].__get__(ps, PythonSite)()
Out[15]: 'ghost'

したがって、クラスは、説明器が手動で1つのクラスのインスタンスをバインディングしない限り、例示的な方法を直接起動することができない。クラスのオブジェクトを使って説明器を呼び出す時、__u uゲットするの最初のパラメータはNoneです。成功的に起動するには、このパラメータをインスタンスpsに置き換える必要があります。このプロセスは方法に対するboundプロセスです。
実例 
前の定義に従って一つが実現されました。get_,_set_,_del_のクラスをまとめてデータディスクリプタと呼びます。次の簡単な例を見ましょう。

class simpleDescriptor(object): 
 def __get__(self,obj,type=None) : 
 pass; 
 def __set__(self,obj,val): 
 pass; 
 def __del__(self,obj): 
 pass 
 
class A(object): 
 foo=simpleDescriptor(); 
print str(A.__dict__); 
print A.foo; 
a=A(); 
print a.foo; 
a.foo=13; 
print a.foo; 
 
ここでget、set、delメソッドの内容は全部略しました。簡単ですが、データ記述子としてもいいです。その出力を見てみましょう。

{'__dict__': <attribute '__dict__' of 'A' objects>, '__module__': '__main__', 
'foo': <__main__.simpleDescriptor object at 0x00C46930>, 
'__weakref__': <attribute '__weakref__' of 'A' objects>, 
'__doc__': None} 
None 
None 
None 
 
上の図から分かるように、私達はa.fooに対して価値を賦課しましたが、まだNoneです。原因は_u u uにあります。ゲットする方法は何も戻りません。
データ記述子の理解を深めるために、簡単に改造します。

class simpleDescriptor(object): 
 def __init__(self): 
 self.result=None; 
 def __get__(self,obj,type=None) : 
 return self.result-10; 
 def __set__(self,obj,val): 
 self.result=val+3; 
 print self.result; 
 def __del__(self,obj): 
 pass 
 
class A(object): 
 foo=simpleDescriptor(); 
a=A(); 
a.foo=13; 
print a.foo; 
印刷の出力結果は以下の通りです。

16
 6
最初の16は私達がa.fooに対して値を賦課する時、人為的なのは13をプラスして3の後でfooの値として、第2の6は私達がa.fooの前人に帰っているので、それを10マイナスしました。
したがって、従来のPython類はget、set方法を定義する際に、特別な需要がない場合、直接に対応する属性に値を付けたり、直接に属性値を返したりすると推測できます。自分でクラスを定義して、そしてobject類を継承するなら、これらの方法は定義しなくてもいいです。
次の例の属性と非データ記述子を紹介します。

class B(object): 
 foo=1.3; 
b=B(); 
print b.__dict__ 
#print b.bar; 
b.bar=13; 
print b.__dict__ 
print b.bar; 
出力結果は:

{}
{'bar': 13}
13
 
ここは実例であることが分かります。dict_.でbar属性を見つけましたので、今回は13を取得できます。
では、データディスクリプタとは何ですか?簡単に言えば、get、set、delの3つの方法が実現されていないすべての種類です。
関数の説明を任意に見ましょう。

def hello(): 
 pass 
 
print dir(hello) 
 
出力:  

['__call__', '__class__', '__delattr__', '__dict__', 
'__doc__', 
'__get__', 
'__getattribute__', 
'__hash__', '__init__', '__module__', '__name__', 
 '__new__', '__reduce__', 
'__reduce_ex__', '__repr__', 
 '__setattr__', '__str__', 'func_closure', 
'func_code', 
'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name'] 
 
上からすべての関数にはget方法がありますが、setとdel方法がないので、クラスメンバー関数はすべてデータ記述子ではありません。
簡単な例を見てください。

class simpleDescriptor(object): 
 def __get__(self,obj,type=None) : 
 return 'get',self,obj,type; 
class D(object): 
 foo=simpleDescriptor(); 
d=D(); 
print d.foo; 
d.foo=15; 
print d.foo;
出力:

('get', <__main__.simpleDescriptor object at 0x00C46870>, 

<__main__.D object at 0x00C46890>, <class '__main__.D'>) 
15 

 
実例的な属性が非データ・ディスクリプタを覆っていることがわかる。
最後に見てくださいゲタトリド方法。その標準的な定義は「__u」です。getattr_(u)self,atr)で、その中のatrは属性名です。
簡単な例を見てみましょう。

class D(object): 
 def __getattr__(self,attr): 
 return attr; 
 #return self.attr; 
  
d=D(); 
print d.foo,type(d.foo); 
d.foo=15; 
print d.foo; 
 出力:

 foo <type 'str'>
 15
 見ることができます。Pythonは本当に方法が見つけられない時に助けを求めます。getattr方法。
 ここに注意して無意識の再帰を避け、少し変えてください。

class D(object): 
 def __getattr__(self,attr): 
 #return attr; 
 return self.attr; 
  
d=D(); 
print d.foo,type(d.foo); 
d.foo=15; 
print d.foo; 
 
今回はスタックオーバーフローの異常を直接投げます。

RuntimeError: maximum recursion depth exceeded