ゆくゆくは有へと

おかゆ/彼ノ∅有生 の雑記

「実践Python3」の Factory method がもやもやする

題のごとし。

ググって理解したところによれば、factory method って「具象クラスごとに、同種手続き(たいてい抽象クラスで定義されてるメソッド)時に呼び出されるクラス可変的なコンストラクタをメソッド化したもの(で、具象クラス実装時に中身が書かれるもの)」っぽい。

で、「同種手続き」というのを抽象クラスのレベルで書いちゃうというのがどうやら template method パティーンらしい。ググった感じだと、factory method パティーンは template method パティーンに更に組み込んで使うっぽい。

んだけど、「実践Python3」の場合、この「同種手続き」というのに共通性がほとんどなくて、「えぇ…」ってなっちゃった。

たぶん、Board の上に駒を置いていく操作、populate_board が同種手続き(factory method パティーンでいうanOperation?)に相当しそうなんだけど、そもそも anOperation は具象クラスでオーバーライドしない(したら意味ないし)から、「???」って感じだ。

でも、create_piece は「ファクトリー関数」らしくて、これのおかげで、populate_board は「「ファクトリーメソッド」と呼ぶことができる」らしい。そうすると、同書においては、__init__ が anOperation に相当して、populate_board がファクトリーメソッドということになるのだろうか…。

Factory Method パターン - Wikipedia 見てたら、

factoryMethodは、[...] パラメータを取り、それによって生成するクラスを変えることもある。

ともあるので、それならむしろ create_piecefactoryMethod では……?

そう考えると、AbstractBoard はこうしたくなる(ちなみに個人的な趣味で ABC にしておいた)

from abc import ABCMeta, abstractmethod

class AbstractBoard(metaclass=ABCMeta):

    def __init__(self, rows, columns):
        self.rows = rows
        self.columns = columns
        self.board = [[None for _ in range(columns)] for _ in range(rows)]
        self.populate_board()

    def populate_board(self):
        for row in range(self.rows):
            for column in range(self.columns):
                self.board[row][column] = self.create_piece(row, column)

    @abstractmethod
    def create_piece(row, column):
        pass

    def __str__(self):
        ...

populate_board は各マスに対して、サブクラスで実装されたcreate_piece(row, column) を呼び出して相応しいインスタンスをもらえる。populate_boardanOperation に相当するはずで、個別の具象ボードにおいて共通のプロセス。

ほんで、ChessBoard 具象クラスでは

class ChessBoard(AbstractBoard):

    def __init__(self):
        super().__init__(8, 8)

    def create_piece(self, row, column):
        piece_dict = self._init_piece_map()
        name_dict = {PAWN: "ChessPawn", ROOK: "ChessRook",
                     KNIGHT: "ChessKnight", BISHOP: "ChessBishop",
                     KING: "ChessKing", QUEEN: "ChessQueen"}
        if (row, column) in piece_dict:
            kind, color = piece_dict[(row, column)]
            name = name_dict[kind]
            return globals()[color + name]()
        else:
            return None

    def _init_piece_map(self):
        colors = (((0, 1), BLACK), ((6, 7), WHITE))
        kinds = (((0, 7), ROOK), ((1, 6), KNIGHT), ((2, 5), BISHOP),
                 ((3,), QUEEN), ((4,), KING), ((0, 1, 2, 3, 4, 5, 6, 7), PAWN))
        piece_dict = {(row, column): (kind, color)
                      for row, color in colors for column, kind in kinds}
        return piece_dict

ヘルパーメソッドとして _init_piece_map を用意した。_init_piece_map で初期状態における各コマの座標の辞書を返してもらって、今の位置がその辞書にあれば相応のインスタンスを渡してあげるという形。

んん…でもこれならもういっそこうした方が…

from abc import ABCMeta, abstractmethod

class AbstractBoard(metaclass=ABCMeta):

    def __init__(self, rows, columns):
        self.rows = rows
        self.columns = columns
        self.board = [[None for _ in range(columns)] for _ in range(rows)]
        self.populate_board()

    def populate_board(self):
        for row in range(self.rows):
            for column in range(self.columns):
                self.board[row][column] = self.create_piece(row, column)

    def create_piece(self, row, column):
        piece_dict = self.initial_piece_map()
        name_dict = self.get_name_dict()
        if (row, column) in piece_dict:
            kind, color = piece_dict[(row, column)]
            name = name_dict[kind]
            return globals()[color + name]()
        else:
            return None

    @abstractmethod
    @staticmethod
    def initial_piece_map():
        pass

    @abstractmethod
    @staticmethod
    def get_name_dict():
        pass

    def __str__(self):
        ...


