Python 記述子の入門

黄舟
リリース: 2016-12-15 09:13:57
オリジナル
1106 人が閲覧しました

長い間 Flask のコードについて書いていなかったのですが、思い出すと本当に恥ずかしいです。それでも今回は書きません。受け入れられない場合は、私に叩きに来てください。あなたはとてもビッチです、できるなら噛んでください)

今回はそれをやります Python で非常に重要なこと、つまり Descriptor について書きましょう

最初のディスクリプタの紹介

古いルール、話は安い、見せてください。まずはコードを見てみましょう

classPerson(object):
""""""
  
#----------------------------------------------------------------------
def__init__(self, first_name, last_name):
"""Constructor"""
 self.first_name = first_name
 self.last_name = last_name
  
#----------------------------------------------------------------------
 @property
deffull_name(self):
"""
 Return the full name
 """
return"%s %s"% (self.first_name, self.last_name)
  
if__name__=="__main__":
 person = Person("Mike","Driscoll")
 print(person.full_name)
# 'Mike Driscoll'
 print(person.first_name)
# 'Mike'
ログイン後にコピー


プロパティについて知らない人はいないでしょう。しかし、プロパティの実装メカニズムについてはご存知ですか? Pythonを勉強してみませんか? ああ。 。 。冗談ですが、次のコードを見てみましょう

