ディスクリプター: __get__、__set__、__delete__
属性アクセスを制御するディスクリプタープロトコルを実装します。
「ディスクリプター: __get__、__set__、__delete__」はCoddyKit上の無料Python Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはPython Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Python Academyコースには全4レッスンが含まれています。
ディスクリプタとは
ディスクリプタは__get__、__set__、または__delete__を定義し、クラス属性として割り当てられるオブジェクトです。Pythonは属性アクセス時にこれらのメソッドを呼び出します。
class Descriptor:
def __get__(self, obj, objtype=None):
print(f"__get__ called, obj={obj}")
return 42
class MyClass:
attr = Descriptor()
print(MyClass().attr) # __get__ called ... 42データディスクリプタと非データディスクリプタ
データディスクリプタは__set__または__delete__を定義し、インスタンスの__dict__より優先されます。非データディスクリプタ(__get__のみ)は__dict__に優先されます。
class DataDesc:
def __get__(self, obj, t): return "data"
def __set__(self, obj, val): pass
class NonDataDesc:
def __get__(self, obj, t): return "nondata"
class C:
d = DataDesc()
n = NonDataDesc()
c = C()
c.__dict__["n"] = "instance" # shadows the non-data desc
c.__dict__["d"] = "instance" # does NOT shadow data desc
print(c.n) # instance
print(c.d) # data__get__のシグネチャ
__get__(self, obj, objtype)では、objはインスタンスです(クラスからアクセスした場合はNone)。objtypeはクラスです。
class Verbose:
def __get__(self, obj, objtype=None):
if obj is None:
return self # accessed on class
return f"value for {obj!r}"
class Widget:
label = Verbose()
print(Widget.label) # <Verbose object>
print(Widget().label) # value for <Widget object>__set__とバリデーション
__set__(self, obj, value)を実装すると、値を保存する前に検証または変換できます。
class PositiveInt:
def __set_name__(self, owner, name):
self.name = name
def __get__(self, obj, t):
return obj.__dict__.get(self.name)
def __set__(self, obj, val):
if not isinstance(val, int) or val <= 0:
raise ValueError(f"{self.name} must be a positive int")
obj.__dict__[self.name] = val
class Rect:
width = PositiveInt()
height = PositiveInt()
r = Rect()
r.width = 10
# r.width = -1 # ValueError__set_name__
Python 3.6以降では、ディスクリプタがクラスに割り当てられたときに__set_name__(owner, name)が呼び出され、自身の属性名にアクセスできるようになります。
class Typed:
def __set_name__(self, owner, name):
self.public = name
self.private = "_" + name
def __get__(self, obj, t):
return None if obj is None else getattr(obj, self.private, None)
def __set__(self, obj, val):
setattr(obj, self.private, val)
class User:
name = Typed()
u = User()
u.name = "Alice"
print(u.name) # Alice属性削除のための__delete__
del obj.attrを横取りするには、__delete__(self, obj)を定義します。
class Protected:
def __set_name__(self, owner, name): self.name = name
def __get__(self, obj, t):
return obj.__dict__.get(self.name)
def __set__(self, obj, v): obj.__dict__[self.name] = v
def __delete__(self, obj):
raise AttributeError(f"Cannot delete {self.name}")
class Config:
host = Protected()
c = Config(); c.host = "localhost"
# del c.host # AttributeError関数は非データディスクリプタ
関数は__get__を実装しており、インスタンスからアクセスされたときにバウンドメソッドを返します。これがPythonのメソッドバインディングの仕組みです。
def greet(self):
return f"Hello from {self}"
class C: pass
C.greet = greet
c = C()
print(c.greet()) # Hello from <C object>classmethodとstaticmethodはディスクリプタ
classmethodとstaticmethodは、関数をラップして__get__の戻り値を変更する組み込みのディスクリプタクラスです。
class MyClass:
@classmethod
def from_string(cls, s):
return cls()
# classmethod.__get__ returns a bound method with cls
# staticmethod.__get__ returns the plain function遅延属性ディスクリプタ
値を一度だけ計算し、インスタンスごとにキャッシュするディスクリプタを作成します。
class LazyAttr:
def __init__(self, func): self.func = func
def __set_name__(self, owner, name): self.name = name
def __get__(self, obj, t):
if obj is None: return self
val = self.func(obj)
obj.__dict__[self.name] = val # shadows descriptor
return val
class Report:
@LazyAttr
def summary(self):
print("computing...")
return "done"
r = Report()
print(r.summary) # computing... done
print(r.summary) # done (cached)propertyはディスクリプタ
property自体が、Cで実装されたデータディスクリプタです。ディスクリプタを理解すると、@property、@classmethod、@staticmethodがどのように動作するかを理解しやすくなります。
class Circle:
def __init__(self, r): self._r = r
@property
def radius(self): return self._r
@radius.setter
def radius(self, v):
if v < 0: raise ValueError
self._r = v
# property is: property.__get__ = fget, property.__set__ = fsetディスクリプタの検索順序
Pythonの属性検索の順序は、1) type(MRO)からのデータディスクリプタ、2) インスタンスの __dict__、3) 非データディスクリプタとクラス変数です。
# Pseudocode for obj.attr:
# 1. Check type(obj).__mro__ for a data descriptor
# 2. Check obj.__dict__
# 3. Check type(obj).__mro__ for non-data descriptor or class var
# 4. Raise AttributeError確認問題
インスタンスの __dict__ より優先されるのは、どの種類のディスクリプタですか。
まとめ
ディスクリプタは、__get__、__set__、__delete__ を通じて属性アクセスを横取りします。データディスクリプタはインスタンスの __dict__ より優先されますが、非データディスクリプタは優先されません。__set_name__ を使うと、属性名を取得できます。property、classmethod、staticmethod はすべてディスクリプタです。
よくある質問
「ディスクリプター: __get__、__set__、__delete__」レッスンは無料ですか?
はい。「ディスクリプター: __get__、__set__、__delete__」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Python Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Python Academyコースには全4レッスンが含まれています。
「ディスクリプター: __get__、__set__、__delete__」で何を学びますか?
属性アクセスを制御するディスクリプタープロトコルを実装します。 ブラウザで直接実行するハンズオンコードでPython Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Python Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのPython Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。
「ディスクリプター: __get__、__set__、__delete__」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このPython Academyレッスンでコードを書いて実行できますか?
はい。すべてのPython Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- Python のクラスが作成される仕組み
- カスタムメタクラスの作成
- ディスクリプター: __get__、__set__、__delete__
- __slots__ とメモリ最適化