class ChessBoard(AbstractBoard):

    def __init__(self):
        super().__init__(8, 8)

    @staticmethod
    def get_name_dict():
        name_dict = {PAWN: "ChessPawn", ROOK: "ChessRook",
                     KNIGHT: "ChessKnight", BISHOP: "ChessBishop",
                     KING: "ChessKing", QUEEN: "ChessQueen"}
        return name_dict

    @staticmethod
    def initial_piece_map():
        colors = (((0, 1), BLACK), ((6, 7), WHITE))
        kinds = (((0, 7), ROOK), ((1, 6), KNIGHT), ((2, 5), BISHOP),
                 ((3,), QUEEN), ((4,), KING), ((0, 1, 2, 3, 4, 5, 6, 7), PAWN))
        piece_dict = {(row, column): (kind, color)
                      for row, color in colors for column, kind in kinds}
        return piece_dict

今回のテーマだと本質的に差異が生じるところって「どこにどんな駒を置くのか」なので、それを具象クラスで決めるという風にしたほうが簡潔そう…。特に、駒の位置情報を司るinitial_piece_map が分離するおかげで、外部ファイルからの読み込みに変更するのも簡単。

ただ、これが factory method パティーンなのかと言われると……。そもそもこのテーマ設定がファクトリーメソッドパティーンに向いてないのかしら。

しかし考えてみれば、population の仕方を抽象メソッドで固定してしまうというのはハードコーディングな気もする。population の仕方は同書のやり方もあるし、おかゆのやり方もあるわけで(たとえばおかゆのやり方は、列真ん中付近の無駄なところまで走査してるので、パフォーマンスが悪い)。そうなると、やはり @abstractmethod にするのは populate_board にしておくべきなのだろうか…。

いや、でも、仮にそうだったとしても、populate_board が factory method とは思えない。こいつは別に anOperation (この状況でいえば__init__) に対してクラス差異的にオブジェクトを生成してるわけではなくて、個別クラス特有的にインスタンスの内部状態を変更してるだけ。単なる素朴なオーバーライドにしか見えない。

もやもやする

追記

2016-11-29-15:20

えーっと。factory パターンというのがあるのを知った。理解によれば、「factoryクラスに指示(引数)を与えて、それに見合ったオブジェクトを持ってきてくれるというクールなやつをつくる」パティーンのことらしい。言い方としては「指示に見合ったクラスのインスタンスを作り出す」といったほうがいいか(関数でよくない…?シンプルなファクトリなら関数でもできる(create_pieceがまさにそう)けど、ファクトリの内部状態によって指示への従い方を変えていく…みたいなことをするなら確かにクラスである必要がありそう(でもファクトリってそんな複雑なことする(していい)のかな))。

個人的には router みも感じる。

これを踏まえると、なるほど、create_piece は確かに「ファクトリー関数」ではあっても factory method ではない気がしてきた。

じゃあ、問題はどれがanOperation で、factory method なのか。

サルでもわかる 逆引きデザインパターン 第2章 逆引きカタログ ロジック編 Factory/Factory Method(ファクトリ/ファクトリメソッド) をみる。

それでは、ファクトリパターンとファクトリメソッドパターンの違いは何でしょうか?

ファクトリパターンは、オブジェクトの生成処理だけでなく、どのオブジェクトを生成するかの判断もオブジェクトェクトの使用者から隠してくれるパターンです。 ファクトリパターンでは、生成するオブジェクトの種類の変更をファクトリの処理の中で動的に行います。

しかし、ファクトリパターンでは、生成するオブジェクトの種類が増えたり、生成処理手順が複雑化した場合、ファクトリ内の処理が冗長で複雑になってしまいます。 そこで登場するのがファクトリメソッドパターンです。

せやな。続き:

ファクトリメソッドパターンでは、生成するオブジェクトごとにファクトリを用意し、ファクトリに対して共通のスーパークラスを設けることで、オブジェクトの生成処理を柔軟に行います。 スーパークラスでは、オブジェクト生成に共通な処理の実装と、オブジェクトのnewを行うメソッドやオブジェクト固有の生成手順を抽象メソッドとして定義します。 サブクラスでは、抽象メソッドの実装(オブジェクトのnew)を行うだけです。 オブジェクトの生成はサブクラスで行い、サブクラスでは生成処理の差分のみを実装すればよいので、生成処理を簡略化できます(注4)。

