Python記述子についてもう少し

少し前のHabréには、すでにレイモンド・ヘッティンガーの記事のガイドの翻訳がありました。 この記事では、私を読んだ後に生じた問題を検討しようとします。 いくつかのコード例があり、実際にはそれらに対する質問と回答があります。 それが何であるかを理解するには、記述子が何であり、なぜであるかを知る必要があります。

記述子はいつ呼び出されますか?


次のコードを検討してください。

>>> class M(type):
... def __new__(cls, name, bases, dct):
... dct['test'] = lambda self, x: x*2
... return type.__new__(cls, name, bases, dct)
...
>>> class A(object):
... def __init__(self):
... self.__dict__['test2'] = lambda self, x: x*4
... __metaclass__ = M
...
>>> A().test(2)
4
>>> A().test2(2)
Traceback (most recent call last):
File " < stdin > ", line 1, in < module >
TypeError: < lambda > () takes exactly 2 arguments (1 given)


* This source code was highlighted with Source Code Highlighter .

何が悪いの? オブジェクト辞書を使用して同じ方法で関数を追加するようです。 test2関数のハンドルが呼び出されないのはなぜですか? 実際、クラス辞書には「test」関数が定義され、オブジェクト辞書には「test2」関数が定義されています。

>>> 'test' in A.__dict__
True
>>> 'test2' in A.__dict__
False
>>> 'test' in A().__dict__
False
>>> 'test2' in A().__dict__
True


* This source code was highlighted with Source Code Highlighter .

したがって、最初の答え:関数「__get__」は、クラスのプロパティであり、このクラスのオブジェクトのプロパティではない記述子に対してのみ呼び出されます。

記述子とは何ですか?


前の答えはすぐに疑問を提起します-「記述子のみ」とはどういう意味ですか? 記述子は作成しませんでした。関数のみを作成しました!

答えは期待されています-Pythonの関数は記述子です(より正確には、データ記述子ではありません):

>>> def foo(x):
... return x * 2
...
>>> '__get__' in dir(foo)
True


* This source code was highlighted with Source Code Highlighter .

バウンド/アンバウンドメソッド


そして最後に、最もおいしい。 チャレンジ:

メソッドを動的に追加する必要がある特定のクラスのオブジェクトがあります。もちろん、このオブジェクトに「self」パラメータを渡す必要があります。 このメソッドをクラスディクショナリに追加することはできません(他のオブジェクトに影響を与えたくない)。

「額」の解決は行かない:

>>> class A(object):
... def __init__(self):
... self.x = 3
...
>>> def add_x(self, add):
... self.x += add
... print 'Modified value: %s' % (self.x,)
...
>>> a = A()
>>> a.add_x = add_x
>>> ax
3
>>> a.add_x(3)
Traceback (most recent call last):
File " < stdin > ", line 1, in < module >
TypeError: add_x() takes exactly 2 arguments (1 given)


* This source code was highlighted with Source Code Highlighter .

最初の答えを確認する予期されるエラーが表示されます-オブジェクトの記述子プロパティにアクセスするとき、「__ get__」は使用されません。 どうする?

作成した関数の__get__メソッドを少し試してみましょう。

>>> add_x.__get__
< method-wrapper '__get__' of function object at 0x800f2caa0 >
>>> add_x.__get__(a)
< bound method ?. add_x of &# 60 ; __main__ . A object at 0x800f2dc50 >>
>>> add_x.__get__(a, A)
< bound method A . add_x of &# 60 ; __main__ . A object at 0x800f2dc50 >>
>>> add_x.__get__(None, A)
< unbound method A . add_x >


* This source code was highlighted with Source Code Highlighter .

ああ、それが私たちに必要なもののようです! 最後から2行目には、「バインド」メソッドがあります。これは、「A()。Add_x」メソッドがクラス「A」で定義されている場合、「Add_x」がどのように見えるかということです。 最後の行には、「A.add_x」の呼び出しの「予想される」結果が表示されています。 これで、オブジェクトにメソッドを追加する方法がわかりました。

>>> a.add_x = add_x.__get__(a, A)
>>> a.add_x(3)
Modified value: 6


* This source code was highlighted with Source Code Highlighter .

ビンゴ!
したがって、クラスおよびオブジェクトの「バインド/非バインド」メソッドを作成するのは、記述子関数の__get__メソッドです。 そして、ここには魔法はありません:)

文学


他に読むものは何ですか? 実際、少し。 Python Data Modelの記述子implementing-descriptors )について説明する章がいくつかありますが、本当に良いHettingerの記事( original )を思い出すことができます。

PSロシア語は母国語ではありません。PMでコメントをお願いします。

Source: https://habr.com/ru/post/J123216/


All Articles