0Pricing
Python Academy · レッスン

ディスクリプター: __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フィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. Python のクラスが作成される仕組み
  2. カスタムメタクラスの作成
  3. ディスクリプター: __get__、__set__、__delete__
  4. __slots__ とメモリ最適化
← Python Academyに戻る