ファクトリメソッドパターンは、1つのファクトリは1つのオブジェクトの生成のみを行うため、生成するオブジェクトの種類の変更を行う場合、ファクトリクラスを切り替える必要があります。 ファクトリメソッドパターンでは多くの場合、オブジェクトの使用者はファクトリのスーパークラスを使用します。 そしてファクトリ指定は、オブジェクト使用者の生成時にコンストラクタで渡したり、ファクトリ設定用メソッドを設けるなどの手段が必要になります。

やっぱりとにかくファクトリメソッドパターンというのは

  • 共通処理は抽象クラスで
  • 差分処理(たいていコンストラクタの指定)は具象クラスで

だと思うんだよな。で、ファクトリである以上、多分、クライアントに渡されるのはプロダクト(を加工したもの)であるべきで、さらにそのプロダクトというのはファクトリーメソッドによって可変的に持ってきたもの。

と考えたときに、やっぱり「実践Python3」のゲームボードの例は気持ちが悪い…。

なんか、もしかして「ファクトリ関数を中に含んだメソッド」をファクトリメソッドと呼んでるんじゃないかコイツ?という感がしてきた。

あ~ もやもやする

追記

16:50

これを Factory method パターンと理解しようとするのを諦めた。

で、こういうの見つけた

doloopwhile.hatenablog.com

あまりにも自由度が高くなってきたらクラスを引数で渡してやれっていう…えぇ…いやそりゃそうするけど……

なんか基本的に批判的な目で読んだほうがいいんかも分からんね(まあ元々がC++デザインパターンだし仕方ないけども)

python でリストのパターンマッチっぽい何か

「実践Python3」のコラム見て、ハッと閃いたのでメモ。(Python3.5)

シーケンスのアンパックは引数定義のところでよく使われますね。

def func(*args, **keywords):
  pass

みたいな感じで。アンパックはまだ使いみちがあって、入れ子になったシーケンスの内側にあるシーケンスをアンパックして平たくできます。

[1, 2, 3, *[4, 5, 6]]
# >> [1, 2, 3, 4, 5, 6]

ところで、Pythonではアンパック代入というのができて、・・・要はタプルの内部に変数を置けます(パターンマッチ的な代入?なんて言うのか分からない)

tpl = (1, 2, 3)
a, b, c = tpl # (a, b, c) = tpl に同じ
assert a == 1 and b == 2 and c == 3

で、このアンパック代入、対タプルならイミュータブルなので割と使えますが、ミュータブルなシーケンス(リストとか)だとふつうの使い方では使い勝手が悪い。

ここで*の出番です。

seq = [1, 2, 3, 4, 5]
x, *xs = seq
assert x == 1
assert xs == [2, 3, 4, 5]

アンパック代入時、最後に*を付した変数を入れておけば、余った部分は全部そこに収容されるので、多い日もあんしん!((少ないときはifして))

で、これ使うと Haskell みたいなリストの取り扱い方(x:xsみたいなの)ができそうだと思って、「すごいH本」4章の実装練習を Python でやってみました。

from typing import List, TypeVar, Tuple, Sequence, Any
from abc import ABCMeta, abstractmethod

T = TypeVar('T')
S = TypeVar('S')
CT = TypeVar('CT', bound=Comparable)

class Comparable(metaclass=ABCMeta):
    @abstractmethod
    def __lt__(self, other: Any) -> bool: pass
    @abstractmethod
    def __gt__(self, other: Any) -> bool: pass
    def __le__(self, other: Any) -> bool:
        return not self > other
    def __ge__(self, other: Any) -> bool:
        return not self < other

def sum_(xs: Sequence[float]) -> float:
    if xs:
        x, *xs = xs
        return x + sum(xs)
    else:
        return 0

def maximum_(xs: Sequence[T]) -> T:
    if xs:
        x, *xs = xs
        if xs:
            return max(x, maximum_(xs))
        else:
            return x
    else:
        raise ValueError("maximum of empty list!")

def take_(n: int, xs: Sequence[T]) -> List[T]:
    if n >= 0 and xs:
        x, *xs = xs
        return [x] + take_(n-1, xs)
    else:
        return []

def reverse_(xs: Sequence[T]) -> List[T]:
    if xs:
        x, *xs = xs
        return reverse_(xs) + [x]
    else:
        return []

