ホームページ >バックエンド開発 >Python チュートリアル >Python 記述子の入門
長い間 Flask のコードについて書いていなかったのですが、思い出すと本当に恥ずかしいです。それでも今回は書きません。受け入れられない場合は、私に叩きに来てください。私はとても雌犬です、できれば噛んでください)
今回はPythonでとても重要なDescriptorについて書きます
記述子の最初の紹介
古いルール、話は安い、まずコードを見てみましょう
この世代では誰もがプロパティについてよく知っているはずですが、プロパティの実装メカニズムをご存知ですか?何が不明ですか? Pythonを勉強してみてはいかがでしょうか? 。 。冗談はさておき、次のコードを見てみましょう
複雑だと思いませんか? 順を追って見ていきましょう。しかし、ここで最初に結論を示します。記述子は特別な種類のオブジェクトであり、__get__、__set__、__delete__ という 3 つの特別なメソッドを実装します。
ディスクリプタの詳しい説明
物件について話す
上記では、プロパティの実装コードを示しました。次に、これについて詳しく説明します
まず、デコレータについて知らない場合は、この記事を読んでください。つまり、コードを正式に実行する前に、インタープリタがコードをスキャンして、関連するコードをチェックします。クラスデコレータについても同様です。上記では、このコード
はそのようなプロセス、つまり 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 全体で使用されます」つまり、最初に記述子があり、次に空があり、次に空気が来ます。新しいスタイルのクラスでは、属性、メソッド呼び出し、静的メソッド、クラス メソッドなどはすべて、記述子の特定の使用法に基づいています。
OK、なぜ記述子がそれほど重要なのかと疑問に思うかもしれません。心配しないで、続けて見てみましょう
記述子を使用する
まず、次のコードを見てください
classA(object):#注意: Python 3.x バージョンでは、Python 2.X (x>2) の場合、新しいクラスを使用するためにオブジェクト クラスからの継承を明示的に指定する必要はありません。バージョン、必須です
このようなステートメント a.a() があることに誰もが気づいています。さて、このメソッドを呼び出すと何が起こるか考えてください。
いいですか?もう分かりましたか?いいえ?はい、続けましょう
まず、属性を呼び出すとき、それがメンバーであってもメソッドであっても、呼び出そうとする属性が実装されている場合、__getattribute__() メソッドで属性 __getattribute__() を呼び出すメソッドがトリガーされます。記述子プロトコルの場合、type(a).__dict__['a'].__get__(b,type(b)) のような呼び出し処理が生成されます。さて、ここで別の結論が得られます: 「そのような呼び出しプロセスには、そのような優先順位があります。呼び出しようとしている属性がデータ記述子の場合、この属性がインスタンスの __dict__ に存在するかどうかに関係なく、辞書では、記述子の __get__ メソッドが最初に呼び出されます。呼び出そうとしている属性がデータ記述子ではない場合、インスタンス内の __dict__ 内の既存の属性が優先されます。対応する原則は次のとおりです。次に、クラスと親クラスの __dict__ に含まれる属性を検索します。属性が存在しない場合は、__get__ メソッドが呼び出されます。少し抽象的でわかりにくいですか?大丈夫、すぐに取り掛かりますが、ここではまずデータ記述子と非データ記述子について説明し、それから例を見てみる必要があります。データ記述子と非データ記述子とは何ですか?実際、記述子に __get__ プロトコルと __set__ プロトコルの両方を実装する記述子は、__get__ プロトコルのみを実装する場合はデータ記述子ではありません。さて、例を見てみましょう:
好的,让我们仔细来看看这段代码,首先类描述符 @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
如上面的例子所描述的一样,我们可以判断所传入的值是否有效等等。