Python記述子

記述子#の紹介

スタックオーバーフローの記述子に関する質問から始めます。

classCelsius:

 def __get__(self, instance, owner):return5*(instance.fahrenheit -32)/9

 def __set__(self, instance, value):
  instance.fahrenheit =32+9* value /5classTemperature:

 celsius =Celsius()

 def __init__(self, initial_f):
  self.fahrenheit = initial_f

t =Temperature(212)print(t.celsius)  #出力100.0
t.celsius =0print(t.fahrenheit)  #出力32.0

上記のコードは、CelsiusとFahrenheitの温度間の自動変換を実現します。 Temperatureクラスには、インスタンス変数fahrenheitとクラス変数celsiusが含まれています。Celsiusは記述子Celsiusで表されます。このコードからの3つの質問:

  1. 質問1:記述子とは何ですか?
  2. 質問2: __get__ __set__、および __delete__の3つのメソッドのパラメーター
  3. 質問3:記述子の適用シナリオは何ですか
  4. 質問4:プロパティと記述子の違いは何ですか?

質問1:記述子とは何ですか? #

記述子は、 __get__ __set__、および __delete__の1つ以上のメソッドを実装するクラスオブジェクトです。クラス変数がそのようなデコレータを指す場合、このクラス変数にアクセスすると __get__メソッドが呼び出され、このクラス変数に値を割り当てると __set__メソッドが呼び出されます。このクラス変数は記述子と呼ばれます。

記述子は実際にはプロキシメカニズムです。クラス変数が記述子として定義されている場合、このクラス変数に対する操作はこの記述子によって表されます。

質問2:記述子の3つのメソッドのパラメーター#

classdescriptor:
 def __get__(self, instance, owner):print(instance)print(owner)return'desc'

 def __set__(self, instance, value):print(instance)print(value)

 def __delete__(self, instance):print(instance)classA:
 a =descriptor()

del A().a  #出力<__main__.A object at 0x7f3fc867cbe0>A().a  #説明を返し、出力<__main__.A object at 0x7f3fc86741d0>,<class'__main__.A'>
A.a  #説明を返し、出力なし、<class'__main__.A'>A().a =5  #出力<__main__.A object at 0x7f3fc86744a8>,5
A.a =5  #クラスAのクラス変数を直接変更します。つまり、aは記述子記述子によってプロキシされなくなります。

上記の出力結果から結論を導き出すことができます。

パラメータの説明##

3つの方法の本質##

質問3:記述子の適用シナリオは何ですか#

新しい形式のインスタンス属性を作成したいのですが、変更とアクセスに加えて、型チェック、数値検証などの追加機能がいくつかあります。記述子「Pythonクックブック」を使用する必要があります。

つまり、記述子は主にインスタンス変数の操作を引き継ぐために使用されます。

classmethodデコレータを実装する##

from functools import partial
from functools import wraps

classClassmethod():
 def __init__(self, fn):
  self.fn = fn

 def __get__(self, instance, owner):returnwraps(self.fn)(partial(self.fn, owner))