def zip_(xs: Sequence[S], ys: Sequence[T]) -> List[Tuple[S, T]]:
    if xs and ys:
        x, *xs = xs
        y, *ys = ys
        return [(x, y)] + zip_(xs, ys)
    else:
        return []

def elem_(a: T, xs: List[T]) -> bool:
    if xs:
        x, *xs = xs
        if a == x:
            return True
        else:
            return elem_(a, xs)
    else:
        return False

def quicksort_(xs: List[CT]) -> List[CT]:
    if xs:
        x, *xs = xs
        smaller_or_equal = [a for a in xs if a <= x]
        larger = [a for a in xs if a > x]
        return quicksort_(smaller_or_equal) + [x] + quicksort_(larger)
    else:
        return []

という感じで。え? pop使えって?うるせーニシキヘビぶつけんぞ。とはいえ確かにジェネレータとかに対しては不向き(特に無限イテレータ)ですね。

とはいえ、ジェネレータで実装する場合はそもそも上のような書き方をしないのでは、とも思うので、もっぱらリスト向きの(トイな)書き方ではある。

ジェネレータで再帰するならyield fromを使うとよさそう

from typing import Iterable, Iterator

def reverse_2(xs: Iterable[T]) -> Iterator[T]:
    xs = iter(xs)
    x = next(xs)
    yield from reverse_2(xs)
    yield x

assert list(reverse_2(range(5))) == [4, 3, 2, 1, 0]

一周してジェネレータを積極的に使いたみというオチになってしまった。ちなみに、遅延評価の Haskell なら簡単にかけるrepeatPython ではジェネレータで書けます。

def repeat(x: T) -> Iterator[T]:
    yield x
    yield from repeat(x)

ジェネレータ便利やね。(全く同じ形式で、yield from repeat(x+1) とすればカウンターになるのも面白い)

ジェネレータ、ループにはwhile使うのが常套手段っぽいけど、再帰のほうがなんかかっこよくていいですよね(?)

P.S.

ところで、Comparable の抽象基底クラスってなんでないんやろ

__getattribute__とsuperのメモ

ディスクリプタについて勉強してから10か月くらい前か~

iuk.hateblo.jp

qiita.com

このQiita記事が公式のHowtoディスクリプタよりも詳しい&分かりやすくてよきよき。特に__getattribute__の中身の擬似コードあるのがいいよね。

Effective Python にはディスクリプタ使おうとして __getattribute__ のぬかるみにハマるなよ!って書いてあるけど、知らないに越したことないでしょ。良質なドキュメントさえあれば理解に難くもないし。

とりあえず、インスタンスの属性アクセスのときに使われる__getattribute__(objectクラスのやつ)

  1. 当該属性名をクラス属性(スーパークラス含め)から探し、それがデータディスクリプタなら__get__(obj, type(obj))呼び出し
  2. インスタンスの属性辞書(__dict__)から当該属性名を探し、あればそれをそのまま返す
  3. 1.の非データディスクリプタ
  4. エラー

次に、クラスの属性アクセスのときに使われる__getattribute__(typeクラスのやつ)*1

  1. 当該属性名をメタクラス属性から探して、それがデータディスクリプタなら__get__(cls, type(cls))呼び出し
  2. クラス(スーパークラス含め)の属性辞書から当該属性名を探し、それがディスクリプタなら__get__(None, cls)呼び出し、違うならそのまま返す。
  3. 1.の非データディスクリプタ
  4. エラー

多分、setの方もこれと同じだと思う。

で、あとは各々のディスクリプタ__get____set__が色々してくれる。おわり!

すーぱー

superクラスはまた別の__getattribute__を持っているらしい。

2. 組み込み関数 — Python 3.5.2 ドキュメント

これも上のリンクから擬似コードをもってきますと、

def super_getattribute(su, key):
    "Emulate super_getattro() in Objects/typeobject.c"
    starttype = su.__self_class__
    mro = iter(starttype.__mro__)
    for cls in mro:
        if cls is su.__self_class__:
            break
    # Note: mro is an iterator, so the second loop
    # picks up where the first one left off!
    for cls in mro:
        if key in cls.__dict__:
            attr = cls.__dict__[key]
            if hasattr(attr, '__get__'):
                return attr.__get__(su.__self__, starttype)
            return attr
    raise AttributeError

