pythonプログラミングデータ記述器

16514 ワード

最初の実際の接触記述子は、クエリー時に特定の条件を加えるために、私たちのプロジェクトで自分のMangerマネージャを実現する必要があるので、Django ORMのソースコードを見に行くと、データ記述子のドアが開きます.
入門記述子については、まずいくつかの前提知識を知る必要があります.
  • ディスクリプタとは?
  • データ記述器とは?
  • 非データ記述器とは?
  • 属性が呼び出された場合の属性アクセスの順序は?

  •  
    01.ディスクリプタとは
    pythonの公式の解釈は、一般的に、1つの記述器は「バインド動作」を含むオブジェクトであり、その属性へのアクセスは記述器プロトコルで定義された方法で上書きされる.これらの方法は、__get__()__set__()__delete__()があります.オブジェクトにこれらのメソッドのいずれかが定義されている場合、このオブジェクトは記述器と呼ばれます.
    通俗的に言えば、ディスクリプタは実は1つのクラスで、このクラスは少なくとも実現しなければならない__get__()__set__()__delete__() 。
    class Descriptor:
        def __init__(self, default):
            self.value = default
            self.attr_dict = dict()
    
        def __get__(self, instance, owner):
            #    instance  Digit    , owner  Digit
            return self.attr_dict[instance]
    
        def __set__(self, instance, value):
            self.attr_dict[instance] = value
    
    
    class Digit:
        num = Descriptor(0)
    
        def __init__(self, num):
            self.num = num

    上図コードでDescriptorクラスが実現した__get__(),__set__()および__delete__() , 。 
    02.データディスクリプタとは
    データ記述器:実現した限り__get__(),__set__()および__delete__() , 。 
    03.非データ記述器とは
    非データ記述器:実現しただけ__get__(),__set__()および__delete__() , 。 
    04、属性が呼び出された場合の属性アクセスの順序
      ① __getattribute__()、無条件呼び出し
    ②データ記述子:①により呼び出しがトリガーされる(この__getattribute_()メソッドが人為的に再ロードされている場合は、呼び出しができないように転勤する場合がある)
    ③インスタンスオブジェクトの辞書(記述子オブジェクトと同名の場合は上書きされますよ)
    ④類の辞書
    ⑤非データ記述子
    ⑥親の辞書
      ⑦ __getattr__()メソッド
     
    この順序から分かるように、記述器がデータ記述器である場合、記述器の属性名とインスタンスの属性が重複している場合、上記のコードのDigitクラス属性numとインスタンス属性numが重複している場合、Digitクラスのインスタンスがnum属性にアクセスする場合、実際にはデータ記述器にアクセスする_get__放し方.もちろんDescriptorディスクリプタが実装されている場合のみget__メソッドは、Digitクラスのインスタンスがnumプロパティにアクセスし、インスタンス独自のnumプロパティにアクセスする非データ記述器です.この属性のアクセス順に基づいて、書き換えると_getattribute__()はデータ記述を失効させることができるので、テストしてみてください.
     
     
    上記の知識を理解したら、このディスクリプタがどのような応用シーンを持っているか知りたいと思います.
    pythonの公式の応用シーンには、関数、属性、静的メソッド、クラスメソッド、すなわちpythonのこれらの下位実装が記述器に用いられており、その後、pythonが持参したproperty記述器を独自に実装する.
    私たちの普段のコードの応用シーンは、データチェック、ORM Managerのクラス呼び出しのみ、インスタンス呼び出しではありません.
     
    05、ORMにおけるManagerDescripterの実現
      
    Django ORM文を使用してデータベースを操作する場合、クラスを使用します.objects.get()はなぜinstanceではないのですか.objects.get()は?これは、ManagerDescripter記述器で呼び出し者を制限し、コードを見ているからです.
    class ManagerDescriptor:
    
        def __init__(self, manager):
            self.manager = manager
    
        def __get__(self, instance, cls=None):
            if instance is not None:           #              onjects      
                raise AttributeError("Manager isn't accessible via %s instances" % cls.__name__)
    
            if cls._meta.abstract:
                raise AttributeError("Manager isn't available; %s is abstract" % (
                    cls._meta.object_name,
                ))
    
            if cls._meta.swapped:
                raise AttributeError(
                    "Manager isn't available; '%s.%s' has been swapped for '%s'" % (
                        cls._meta.app_label,
                        cls._meta.object_name,
                        cls._meta.swapped,
                    )
                )
    
            return cls._meta.managers_map[self.manager.name]

    ここは本当にデザインが本当に精巧で、多くの友达がこれを見て少し愚かなobjectsは何ですか?ManagerDescriptorがどのように呼び出されるかは、このセクションの説明器に関する知識のため、ORM関連のソースコードを問い合わせることができます.
     
    06、データチェック
    私が教育管理システムをしているとき、私たちは学生クラスを持っていて、学生の各科の成績を記録するために使用しています.私たちは記録の成績を負数にすることはできません.このとき、私たちの学生クラスのコードは以下のようになります.
    class Student:
    
        def __init__(self, chinese, math):
            self.chinese = chinese if chinese >= 0 else 0
            self.math = math if chinese >= 0 else 0

    このような書き方であれば、点数についていくつかの検査条件やいくつかの科目を多ければ、このコード全体は非常に肥大化し、見苦しいと思います.
     
    このとき、pythonが持っているpropertyで科目の属性を飾ると、大量の重複コードを省くことができ、2.0コードがあるかもしれません.
    class Student:
    
        def __init__(self, chinese, math):
            self._chinese = None
            self._math = None
            self.chinese = chinese
            self.math = math
    
        @property
        def math(self):
            return self._math
    
        @math.setter
        def math(self, value):
            if value >= 0:
                self._math = value
            else:
                self._math = 0
    
        @property
        def chinese(self):
            return self._chinese
    
        @chinese.setter
        def chinese(self, value):
            if value >= 0:
                self._chinese = value
            else:
                self._chinese = 0
    
    
    c = Student(1, -1)

     
    この場合、複数のチェックがある場合、コードを簡潔にすることができますが、多科目の場合にコードを繰り返す問題を解決することができず、各科目の追加変数を作成してスコアを格納する追加のオーバーヘッドをもたらします.
    この時、私たちのディスクリプタが登場しました.
    class Score:
    
        def __init__(self):
            self.score = dict()
    
        def __get__(self, instance, owner):
            if not self.score[instance]:
                return 0
            return self.score[instance]
    
        def __set__(self, instance, value):
            if value >= 0:
                self.score[instance] = value
            else:
                self.score[instance] = 0
    
    
    class Student:
        chinese = Score()
        math = Score()
    
        def __init__(self, chinese, math):
            self.chinese = chinese
            self.math = math
    
    
    c = Student(1, -1)
    print(c.math)

    ここではScoreディスクリプタを用いて各科目の点数を格納し,多科目時のデータ検証問題を完璧に解決したが,限界もあり,ここでは各科目の検査方式が同じであることを前提とし,そうでなければディスクリプタを新設して点数値を格納し,ディスクリプタではinstanceを辞書のキーとして用い,これには一定の限界があり,instanceのクラスがlistタイプなどの可変タイプであればinstanceをキーとすることはできない.
    ここを見て気づいたかどうか、ORMのModel類に似ています.ははは、自分でソースコードを見に行きます.
     
    07.ディスクリプタでpropertyを実現する
    class Property:
        def __init__(self, fget=None, fset=None, fdelete=None):
            self.fget = fget
            self.fset = fset
            self.fdel = fdelete
    
        def __get__(self, instance, owner):
            if instance is None:
                return self
            if self.fget is None:
                raise AttributeError("unreadable attribute")
            return self.fget(instance)
    
        def __set__(self, instance, value):
            if self.fset is None:
                raise AttributeError("can't set attribute")
            self.fset(instance, value)
    
        def __delete__(self, instance):
            if self.fdel is None:
                raise AttributeError("can't delete attribute")
            self.fdel(instance)
    
        def getter(self, func):
            return type(self)(func, self.fset, self.fdel)
    
        def setter(self, func):
            return type(self)(self.fget, func, self.fdel)
    
        def deleter(self, func):
            return type(self)(self.fget, self.fset, func)
    
    
    class Student:
    
        def __init__(self, chinese, math):
            self._chinese = None
            self._math = None
            self.chinese = chinese
            self.math = math
    
        @Property
        def math(self):
            return self._math
    
        @math.setter
        def math(self, value):
            if value >= 0:
                self._math = value
            else:
                self._math = 0
    
        @Property
        def chinese(self):
            return self._chinese
    
        @chinese.setter
        def chinese(self, value):
            if value >= 0:
                self._chinese = value
            else:
                self._chinese = 0
    
    
    c = Student(1, -1)
    print(c.math)
    c.math = 1

    上記のコードのPropertyはpythonコードの実装であり,本質的に記述器が用いられていることが分かった.
    他のpython下位メソッド実装は、公式サイトを参照してください.https://docs.python.org/zh-cn/3/howto/descriptor.html