メソッドfnの最初のパラメーターをインスタンスのクラスに修正します。公式のpythonドキュメントで別の書き方を参照できます:[descriptor](https://docs.python.org/3.5/howto/descriptor.html#static-methods-and-class-methods)

classClassMethod(object):
 def __init__(self, fn):
  self.fn = fn

 def __get__(self, instance, owner=None):if owner is None:
   owner =type(obj)
  def newfunc(*args):return self.f(owner,*args)return newfunc

staticmethodデコレータを実装する##

classStaticmethod:
 def __init__(self, fn):
  self.fn = fn

 def __get__(self, instance, cls):return self.fn

プロパティデコレータを実装する##

classProperty:
 def __init__(self, fget, fset=None, fdel=None, doc=''):
  self.fget = fget
  self.fset = fset
  self.fdel = fdel
  self.doc = doc

 def __get__(self, instance ,owner):if instance is not None:return self.fget(instance)return self

 def __set__(self, instance, value):if not callable(self.fset):
   raise AttibuteError('cannot set')
  self.fset(instance, value)

 def __delete__(self, instance):if not callable(self.fdel):
   raise AttributeError('cannot delete')
  self.fdel(instance)

 def setter(self, fset):
  self.fset = fset
  return self

 def deleter(self, fdel):
  self.fdel = fdel
  return self

カスタムプロパティを使用して、farenheitおよびcelsiusクラス変数を記述します。

classTemperature:
 def __init__(self, cTemp):
  self.cTemp = cTemp  #インスタンス変数cTempがあります:セルシウス温度

 def fget(self):return self.celsius *9/5+32

 def fset(self, value):
  self.celsius =(float(value)-32)*5/9

 def fdel(self):print('Farenhei cannot delete')

 farenheit =Property(fget, fset, fdel, doc='Farenheit temperature')

 def cget(self):return self.cTemp

 def cset(self, value):
  self.cTemp =float(value)

 def cdel(self):print('Celsius cannot delete')

 celsius =Property(cget, cset, cdel, doc='Celsius temperature')

使用結果:

t =Temperature(0)
t.celsius  #0を返します.0
del t.celsius  #出力Celsiusは削除できません
t.celsius =5
t.farenheit  #41を返します.0
t.farenheit =212
t.celsius  #100を返します.0
del t.farenheit  #出力ファレンヘイは削除できません

デコレータを使用して、Temperatureの2つのプロパティ、farenheitとcelsiusを装飾します。

classTemperature:
 def __init__(self, cTemp):
  self.cTemp = cTemp

 @ Property  # celsius =Property(celsius)
 def celsius(self):return self.cTemp

 @ celsius.setter
 def celsius(self, value):
  self.cTemp = value

 @ celsius.deleter
 def celsius(self):print('Celsius cannot delete')

 @ Property  # farenheit =Property(farenheit)
 def farenheit(self):return self.celsius *9/5+32

 @ farenheit.setter
 def farenheit(self, value):
  self.celsius =(float(value)-32)*5/9

 @ farenheit.deleter
 def farenheit(self):print('Farenheit cannot delete')

結果を使用して、記述子を使用してクラス変数を直接記述します

属性タイプチェックを実装します##

まず、型チェック記述子Typedを実装します

classTyped:
 def __init__(self, name, expected_type):
  # 各属性には名前と対応するタイプがあります
  self.name = name
  self.expected_type = expected_type

 def __get__(self, instance, cls):if instance is None:return self
  return instance.__dict__[self.name]

 def __set__(self, instance ,value):if not isinstance(value, self.expected_type):
   raise TypeError('Attribute {} expected {}'.format(self.name, self.expected_type))
  instance.__dict__[self.name]= value

 def __delete__(self, instance):
  del instance.__dict__[self.name]

次に、Personクラスを実装します。Personクラスの属性名と年齢は、Typedで記述されます。

classPerson:
 name =Typed('name', str)
 age =Typed('age', int)

 def __init__(self, name: str, age: int):
  self.name = name
  self.age = age

タイプチェックプロセス:

>>> Person.__dict__
mappingproxy({'__dict__':<attribute '__dict__'of'Person' objects>,'__doc__': None,'__init__':<function __main__.Person.__init__>,'__module__':'__main__','__weakref__':<attribute '__weakref__'of'Person' objects>,'age':<__main__.Typed at 0x7fe2f440bd68>,'name':<__main__.Typed at 0x7fe2f440bc88>})>>> p =Person('suncle',18)>>> p.__dict__
{' age':18,'name':'suncle'}>>> p =Person(18,'suncle')---------------------------------------------------------------------------
TypeError                                 Traceback(most recent call last)<ipython-input-88-ca4808b23f89>in<module>()---->1 p =Person(18,'suncle')<ipython-input-84-f876ec954895>in__init__(self, name, age)45     def __init__(self, name: str, age: int):---->6         self.name = name
  7   self.age = age

< ipython-input-83-ac59ba73c709>in__set__(self, instance, value)11     def __set__(self, instance ,value):12if not isinstance(value, self.expected_type):--->13             raise TypeError('Attribute {} expected {}'.format(self.name, self.expected_type))14         instance.__dict__[self.name]= value
     15 

TypeError: Attribute name expected <class'str'>

ただし、上記の型チェック方法にはいくつか問題があります。Personクラスには多くの属性がある可能性があるため、各属性をTyped記述子で1回記述する必要があります。この問題を解決するために、パラメーターを使用してクラスデコレーターを作成できます。

def typeassert(**kwargs):
 def wrap(cls):for name, expected_type in kwargs.items():setattr(cls, name,Typed(name, expected_type))  #古典的な執筆
  return cls
 return wrap

次に、typeassertクラスデコレータを使用してPersonクラスを再定義します。

@ typeassert(name=str, age=int)classPerson:
 def __init__(self, name, age):
  self.name = name
  self.age = age

typeassertクラスデコレータのパラメータは、渡された属性名とタイプのキーと値のペアであることがわかります。

typeassertクラスデコレータがクラスの初期化パラメータタイプを自動的に認識し、対応するクラス変数を追加する場合は、inspectライブラリとpythonタイプアノテーションを使用して次のことを実現できます。

import inspect
def typeassert(cls):
 params = inspect.signature(cls).parameters
 for name, param in params.items():if param.annotation != inspect._empty:setattr(cls, name,Typed(name, param.annotation))return cls

@ typeassert
classPerson:
 def __init__(self, name: str, age: int):  #タイプ注釈のないパラメータは管理されません
  self.name = name
  self.age = age

質問4:プロパティと記述子の違い#

Pythonの内部メカニズムを使用して、属性値を取得および設定できます。全部で3つの方法があります:

  1. ゲッターとセッター。メソッドを使用して、各インスタンス変数をカプセル化し、インスタンス変数の値を取得および設定できます。インスタンス変数が外部からアクセスされないようにするために、これらのインスタンス変数をプライベートとして定義できます。したがって、オブジェクトのプロパティにアクセスするには、明示的な関数anObject.setPrice(someValue); anObject.getValue()が必要です。
  2. プロパティ。組み込みのプロパティ関数を使用して、getter、setter(およびdeleter)関数をプロパティ名にバインドできます。したがって、プロパティへの参照は直接アクセスと同じくらい単純に見えますが、基本的にはオブジェクトの対応する関数を呼び出しています。たとえば、anObject.price = someValue; anObject.valueです。
  3. ディスクリプタ。 getter、setter(およびdeleter)関数を単一のクラスにバインドできます。次に、このクラスのオブジェクトを属性名に割り当てます。現時点では、各属性への参照は直接アクセスに似ていますが、基本的には記述子オブジェクトの対応するメソッドを呼び出します(例:anObject.price = someValue; anObject.value)。

GetterとSetterのデザインパターンはPythonicでは不十分です。C++とJAVAでは一般的ですが、Pythonは導入と直接アクセスを追求しています。

付録1、データ記述子およびデータなし記述子

中国語に翻訳されるのは、実際にはデータ記述子と非データ記述子です

2つの違いは次のとおりです。

classInt:
 def __get__(self, instance, cls):return3classA:
 val =Int()

 def __init__(self):
  self.__dict__['val']=5A().val  #5を返します
classInt:
 def __get__(self, instance, cls):return3

 def __set__(self, instance, value):
  pass

classA:
 val =Int()

 def __init__(self):
  self.__dict__['val']=5A().val  #3を返します

付録2.記述子メカニズム分析データ:

  1. [ 公式ドキュメント記述子](https://docs.python.org/3.5/howto/descriptor.html)
  2. understanding-get-and-set-and-python-descriptors
  3. [ anyisalin-Python-Descriptor](https://anyisalin.github.io/2017/03/08/python-descriptor/)
  4. [ Python記述子ガイド(翻訳)](http://pyzh.readthedocs.io/en/latest/Descriptor-HOW-TO-Guide.html#id8)
  5. Properties and Descriptors

Recommended Posts

Python記述子
Python CookBook
Python FAQ
Python3モジュール
python(you-get)
Python文字列
Pythonの基本
Pythonの基本2
Python exec
Pythonノート
Python3タプル
CentOS + Python3.6 +
Python Advanced(1)
Pythonデコレータ
Python IO
Pythonマルチスレッド
Pythonツールチェーン
Python3リスト
Pythonマルチタスク-日常
Pythonの概要
pythonの紹介
Pythonアナリティック
Pythonの基本
07.Python3関数
Pythonの基本3
Pythonマルチタスクスレッド
Python関数
python sys.stdout
python演算子
Pythonエントリ-3
Centos 7.5 python3.6
Python文字列
pythonキューキュー
Pythonの基本4
Pythonの基本5
Centos6はPython2.7.13をインストールします
Pythonは質問に答えます
Pythonはloopメソッドを終了します
Ubuntu16アップグレードPython3
Centos7はPython3.6をインストールします。
ubuntu18.04インストールpython2
Pythonの古典的なアルゴリズム
Python2.7 [インストールチュートリアル]
Python文字列操作
Python 3.9が登場!
python学習ルート
CentOS7アップグレードpython3
Python3の基本構文
Pythonレビュー1
linux + ubuntuはpythonを解決します
pythonの関数
CentOSはPython3.6をインストールします
Python3.9の7つの機能
Pythonファイル操作
ubuntu12.04インストールpython3
Pythonのデザインパターン
Python-centos6のインストール
Centos7はPython2.7をインストールします
01.Pythonの概要
100の小さなPythonの例
Pythonネットワークプログラミング