メソッド内で呼び出した場合は、superを呼び出したクラスの__mro__自分自身以降の属性辞書を探索して、それがディスクリプタ(大抵は__init__とか上位クラスのメソッドを呼ぶわけで、つまり大体の場合でディスクリプタ)なら、呼び出したところのインスタンスとクラスで__get__を呼び出す(__init__selfに呼び出した時点のインスタンスを束縛したバウンドメソッドを返すわけですな)。

もちろん、親メソッドを連鎖させるためには、それぞれのクラスがsuperで親クラスを呼び出していくことで実現する。

なんか Python2 だとあんまいい感じに動かない仕様だったらしく、Python3 使っててよかった~って感じだ。

ところで、さっきの擬似コードと少し挙動の異なるところを見つけたのでそれを調べる。確かめたいのは次の2つ:

  • __get__ の引数はこれで本当にあってるのか
  • superが第1引数に取ったクラスはどこで使われてるのか(上の擬似コードだとこれは不使用)
class Descriptor():
    def __init__(self, value):
        self.value = value

    def __get__(self, obj, obj_cls=None):
        return (obj, obj_cls, self.value)


class C():
    def __init__(self):
        super().__init__()

    x = Descriptor("C")


class D(C):
    def __init__(self):
        super().__init__()

    x = Descriptor("D")


class D2(C):
    def __init__(self):
        super().__init__()

    x = Descriptor("D2")


class E(D, D2):
    def __init__(self):
        su = super()
        su.__init__()

    x = Descriptor("E")


class F(E):
    def __init__(self):
        super().__init__()

    x = Descriptor("F")

if __name__ == '__main__':
    print("F mro :", F.__mro__)
    print("E mro :", E.__mro__)
    print("D mro :", D.__mro__)
    print("D2 mro :", D2.__mro__)
    print("C mro :", C.__mro__)
    print()
    print("F() :", F().x)
    print("super(F, F()) :", super(F, F()).x)
    print("super(E, F()) :", super(E, F()).x)
    print("super(D, F()) :", super(D, F()).x)
    print("super(D2, F()) :", super(D2, F()).x)
> python func_annotations.py
F mro : (<class '__main__.F'>, <class '__main__.E'>, <class '__main__.D'>, <class '__main__.D2'>, <class '__main__.C'>, <class 'object'>)
E mro : (<class '__main__.E'>, <class '__main__.D'>, <class '__main__.D2'>, <class '__main__.C'>, <class 'object'>)
D mro : (<class '__main__.D'>, <class '__main__.C'>, <class 'object'>)
D2 mro : (<class '__main__.D2'>, <class '__main__.C'>, <class 'object'>)
C mro : (<class '__main__.C'>, <class 'object'>)

F() : (<__main__.F object at 0x000001C4756C4470>, <class '__main__.F'>, 'F')
super(F, F()) : (<__main__.F object at 0x000001C4756C4470>, <class '__main__.F'>, 'E')
super(E, F()) : (<__main__.F object at 0x000001C4756C4470>, <class '__main__.F'>, 'D')
super(D, F()) : (<__main__.F object at 0x000001C4756C4470>, <class '__main__.F'>, 'D2')
super(D2, F()) : (<__main__.F object at 0x000001C4756C4470>, <class '__main__.F'>, 'C')

すべてのクラスが非データディスクリプタであるxをもっています。super第二引数は最下層のF()に固定して、第一引数を色々動かして、xにアクセスしてみました。もちろん、isinstance(F(), cls)の範囲内で。

Descriptorは見てもらえばその通りですが、__get__の引数と、ディスクリプタ初期化の際に入れた文字列(それぞれのクラス名にしてある)のタプルを返してくれます。

まず、擬似コードにおける__get__の引数は間違ってないようです。それぞれ obj, obj_cls に渡されるのは、super第二引数のオブジェクトsu.__self__と、そのクラスsu.__self_class__です。

一方で、呼び出されているディスクリプタsuper第一引数によって変化しています!第一引数とディスクリプタの所属するクラスを比べてみると、どうやらsuper第一引数は擬似コードにおけるstarttypeに相当しそうです(もちろん、__get__の第二引数にはもはやstarttypeは使えず、su.__self_class__を代入するように書き換える必要がありますが)。つまり、属性辞書の探索は、第二引数オブジェクトのクラス以降ではなく、super第一引数以降ということになりそうです。

というわけで、変更すべき箇所はこのあたりです:

def super_getattribute(su, key):
    "Emulate super_getattro() in Objects/typeobject.c"
    starttype = su.__self_class__
    mro = iter(starttype.__mro__)
    for cls in mro:
        if cls is su.__self_class__:
            break

superオブジェクトがどんな属性を持っているのか、日本語のドキュメントが見つからなくてムムムしていましたが、どうやらsuper第一引数は__thisclass__に収納されてるようです。よって、まず、starttype = su.__thisclass__とすべきです。

さらに、__thisclass__starttype とした場合、mro についても変更すべきです。属性辞書の探索はmroに従って行われるわけですが、もしmrostarttype.__mro__を元につくられるとしたら、super(D, F()).x(下から2行目)がD2のディスクリプタに辿り着くことは不可能だからです(D.__mro__参考)。というわけで、mro = iter(su.__self_class__.__mro__) とすべきです。

さらに、イテレータの巻き上げは、su.__self_class__ ではなく、 starttype で止めるべきですね。

というわけで、恐らくより正しい擬似コードは以下の通りです:

def super_getattribute(su, key):
    "Emulate super_getattro() in Objects/typeobject.c .. and revised by iuk."
    starttype = su.__thisclass__
    mro = iter(su.__self_class__.__mro__)
    for cls in mro:
        if cls is starttype:
            break
    # Note: mro is an iterator, so the second loop
    # picks up where the first one left off!
    for cls in mro:
        if key in cls.__dict__:
            attr = cls.__dict__[key]
            if hasattr(attr, '__get__'):
                return attr.__get__(su.__self__, su.__self_class__)
            return attr
    raise AttributeError

super第一引数は mro の巻き上げに使われてたってことですね。

というわけで、super のもう少し詳しい挙動が分かりました。

super(type, obj)の属性アクセスは、objのクラスの__mro__に従って、type以降(typeは含まない)のクラスの属性辞書を順に探索し、それがディスクリプタなら__get__(obj, obj.__class__) を呼び出し、ディスクリプタでないならそのまま返す。

まあディスクリプタのあたりの挙動は他と特に変わらないですね。ちなみにsuper(fromtype, type)の属性アクセスの場合は __get__(None, type) になります。

*1:オブジェクトの属性アクセスはそのオブジェクトのクラスの__getattribute__に支配されるわけで、つまりオブジェクトがクラスの場合はメタクラスの__getattribute__。

関数がディスクリプタであることを利用してインスタンスに動的にメソッドを追加する

だからタイトル長くない?

kk6.hateblo.jp

types.MethodType 使えば個々のインスタンスにメソッドを追加できるんだけど、typesをわざわざ import するのもなって感じにもなる。

で、

qiita.com

を読んでたら、関数オブジェクト(こいつは非データディスクリプタ)の__get__内でtypes.MethodTypeがよしなに呼び出されているのを見つけたので、これを使えばいいなと思いました。

class Klass():
    def __init__(self):
        self.value = 10


def meth(self, value):
    self.value += value
    return self.value


if __name__ == '__main__':
    c = Klass()
    c.meth = meth.__get__(c)
    print(c.value) # => 10
    print(c.meth(5)) # => 15
    print(c.__dict__) # => {'meth': <bound method meth of <__main__.Klass object at 0x000001994F40D9E8>>, 'value': 15}

ちょっと面白いのは、当たり前だけど、こうやって追加すると、クラスに定義されたメソッドではないから、インスタンスの属性辞書に直接メソッド(バウンドメソッド)が登録されるってことですね。まあ逆に言えば、いつもはインスタンスの属性辞書にメソッドは登録されておらず、あくまでクラスの属性を引っ張ってきてる(そのときにディスクリプタによってselfがバウンドされる)ってことですね。先にバウンドしておくか、呼び出し時にその都度バウンドしてもらうかってところが異なります。

ところで、メソッド(バウンドメソッドも)は新たに属性を加えられないっぽいですが、関数オブジェクトには色々属性を追加できます。

    Klass.hoge.atr = 100
    print(Klass.hoge.atr)
    print(c.hoge.atr) # => 100

クラスでメソッドを定義した場合、クラスからアクセスすればそれはただの関数オブジェクトなので、上の例みたいに新たに属性を追加できますが、インスタンスにバウンドメソッドをもたせた場合はそういうことができません(まあそんな使う性質でもないし、ほとんど差はないって思っていいとは思うけど)。

propertyはデータディスクリプタだからインスタンス属性辞書より優先される

タイトル長くない?

