Closed11

Fluent Python 読書メモ

Daiki OkayamaDaiki Okayama

19章 動的属性とプロパティ

Daiki OkayamaDaiki Okayama

obj.data の検索順

  1. obj.__class__data という名前の プロパティ
  2. obj
  3. obj.__class__
  4. obj.__class__ の親クラス
Daiki OkayamaDaiki Okayama
>>> class Foo:
...     @property
...     def bar(self):
...         '''The bar attribute'''
...         return self.__dict__['bar']
...     
...     @bar.setter
...     def bar(self, value):
...         self.__dict__['bar'] = value
...         
>>> help(Foo.bar)
Help on property:

bar
    The bar attribute

下のコードと等価

>>> class Hoge:
...     fuga = property(
...         lambda self: self.__dict__["fuga"],
...         lambda self, value: self.__dict__.update({"fuga": value}),
...         doc="The fuga attribute",
...     )
...                 
>>> help(Hoge.fuga)
Help on property:

fuga
    The fuga attribute
Daiki OkayamaDaiki Okayama

weight = quantity("weight") と 2 回 weight を書かないといけないのが冗長。

>>> def quantity[T](storage_name: str) -> property:
...     def qty_getter(instance: T) -> int:
...         return instance.__dict__[storage_name]
... 
...     def qty_setter(instance: T, value: int):
...         if value > 0:
...             instance.__dict__[storage_name] = value
...         else:
...             raise ValueError("value must be > 0")
... 
...     return property(qty_getter, qty_setter)
... 
... 
... class LineItem:
...     weight = quantity("weight")
...     price = quantity("price")
... 
...     def __init__(self, description: str, weight: int, price: int) -> None:
...         self.description = description
...         self.weight = weight
...         self.price = price
... 
...     def subtotal(self) -> int:
...         return self.weight * self.price
... 
... 
>>> apple = LineItem("apple", 10, 1)
>>> apple.weight
10
>>> apple.weight = 11
>>> apple.weight
11
>>> apple.weight = -11
Traceback (most recent call last):
  File "<python-input-4>", line 1, in <module>
    apple.weight = -11
    ^^^^^^^^^^^^
  File "<python-input-0>", line 9, in qty_setter
    raise ValueError("value must be > 0")
ValueError: value must be > 0
Daiki OkayamaDaiki Okayama

__getattribute__ によって属性を取得しようとしたときの振る舞いを定義できる。(ドット表記、getattr(), hasattr()
__getattr__ は、__getattribute__ の呼び出し中に AttributeError が投げられると呼ばれる。

>>> class Sample:
...     def __getattribute__(self, name: str):
...         print("called __getattribute__")
... 
...         raise AttributeError
... 
...     def __getattr__(self, name: str):
...         print("called __getattr__")
... 
... 
>>> Sample().name
called __getattribute__
called __getattr__
Daiki OkayamaDaiki Okayama

20章 属性ディスクリプタ

Daiki OkayamaDaiki Okayama

ディスクリプタとは、同じアクセスロジックを複数の属性で再利用する方法です。

import abc


class AutoStorage[T]:
    __counter = 0

    def __init__(self):
        cls = self.__class__
        prefix = cls.__name__
        index = cls.__counter
        self.storage_name = f"_{prefix}#{index}"
        cls.__counter += 1

    def __get__(self, instance: object, owner):
        if instance is None:
            return self
        else:
            return getattr(instance, self.storage_name)

    def __set__(self, instance: object, value: T):
        setattr(instance, self.storage_name, value)


class Validated[T](abc.ABC, AutoStorage[T]):
    def __set__(self, instance: object, value: T):
        value = self.validate(instance, value)
        super().__set__(instance, value)

    @abc.abstractmethod
    def validate(self, instance: object, value: T) -> T:
        """return validated value or raise ValueError"""


class Quantity(Validated[int]):
    def validate(self, instance: object, value: int) -> int:
        if value <= 0:
            raise ValueError("value must be > 0")
        return value


class NonBlank(Validated[str]):
    def validate(self, instance: object, value: str) -> str:
        value = value.strip()
        if len(value) == 0:
            raise ValueError("value cannot be empty or blank")
        return value

Daiki OkayamaDaiki Okayama

このアサーションは失敗する

class Sample:
    def f(self):
        pass


assert Sample().f is Sample.f

Sample().f の方は自動的に第一引数に Sample() が渡されることを考えれば驚くこともない。

Daiki OkayamaDaiki Okayama

特殊メソッドにおいては、インタプリタはクラスそのものからメソッドを検索する。

repr(x) <=> x.__class__.__repr__(x)

このスクラップは2025/01/12にクローズされました