Pythonのdescriptorについて〜〜obj.dは一体どういう処理をしている？

#はじめに
[前回](https://qiita.com/edad811/items/824e02a474e71df88745)の続きとして、今回もPythonのクラスについて復習していきたいと思います。今回はdescriptorについて話します。（続編もあるはずです）

あくまでも個人の見解なので、間違いがあったらご容赦ください。

##attributeについて

###class attributeとinstance attribute
descriptorを説明する前に、まずattributeについて復習しましょう。

例えば、次の例を考えます。

```py
class MyClass:
    a = "class attribute"
    def __init__(self):
        self.b = "instance attribute"

obj = MyClass()

print(obj.a)  # class attribute
print(obj.b)  # instance attribute
print(MyClass.a)  # class attribute
print(MyClass.b)  # AttributeError: type object 'MyClass' has no attribute 'b'
```
ここで、MyClassというクラスの中に、class attributeのaと＿init＿の中にinstance attributeのbが定義されている。このようなattributeは属性とも呼ばれます。
MyClassをインスタンス化して、objというインスタンスを作ります。
そうすると、上で書いてあるように、class attributeはインスタンスとクラスの両方からアクセスできますが、instance attributeはインスタンスからしかアクセスできません。

これは当たり前ですね。

では、ちょっと面白いものを見てみましょう。例えば、class attributeとinstance attributeの名前が同じものにすると、何が起きるでしょう？

```py
class MyClass:
    a = "class attribute"
    def __init__(self):
        self.a = "instance attribute"

obj = MyClass()
print(obj.a)  # instance attribute
print(MyClass.a)  # class attribute
```
obj.aはinstance attributeにしかアクセスできません。class attributeにアクセスしたい場合、MyClass.aのようにクラスの名前を使う必要があります。

###attributeのアクセスの背後
attributeのアクセスは実は\_\_getattribute\_\_というマジックメソッドが使われています。
ビルトイン関数dir()を使えば、\_\_getattribute\_\_の存在を確認することができます。

```py
dir(MyClass)

# ['__class__',
#  '__delattr__',
#  '__dict__',
#  '__dir__',
#  '__doc__',
#  '__eq__',
#  '__format__',
#  '__ge__',
#  '__getattribute__',　　　# ここです
#  '__gt__',
#  '__hash__',
#  '__init__',
#  '__init_subclass__',
#  '__le__',
#  '__lt__',
#  '__module__',
#  '__ne__',
#  '__new__',
#  '__reduce__',
#  '__reduce_ex__',
#  '__repr__',
#  '__setattr__',
#  '__sizeof__',
#  '__str__',
#  '__subclasshook__',
#  '__weakref__',
#  'a']
```
ここで、もう一個注意していただきたいのは\_\_dict\_\_という辞書です。
この辞書はオブジェクトのattributeを保存しています。クラスのattributeはクラスの\_\_dict\_\_に保存されていて、インスタンスのattributeはインスタンスの\_\_dict\_\_に保存されています。

上の例を見てみると

```py
print(obj.__dict__)
# {'a': 'instance attribute'}

print(MyClass.__dict__)

# {'__module__': '__main__', 'a': 'class attribute', '__init__': <function MyClass.__init__ at 0x108715048>, '__dict__': <attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': None}

```
なので、実はobj.aはobj.\_\_dict\_\_["a"]と同じ結果となります。MyClass.aもMyClass.\_\_dict\_\_["a"]と同じ結果となります。

```py
obj.a == obj.__dict__["a"]  # True

MyClass.a == MyClass.__dict__["a"]  # True
```
しかし、その背後には\_\_getattribute\_\_が使われています。この\_\_getattribute\_\_は全てのクラスに存在します。その挙動を明確化するために、ちょっとオーバーライドしましょう。

```py
class MyClass:
    a = "class attribute"
    def __init__(self):
        self.a = "instance attribute"

    def __getattribute__(self, name):
        print(f"Called __getattribute__({name})")
        try:
            return super().__getattribute__(name)
        except AttributeError:
            raise
    

obj = MyClass()
print(obj.a)
# Called __getattribute__(a)
# instance attribute
```
このように、全てのattributeにアクセスする前に、実はこの\_\_getattribute\_\_が呼び出されています。obj.aは\_\_getattribute\_\_を呼び出し、obj.\_\_dict\_\_の中から"a"の値を探して、それが見つかったらその値obj.\_\_dict\_\_["a"]を返します。この例ではobj.\_\_dict\_\_の中に"a"が存在するので、そのまま値を返せば良いわけです。

しかし、最初の例を思い出してください。aがclass attributeの場合はどうなるの

```py
class MyClass:
    a = "class attribute"
    def __init__(self):
        self.b = "instance attribute"

    def __getattribute__(self, name):
        print(f"Called __getattribute__({name})")
        try:
            return super().__getattribute__(name)
        except AttributeError:
            raise
    

obj = MyClass()
print(obj.a)

# Called __getattribute__(a)
# class attribute
```
ここでは、実は同じく\_\_getattribute\_\_が呼び出されていて、まずobj.\_\_dict\_\_の中を探して、見つからないので、クラスの\_\_dict\_\_に戻してもう一度探します。
つまり、type(obj).\_\_dict\_\_（これはMyClass.\_\_dict\_\_と同じ意味です）の中から"a"の存在をチェックします。この例では、クラスの\_\_dict\_\_に"a"が存在しますので、その値を返します。

もしなかったら、次の例のように、例外をおこします。

```py
print(obj.c)


# Called __getattribute__(c)
# #######################
# #######################
# #######################
# AttributeError: 'MyClass' object has no attribute 'c'
```
### instance attributeとclass attributeのまとめ
ちょっとまとめると、obj.aはまずinstanceの\_\_dict\_\_をチェックして、発見したらその値(instance attribute)を返します。なかったら、classの\_\_dict\_\_をチェックして、発見したらその値(class attribute)を返します。もし、このクラスが何か他のクラスを継承していれば、スーパークラスの\_\_dict\_\_をもチェックします。これでもなかったら、例外処理に入ります。(実は\_\_getattr\_\_にはいりますが、定義する必要があります。)

##descriptor
descriptorは\_\_get\_\_、\_\_set\_\_、\_\_delete\_\_メソッドを提供したオブジェクトのことです。（すべて必須というわけではありません）。
property、classmethod、staticmethod、superや通常のインスタンスメソッドは全てdescriptorがあるからこそ定義できます。（これらとdescriptorの詳細な内部構造はここで詳しく説明しません。次の記事で解説する予定です。）

通常は以下のような構造になっています。

```py
class Descriptor():
    def __get__(self, obj, type=None):
        pass
    def __set__(self, obj, value):
        pass
    def __delete__(self, obj):
        pass
```


特に、以下のような分類があります。これを覚えておきましょう。
Data Descriptor:
\_\_get\_\_と\_\_set\_\_の両方を定義しているdescriptor
Non-data Descriptor:(メソッドの典型的な実現方法)
\_\_get_\_だけを定義しているdescriptor

次は例を見てみましょう。

```py
class DataDiscriptor(object):
    """
     data discriptor

    """

    def __init__(self, v=None):
        print("Called __init__() in descriptor")
        self._v = v

    def __get__(self, instance, type):
        print("Called __get__() in descriptor")
        return self._v

    def __set__(self, instance, value):
        print("Called __set__() in descriptor")
        self._v = value

    def __delete__(self, instance):
        print("Called __delete__() in descriptor")
        del self._v
        
class MyClass:
    d = DataDiscriptor("descriptor attribute")

```
ここで、 MyClassを定義する時、dはDataDiscriptorのインスタンスとして初期化されます。
なので、上記のコードを実行すると、次のような結果が表示されます。

```
Called __init__() in descriptor
```
MyClassのobjを作ります。obj.dの値をアクセスまたは修正する時、それぞれ\_\_get\_\_と\_\_set\_\_が呼び出されます。

```py
obj = MyClass()
print(obj.d)

# Called __get__() in descriptor
# descriptor attribute
```
```py
print(MyClass.d)

# Called __get__() in descriptor
# descriptor attribute
```

```py
obj.d = "new descriptor attribute"

# Called __set__() in descriptor

print(obj.d)
# Called __get__() in descriptor
# new descriptor attribute
```


しかし、このdはclass attributeではなく、DataDiscriptor objectとなります。確認してみましょう。

```py
print(MyClass.__dict__)

#{'__module__': '__main__', 'd': <__main__.DataDiscriptor object at 0x110b34470>, '__dict__': <attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': None}
```
なので、obj.dという短い文は、このdはclass attributeか、instance attributeか、descriptor objectかのどちらかによって、振る舞いが全然違ってきます。

ここで、問題です。
obj.dは実際どういう順序で何をチェックしているのでしょう？


## descriptorまで考慮する時のアクセス順序
前半の部分でclass attributeとinstance attributeのアクセス順序について議論しました。これらに加えて、descriptorまで考慮すると、どうなるでしょう。

結論からいうと、obj.dは次のような順序でチェックしています。
1.\_\_getattribute\_\_、無条件で呼び出す
2.data descriptorであれば、\_\_get\_\_を呼び出す
3.インスタンスの\_\_dict\_\_（obj.\_\_dict\_\_）をチェックする（descriptorと名前が一緒だったら、インスタンス\_\_dict\_\_中の同名なものが消える）
4.non-data descriptorであれば、\_\_get\_\_を呼び出す
5.クラスの\_\_dict\_\_（MyClass.\_\_dict\_\_）をチェックする
6.クラス（MyClass）のスーパークラスの\_\_dict\_\_をチェックする(多重継承の場合は多重チェック)
7.\_\_getattr\_\_メソッド

ここで、上記の３について検証してみましょう。

```py
class DataDiscriptor(object):
    """
     data discriptor

    """

    def __init__(self, v=None):
        self._v = v

    def __get__(self, instance, type):
        return self._v

    def __set__(self, instance, value):
        self._v = value

    def __delete__(self, instance):
        del self._v
        
class MyClass:
    d = DataDiscriptor("descriptor attribute")
    def __init__(self):
        self.d = "xxx"
```

```py
obj = MyClass()

print(obj.__dict__)
# {}

print(MyClass.__dict__)
#{'__module__': '__main__', 'd': <__main__.DataDiscriptor object at 0x10a939550>, '__init__': <function MyClass.__init__ at 0x10a929840>, '__dict__': <attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': None}
```

ここで問題です。次のコードはどうしてでしょう？

```py
print(obj.d)

# xxx
```
このobj.\_\_dict\_\_の中にdが存在しないので、obj.dがxxxになっています。これは上の順序関係と違うじゃん！

実は、MyClassを定義する時、まずdはdescriptorのインスタンスとして初期化されて、ここでDataDiscriptorの\_\_init\_\_が呼び出されます。
次に、objを作る時、MyClassの\_\_init\_\_が呼び出されて、self.bをアクセス（ここではdescriptorの\_\_get\_\_が呼び出されます）して、このbを"xxx"となるように書き換え（ここではdescriptorの\_\_set\_\_が呼び出されます）をしています。


ここはコードを書いて検証してみましょう。

```py
class DataDiscriptor(object):
    """
     data discriptor

    """

    def __init__(self, v=None):
        print("Called __init__() in descriptor")
        self._v = v

    def __get__(self, instance, type):
        print("Called __get__() in descriptor")
        return self._v

    def __set__(self, instance, value):
        print("Called __set__() in descriptor")
        self._v = value

    def __delete__(self, instance):
        print("Called __delete__() in descriptor")
        del self._v
        
class MyClass:
    d = DataDiscriptor("descriptor attribute")
    def __init__(self):
        self.d = "xxx"
```
結果：

```py
Called __init__() in descriptor
```

次に、

```py
obj = MyClass()
```
結果：

```py
Called __set__() in descriptor
```
予想通りの動きになっています。

もし、dの後に同名のclass attributeを定義したら、上記と違って、このclass attributeは同名なdescriptorを完全に上書きして、クラスの\_\_dict\_\_の中から、'd': <\_\_main\_\_.DataDiscriptor object at 0x10a939550>を'd': class attributeのように上書きします。
しかし、このようなやり方をする人は流石にいないでしょう。

#終わりに
今回の記事の最終目的はobj.dのような文は一体どういう処理をしているかを説明するものです。まだまだ不十分なところがありますが(そもそも\_\_getattribute\_\_、\_\_getattr\_\_などはなに？）、これから、ゆっくりとまとめていきたいつもりです。
結論をもう一度いうと、obj.dは次のような順序でチェックしています。
1.\_\_getattribute\_\_、無条件で呼び出す
2.data descriptorであれば、\_\_get\_\_を呼び出す
3.インスタンスの\_\_dict\_\_（obj.\_\_dict\_\_）をチェックする（descriptorと名前が一緒だったら、インスタンス\_\_dict\_\_中の同名なものが消える）
4.non-data descriptorであれば、\_\_get\_\_を呼び出す
5.クラスの\_\_dict\_\_（MyClass.\_\_dict\_\_）をチェックする
6.クラス（MyClass）のスーパークラスの\_\_dict\_\_をチェックする(多重継承の場合は多重チェック)
7.\_\_getattr\_\_メソッド

次回はdescriptorの内部の詳細とproperty、classmethod、staticmethod、superや通常のインスタンスメソッドなどがどういう風に実現されているのかを解説する予定です。


#参考記事
https://qiita.com/knzm/items/a8a0fead6e1706663c22