class C():
    def __init__(self):
        self.x = 10

    @property
    def x(self):
        return self.__dict__["x"]

    @x.setter
    def x(self, value):
        print("eh-eh")
        self.__dict__["x"] = value

データディスクリプタインスタンス属性辞書よりもアクセスが優先されるので、上のように書いてもC().xC().__dict__["x"]ではなく、type(obj).__dict__["x"].__get__(obj, type(obj)) に繋がる表現になってくれる。

(単純なフックとして使われてる(?))プロパティ内で用いる属性変数は大抵アンダースコア1つ付けたものにするけど、それだと直接その変数に(比較的簡便に)アクセスできちゃうし、こっちのほうがより隠されてる感はある(単純な属性アクセスではディスクリプタが優先され、無理やりアクセスしようとする場合には__dict__直接覗かないといけないという意味で)

__new__ がわからん

メタクラス関連で__new__の使い方はよくみるけど、クラスレベルでの使い方がぐぐってもあんまりなし。

というところに、以下の記事のコメントでやってることが面白かったので、似たことを検証がてらメモ。

qiita.com

class A():
    def __new__(cls, value):
        print("generate by A.__new__")
        cls.X = 50
        return object.__new__(cls)

    def __init__(self, value):
        self.a = value

    def hello(self):
        return 'hello'


class B():
    def __new__(cls, value):
        return A.__new__(cls, value)

    def __init__(self, value):
        self.b = value


class C():
    def __new__(cls, value):
        return A(value)

    def __init__(self, value):
        self.c = value


if __name__ == '__main__':
    b = B(10)
    print("type of b :", type(b))
    print("b has a? :", hasattr(b, "a"))
    print("b has b? :", hasattr(b, "b"))
    print("B has X? :", hasattr(B, "X"))
    print("b.__dict__ :", b.__dict__)
    c = C(0)
    print("type of c :", type(c))
    print("c has a? :", hasattr(c, "a"))
    print("c has c? :", hasattr(c, "c"))
    print("c.__dict__ :", c.__dict__)
> python new_with_other.py
generate by A.__new__
type of b : <class '__main__.B'>
b has a? : False
b has b? : True
B has X? : True
b.__dict__ : {'b': 10}
generate by A.__new__
type of c : <class '__main__.A'>
c has a? : True
c has c? : False
c.__dict__ : {'a': 0}

BとCは__new__A.__new__(cls, value)で返すか、A(value)で返すかの違い。

クラスを呼び出してインスタンスを生成すると、__new__が呼ばれ、インスタンスが作られてから __init__ で初期化されるというのが典型的な流れ。

まず、b。こいつはBインスタンスで、この際呼ばれた__new__(cls, value) では、clsB が入ってるわけなので、たとえA.__new__を使おうと、Bを使ってインスタンスを作ろうとしていることに変わりはない。

持ってるアトリビュートを見る限り、呼び出したクラスの__init__だけが呼び出されている模様(つまり今回 A の __init__ は呼び出されていない)。まあこれも当然っちゃあ当然か。一方で、A.__new__ ではクラスのアトリビュート X が加えられるんだけど、そのおかげで B.X が存在する。まあこれも当然。

b.__dict__ みると分かる通り、基本的にメソッドはクラスのもんで、インスタンスから呼び出す場合も、クラスのアトリビュートとしてその関数が探索され(そして、selfインスタンスが束縛され)るわけなので、Aのコンストラクタを使ったからといって、Aのメソッドがそのインスタンスに直接登録されるわけではない(よって、b.hello()はエラーが起きる)。

続いて c。こっちは __new__の返り値で A(value)、Aクラスのインスタンスを返している。ので、クラスCの呼び出しをしたのに返ってくるインスタンスの生成元クラスはAという変なことになっている。興味深いのはCの__init__が呼び出されていないというところですかね。

で、これはなんでかというと、3. データモデル — Python 3.5.2 ドキュメント にある通りで、

__new__() が cls のインスタンスを返さない場合、インスタンスの __init__() メソッドは呼び出されません。

わけですな。だので、ちゃんとそのクラス独自の初期化をしたい場合は、object.__new__(cls, ...) なんなり、他の__new__を使ってとにかくclsインスタンスを作って返すというのが必要そう。

__new__()clsインスタンスを返さないような場合(Noneはアホらしいので除くとして)ってどういうときに使えるんだろ…?

ごく平凡な1つ目の案は、実体のあるエイリアス。Aのインスタンスを作るときに、C()が使える。*1