classProperty(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
ifdocisNoneandfgetisnotNone:
 doc = fget.__doc__
 self.__doc__ = doc
  
def__get__(self, obj, objtype=None):
ifobjisNone:
returnself
ifself.fgetisNone:
raiseAttributeError("unreadable attribute")
returnself.fget(obj)
  
def__set__(self, obj, value):
ifself.fsetisNone:
raiseAttributeError("can't set attribute")
 self.fset(obj, value)
  
def__delete__(self, obj):
ifself.fdelisNone:
raiseAttributeError("can't delete attribute")
 self.fdel(obj)
  
defgetter(self, fget):
returntype(self)(fget, self.fset, self.fdel, self.__doc__)
  
defsetter(self, fset):
returntype(self)(self.fget, fset, self.fdel, self.__doc__)
  
defdeleter(self, fdel):
returntype(self)(self.fget, self.fset, fdel, self.__doc__)
ログイン後にコピー

複雑そうに見えますか? 大丈夫、ステップバイステップで見てみましょう。しかし、ここで最初に結論を示します。記述子は、__get__ を実装する特別な種類のオブジェクトです。 __set__、__delete__ これら 3 つの特別なメソッド。

ディスクリプタの詳しい説明

Propertyについて話しましょう

上記ではPropertyの実装コードを紹介しましたが、ここからは詳しく説明していきます

classPerson(object):
""""""
  
#----------------------------------------------------------------------
def__init__(self, first_name, last_name):
"""Constructor"""
 self.first_name = first_name
 self.last_name = last_name
  
#----------------------------------------------------------------------
 @Property
deffull_name(self):
"""
 Return the full name
 """
return"%s %s"% (self.first_name, self.last_name)
  
if__name__=="__main__":
 person = Person("Mike","Driscoll")
 print(person.full_name)
# 'Mike Driscoll'
 print(person.first_name)
# 'Mike'
ログイン後にコピー

まず、デコレータについて知らない方のためにつまり、コードを正式に実行する前に、インタプリタがコードをスキャンし、デコレータに関係する部分を置き換えます。クラスデコレータについても同様です。上記では、このコード

@Property
deffull_name(self): 
""" 
 Return the full name 
 """
return"%s %s"% (self.first_name, self.last_name)
ログイン後にコピー

がそのようなプロセス、つまり full_name=Property(full_name) をトリガーします。次に、オブジェクトをインスタンス化した後、後で呼び出します person.full_name このようなプロセスは、実際には person.full_name.__get__(person) と同等であり、__get__() をトリガーします。 メソッド内に記述された return self.fget(obj) は、最初に記述した def full_name の実行コードです。

この時点で、同志はgetter()、setter()、deleter()について考えることができます 具体的な動作メカニズム =. =まだ質問がある場合は、コメントでお気軽にご相談ください。

記述子について

前に説明した定義を覚えていますか: 記述子は、 __get__ 、 __set__ 、 __delete__ を実装する特別な種類のオブジェクトです。 この3つの特別な方法です。そして、Python の公式ドキュメントの説明には、記述子の重要性を反映するために、次の段落があります。「記述子の背後にあるメカニズムです。 プロパティ、メソッド、静的メソッド、クラスメソッド、および super() が使用されます。 Python 自体全体で、 で導入された新しいスタイル クラスを実装します。 バージョン 2.2 」 つまり、最初に記述子があり、次に空があり、毎秒空気が存在します。 。新しいスタイルのクラスでは、属性、メソッド呼び出し、静的メソッド、クラス メソッドなどはすべて、記述子の特定の使用法に基づいています。

OK、なぜ記述子がそれほど重要なのかと疑問に思うかもしれません。心配しないで、引き続き

記述子の使用

を見てみましょう

まず、次のコード部分を見てください


classA(object): #注意: Python 3.xバージョンでは、明示的に指定する必要はありません。新しいクラスの使用からのオブジェクトは、クラスを継承します。 Python 2 のバージョンでは何が起こったのでしょうか?

いいですか?もう分かりましたか?いいえ?さて、続けましょう

まず第一に、属性を呼び出すとき、それがメンバーであってもメソッドであっても、そのようなメソッドをトリガーして属性 __getattribute__() を呼び出します。 __getattribute__() メソッドでは、呼び出そうとする属性が記述子プロトコルを実装している場合、そのような呼び出しプロセスが発生します。 type(a).__dict__['a'].__get__(b,type(b)) 。さて、ここで別の結論が得られます。「このような呼び出しプロセスでは、呼び出そうとしている属性がデータ記述子の場合、そのような優先順位が存在します。」 この属性がインスタンスの __dict__ ディクショナリに存在するかどうかに関係なく、呼び出そうとしている属性がデータでない場合は、記述子の __get__ メソッドが最初に呼び出されます。 記述子の場合、インスタンス内の __dict__ 内の既存の属性を優先して呼び出します。それらの属性が存在しない場合は、対応する原則に従ってクラスと親クラス内の __dict__ を検索します。 に含まれる属性の場合、属性が存在する場合は __get__ メソッドが呼び出され、属性が存在しない場合は __getattr__() が呼び出されます。 少し抽象的でわかりにくいですか? 大丈夫、すぐに説明しますが、ここではまずデータ記述子と非データ記述子について説明する必要があります。 、別の例を見てみましょう。データ記述子と非データ記述子とは何ですか?実際、__get__ も記述子に実装されています。 __set__ プロトコルの記述子は、__get__ プロトコルのみが実装されている場合、データ記述子ではありません。 。さて、例を見てみましょう:

defa(self):
pass
if__name__=="__main__":
 a=A()
 a.a()
ログイン後にコピー

好的,让我们仔细来看看这段代码,首先类描述符 @lazyproperty 的替换过程,前面已经说了,我们不在重复。接着,在我们第一次调用 c.area 的时候,我们首先查询实例 c 的 __dict__ 中是否存在着 area 描述符,然后发现在 c 中既不存在描述符,也不存在这样一个属性,接着我们向上查询 Circle 中的 __dict__ ,然后查找到名为 area 的属性,同时这是一个 non data descriptors ,由于我们的实例字典内并不存在 area 属性,那么我们便调用类字典中的 area 的 __get__ 方法,并在 __get__ 方法中通过调用 setattr 方法为实例字典注册属性 area 。紧接着,我们在后续调用 c.area 的时候,我们能在实例字典中找到 area 属性的存在,且类字典中的 area 是一个 non data descriptors ,于是我们不会触发代码里所实现的 __get__ 方法,而是直接从实例的字典中直接获取属性值。

描述符的使用

描述符的使用面很广,不过其主要的目的在于让我们的调用过程变得可控。因此我们在一些需要对我们调用过程实行精细控制的时候,使用描述符,比如我们之前提到的这个例子

classlazyproperty:
def__init__(self, func):
 self.func = func
  
def__get__(self, instance, owner):
ifinstanceisNone:
returnself
else:
 value = self.func(instance)
 setattr(instance, self.func.__name__, value)
returnvalue
  
def__set__(self, instance, value=0):
pass
  
  
importmath
  
  
classCircle:
def__init__(self, radius):
 self.radius = radius
pass
  
 @lazyproperty
defarea(self, value=0):
 print("Com")
ifvalue ==0andself.radius ==0:
raiseTypeError("Something went wring")
  
returnmath.pi * value *2ifvalue !=0elsemath.pi * self.radius *2
  
deftest(self):
pass
ログイン後にコピー

利用描述符的特性实现懒加载,再比如,我们可以控制属性赋值的值

classProperty(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
ifdocisNoneandfgetisnotNone:
 doc = fget.__doc__
 self.__doc__ = doc
  
def__get__(self, obj, objtype=None):
ifobjisNone:
returnself
ifself.fgetisNone:
raiseAttributeError("unreadable attribute")
returnself.fget(obj)
  
def__set__(self, obj, value=None):
ifvalueisNone:
raiseTypeError("You can`t to set value as None")
ifself.fsetisNone:
raiseAttributeError("can't set attribute")
 self.fset(obj, value)
  
def__delete__(self, obj):
ifself.fdelisNone:
raiseAttributeError("can't delete attribute")
 self.fdel(obj)
  
defgetter(self, fget):
returntype(self)(fget, self.fset, self.fdel, self.__doc__)
  
defsetter(self, fset):
returntype(self)(self.fget, fset, self.fdel, self.__doc__)
  
defdeleter(self, fdel):
returntype(self)(self.fget, self.fset, fdel, self.__doc__)
  
classtest():
def__init__(self, value):
 self.value = value
  
 @Property
defValue(self):
returnself.value
  
 @Value.setter
deftest(self, x):
 self.value = x
ログイン後にコピー

   

如上面的例子所描述的一样,我们可以判断所传入的值是否有效等等。

以上就是Python 描述符(Descriptor)入门,更多相关文章请关注PHP中文网(www.php.cn)!


関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート