web-dev-qa-db-ja.com

__init_subclass__を理解する

最終的にpythonバージョンをアップグレードし、追加された新機能を発見していました。とりわけ、新しい __init_subclass__ メソッド。ドキュメントから:

このメソッドは、包含クラスがサブクラス化されるたびに呼び出されます。その場合、clsは新しいサブクラスです。通常のインスタンスメソッドとして定義されている場合、このメソッドは暗黙的にクラスメソッドに変換されます。

それで、ドキュメントの例に従って、少し遊んでみました:

class Philosopher:
    def __init_subclass__(cls, default_name, **kwargs):
        super().__init_subclass__(**kwargs)
        print(f"Called __init_subclass({cls}, {default_name})")
        cls.default_name = default_name

class AustralianPhilosopher(Philosopher, default_name="Bruce"):
    pass

class GermanPhilosopher(Philosopher, default_name="Nietzsche"):
    default_name = "Hegel"
    print("Set name to Hegel")

Bruce = AustralianPhilosopher()
Mistery = GermanPhilosopher()
print(Bruce.default_name)
print(Mistery.default_name)

次の出力を生成します。

Called __init_subclass(<class '__main__.AustralianPhilosopher'>, 'Bruce')
'Set name to Hegel'
Called __init_subclass(<class '__main__.GermanPhilosopher'>, 'Nietzsche')
'Bruce'
'Nietzsche'

このメソッドはサブクラス定義の後でと呼ばれることを理解していますが、私の質問はこの機能の使用に関するものです。 PEP 487 の記事も読みましたが、あまり助けにはなりませんでした。この方法はどこで役立ちますか?以下のためですか?

  • 作成時にサブクラスを登録するスーパークラス?
  • 定義時にサブクラスにフィールドを設定させる

また、 __set_name__ その使用法を完全に理解するには?

34
EsotericVoid

__init_subclass____set_name__は直交メカニズムです-これらは互いに結び付けられておらず、同じPEPで説明されています。どちらも以前はフル機能のメタクラスを必要としていた機能です。 PEP 487は、メタクラスの最も一般的な使用法の2に対応しています。

  • サブクラス化されたときに親に通知する方法(__init_subclass__
  • 記述子クラスに使用されるプロパティの名前を知らせる方法(__set_name__

PEPが言うように:

メタクラスを使用する方法は多数ありますが、大部分のユースケースは、3つのカテゴリに分類されます。クラス作成後に実行される初期化コード、記述子の初期化、およびクラス属性が定義されました。

最初の2つのカテゴリは、クラス作成への単純なフックによって簡単に実現できます。

  • 特定のクラスのすべてのサブクラスを初期化する__init_subclass__フック。
  • クラスの作成時に、クラスで定義されているすべての属性(記述子)で__set_name__フックが呼び出されます。

3番目のカテゴリは、別のPEPのトピック PEP 52 です。

また、__init_subclass__thisクラスの継承ツリーでメタクラスを使用する代わりになりますが、descriptor class__set_name__属性としての記述子のインスタンスを持つクラスにメタクラスを使用します。

21
Antti Haapala

PEP 487は、2つの一般的なメタクラスのユースケースを採用し、メタクラスのすべてのインとアウトを理解する必要なく、よりアクセスしやすくすることを目指しています。 2つの新機能、__init_subclass__および__set_name__はそれ以外は独立であり、互いに依存しません。

__init_subclass__は単なるフックメソッドです。好きなものに使用できます。何らかの方法でサブクラスを登録するのに役立ちます。これらのサブクラスにデフォルトの属性値を設定するにはandを使用します。

最近、これを使用して、さまざまなバージョン管理システムに「アダプター」を提供しました。例:

class RepositoryType(Enum):
    HG = auto()
    GIT = auto()
    SVN = auto()
    PERFORCE = auto()

class Repository():
    _registry = {t: {} for t in RepositoryType}

    def __init_subclass__(cls, scm_type=None, name=None, **kwargs):
        super().__init_subclass__(**kwargs)
        if scm_type is not None:
            cls._registry[scm_type][name] = cls

class MainHgRepository(Repository, scm_type=RepositoryType.HG, name='main'):
    pass

class GenericGitRepository(Repository, scm_type=RepositoryType.GIT):
    pass

これにより、メタクラスやデコレータを使用することなく、特定のリポジトリのハンドラクラスを簡単に定義できます。

24
Martijn Pieters

__init_subclass__の主なポイントは、PEPのタイトルが示唆するように、クラスのカスタマイズのより単純な形式を提供することでした。

これは、メタクラスについて知る必要のないクラスをいじったり、クラス構築のすべての側面を追跡したり、将来的にメタクラスの競合を心配したりすることができるフックです。 メッセージ このPEPの初期段階でのニックコグランによる:

読みやすさ/保守性の主な目的は、「サブクラスのカスタマイズ初期化」ケースと「サブクラスのランタイム動作のカスタマイズ」ケースをより明確に区別する観点からです。

完全なカスタムメタクラスは影響の範囲を示しませんが、__init_subclass__はサブクラス作成後の動作に永続的な影響がないことをより明確に示します。

メタクラスは魔法と見なされますが、クラスが作成された後のメタクラスの効果はわかりません。一方、__init_subclass__は単なる別のクラスメソッドであり、一度実行されてから完了します。 (正確な機能についてはそのドキュメントを参照してください。)


PEP 487の全体的なポイントは、いくつかの一般的な用途のためにメタクラスを単純化する(つまり、使用する必要をなくす)ことです。

__init_subclass__はクラス後の初期化を処理し、__set_name__(記述子クラスに対してのみ意味をなす)は記述子の初期化を簡素化するために追加されました。それを超えて、それらは関連していません。

言及されているメタクラスの3番目の一般的なケース(定義の順序を保持) これも簡略化されました 。これは、名前空間の順序マッピングを使用して、フックなしで対処されました(Python 3.6ではdictですが、これは実装の詳細です:-)

役に立つかもしれないメタクラスと__init_subclass__に関連するいくつかの参照を追加したいと思います。

背景

__init_subclass__は、メタクラスを作成する代わりに導入されました。コア開発者の1人であるBrett Cannonによる トークPEP 487 の2分間の概要を以下に示します。

推奨参考文献

  • Pythonのメタクラスの初期の歴史に関するGuido van Rossumの ブログ投稿
  • Jake Vanderplasの ブログ投稿 メタクラスの実装についてさらに詳しく調べる
1
pylang