あるいは、Aの呼び出しに関してなにかprintとかフックしたいとき*2。クラスCになにかクラス変数をもたせておいて、それを使ってなにかフックしたりできそう*3

あるいは、ディスパッチ*4

なんか何を考えても大体「関数でよくない?」になってしまう…。

あくまで「インスタンス生成」だということを主張しつつ、でもそれ自身のインスタンスじゃないことにそこまで違和感をもたれないような使い方か…。たとえば、A がファイルオブジェクトを引数にとるけど、Cではfilenameを引数にとって、コンストラクタ内でファイルオブジェクトをつくって、それを使ってAインスタンスを生成して返してもらう。とか。

class Hoge():
    def __init__(self, file):
        # ..

class HogeWithFilename():
    def __new__(self, filename):
        with open(filename) as f:
            return Hoge(f)

みたいな。いやどうなんだ・・・・・・。

インスタンス生成しないクラス

ところで、インスタンス生成しないクラスというのをつくるときには __new__ が必要そうだ*5

class InstantiateError(Exception):
    pass

class DontInit():
    def __init__(self):
        raise InstantiateError


if __name__ == '__main__':
    try:
        DontInit()
    except InstantiateError:
        print("eh, don't instantiate.")
    d = DontInit.__new__(DontInit)
    print("type of d :", type(d))
eh, don't instantiate.
type of d : <class '__main__.DontInit'>

これは、__init__時点で例外吐いていて、一見インスタンス生成不可に見えますが、__new__ が生き残ってるので無理やり DontIntインスタンスを作ることができてしまう。ので、本当にインスタンスを作りたくなければ、__new__ で例外吐くしかない。

class DontInitAtAll():
    def __new__(cls):
        raise InstantiateError


if __name__ == '__main__':
    try:
        DontInitAtAll()
    except InstantiateError:
        print("eh, don't instantiate.")
    try:
        d = DontInitAtAll.__new__(DontInitAtAll)
    except:
        print("oh, can't really instantiate")
eh, don't instantiate.
oh, can't really instantiate

たとえば、MixInクラス(ふつうこいつのインスタンスを作ろうとは思わんよね?)であることを明示するために、こういう処置をするのはいいような気もする。

class MixIn():
    def __new__(cls):
        raise InstantiateError

という MixIn 基底クラスを用意しておいて、MixInクラスはこれを継承していけば、結構いい感じにいい感じなのでは?と思ったけど、__new__ も継承されるから、MixIn先のクラスの__new__をきちんとオーバーライドしてやらないといけないことになるから面倒か(そもそも __init__ がないって時点で MixInクラスか?ってなるだろうし、有用じゃなさが上回る…)

ううん

やっぱようわからんね

*1:関数でよくない?

*2:関数でよくない?

*3:これは関数でやるならクロージャになる。

*4:関数でよくない?

*5:そういうクラスが必要になるかはともかく

メタクラスで@propertyを使ってクラスプロパティをつくる

クラスプロパティってできんのかな~と思って調べていた。

fakatatuku.hatenablog.com

なるほどな~と思いつつ、この記事を読み返していた。

postd.cc

プロパティって(というかディスクリプタって)、インスタンスの生成元クラスのアトリビュートとして登録しておいて使うわけだから、クラスプロパティはクラスの生成元クラスにプロパティとして登録すればいいのでは?そんで、クラスの生成元クラスというのはつまりメタクラスでは?

というわけで、typeメタクラスのサブクラスにプロパティを登録して、それをメタクラスとしたクラスを作れば、その子は登録したプロパティが使えるはずや!

読み込み専用のクラスプロパティを作ってみまふ。

class PropertyXMeta(type):

    @property
    def x(self):
        return 0

    @x.setter
    def x(self, value):
        raise AttributeError


class Klass(metaclass=PropertyXMeta):
    pass

if __name__ == '__main__':
    print(Klass.x)
    try:
        Klass.x = 5
    except AttributeError:
        print("uh-huh, nice error.")

結果。

0
uh-huh, nice error.

Klass.x に代入しようとするとAttributeErrorが出るようになります。ガチもんの定数になりますね!(そこまでして変更不能にしたいかどうかはともかく)

上で見たような、@ClassPropertyといったデコレータをクラス内部に書いて使えるようにしたもののほうが実用的だとは思います。

クラスがメタクラスインスタンスだってことを改めて知れた実装でした。