最終的に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__
その使用法を完全に理解するには?
__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__
は属性としての記述子のインスタンスを持つクラスにメタクラスを使用します。
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
これにより、メタクラスやデコレータを使用することなく、特定のリポジトリのハンドラクラスを簡単に定義できます。
__init_subclass__
の主なポイントは、PEPのタイトルが示唆するように、クラスのカスタマイズのより単純な形式を提供することでした。
これは、メタクラスについて知る必要のないクラスをいじったり、クラス構築のすべての側面を追跡したり、将来的にメタクラスの競合を心配したりすることができるフックです。 メッセージ このPEPの初期段階でのニックコグランによる:
読みやすさ/保守性の主な目的は、「サブクラスのカスタマイズ初期化」ケースと「サブクラスのランタイム動作のカスタマイズ」ケースをより明確に区別する観点からです。
完全なカスタムメタクラスは影響の範囲を示しませんが、
__init_subclass__
はサブクラス作成後の動作に永続的な影響がないことをより明確に示します。
メタクラスは魔法と見なされますが、クラスが作成された後のメタクラスの効果はわかりません。一方、__init_subclass__
は単なる別のクラスメソッドであり、一度実行されてから完了します。 (正確な機能についてはそのドキュメントを参照してください。)
PEP 487の全体的なポイントは、いくつかの一般的な用途のためにメタクラスを単純化する(つまり、使用する必要をなくす)ことです。
__init_subclass__
はクラス後の初期化を処理し、__set_name__
(記述子クラスに対してのみ意味をなす)は記述子の初期化を簡素化するために追加されました。それを超えて、それらは関連していません。
言及されているメタクラスの3番目の一般的なケース(定義の順序を保持) これも簡略化されました 。これは、名前空間の順序マッピングを使用して、フックなしで対処されました(Python 3.6ではdict
ですが、これは実装の詳細です:-)