クラスの定義時にクラスのインスタンスを登録したいのですが。理想的には、以下のコードでうまくいくでしょう。
registry = {}
def register( cls ):
registry[cls.__name__] = cls() #problem here
return cls
@register
class MyClass( Base ):
def __init__(self):
super( MyClass, self ).__init__()
残念ながら、このコードはエラーNameError: global name 'MyClass' is not defined
を生成します。
MyClass
をインスタンス化しようとしている#problem here
行で起こっていますが、デコレータがまだ返されていないため、存在しません。
メタクラスなどを使ってこれの周りに何かありますか?
はい、メタクラスはこれを行うことができます。メタクラスの___new__
_メソッドはクラスを返すので、返す前にそのクラスを登録するだけです。
_class MetaClass(type):
def __new__(cls, clsname, bases, attrs):
newclass = super(MetaClass, cls).__new__(cls, clsname, bases, attrs)
register(newclass) # here is your register function
return newclass
class MyClass(object):
__metaclass__ = MetaClass
_
前の例は、Python 2.xで機能します。Python 3.xでは、MyClass
の定義が少し異なります(MetaClass
は変更されていないため表示されません-super(MetaClass, cls)
がsuper()
になる場合があることを除いて):
_#Python 3.x
class MyClass(metaclass=MetaClass):
pass
_
Python 3.6以降、メタクラスの代わりに使用できる新しい___init_subclass__
_メソッド( PEP 487 を参照)も使用できます(@matuskoに感謝)以下の彼の答えのために):
_class ParentClass:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
register(cls)
class MyClass(ParentClass):
pass
_
[編集:欠落したcls
引数をsuper().__new__()
に修正しました]
[編集:追加Python 3.xの例]]
[編集:引数の順序をsuper()に修正し、3.xの違いの説明を改善]
[編集:追加Python 3.6 ___init_subclass__
_の例]
python 3.6では、クラス作成のより簡単なカスタマイズが導入されました( PEP 487 )。
__init_subclass__
指定されたクラスのすべてのサブクラスを初期化するフック。
提案には次の サブクラス登録 の例が含まれます
class PluginBase:
subclasses = []
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
cls.subclasses.append(cls)
この例では、
PluginBase.subclasses
には、継承ツリー全体のすべてのサブクラスのプレーンリストが含まれます。これはミックスインクラスとしてもうまく機能することに注意してください。
問題の原因は、実際には指定した行ではなく、___init__
_メソッドのsuper
呼び出しです。 dappawitで提案されているようにメタクラスを使用すると、問題は解決しません。その答えの例が機能する理由は、dappawitがBase
クラスを省略してsuper
呼び出しを省略し、例を簡略化したことです。次の例では、ClassWithMeta
もDecoratedClass
も機能しません。
_registry = {}
def register(cls):
registry[cls.__name__] = cls()
return cls
class MetaClass(type):
def __new__(cls, clsname, bases, attrs):
newclass = super(cls, MetaClass).__new__(cls, clsname, bases, attrs)
register(newclass) # here is your register function
return newclass
class Base(object):
pass
class ClassWithMeta(Base):
__metaclass__ = MetaClass
def __init__(self):
super(ClassWithMeta, self).__init__()
@register
class DecoratedClass(Base):
def __init__(self):
super(DecoratedClass, self).__init__()
_
問題はどちらの場合も同じです。 register
関数はクラスオブジェクトが作成された後、名前にバインドされる前に(メタクラスによって、またはデコレータとして直接)呼び出されます。これは、super
が危険にさらされる場所です(Python 2.x))。これは、super
呼び出しでクラスを参照する必要があるためです。グローバル名を使用し、super
呼び出しが呼び出されるまでにその名前にバインドされていると信頼することによってのみ合理的に実行されます。この場合、その信頼は誤った場所に置かれます。
ここではメタクラスは間違った解決策だと思います。メタクラスは、カスタム動作が共通するclassesのファミリを作成するためのものであり、クラスがカスタム動作が共通するインスタンスのファミリを作成するためのものです。クラスの関数を呼び出すだけです。文字列の関数を呼び出すクラスを定義したり、クラスの関数を呼び出すメタクラスを定義したりしないでください。
したがって、問題は、(1)クラス作成プロセスでフックを使用してクラスのインスタンスを作成することと、(2)super
を使用することの間の基本的な非互換性です。
これを解決する1つの方法は、super
を使用しないことです。 super
は難しい問題を解決しますが、他の問題を引き起こします(これはそのうちの1つです)。複雑な多重継承スキームを使用している場合、super
の問題はsuper
を使用しない場合の問題よりも優れており、super
次に、super
を使用する必要があります。これらの条件のいずれにも該当しない場合は、super
呼び出しを直接の基本クラス呼び出しに置き換えるだけで、実際に妥当な解決策になる場合があります。
別の方法は、register
をクラス作成にフックしないことです。各クラス定義の後にregister(MyClass)
を追加することは、それらの前に_@register
_または___metaclass__ = Registered
_(またはメタクラスを呼び出すもの)を追加することとほぼ同じです。ただし、一番下の行は、クラスの一番上にあるNice宣言よりも自己文書化が少ないため、これはあまり気分が良くありませんが、実際には妥当な解決策である可能性があります。
最後に、不愉快なハックに頼ることができますが、おそらく機能します。問題は、名前がモジュールのグローバルスコープで検索されていることです直前そこにバインドされています。したがって、次のようにしてカンニングすることができます。
_def register(cls):
name = cls.__name__
force_bound = False
if '__init__' in cls.__dict__:
cls.__init__.func_globals[name] = cls
force_bound = True
try:
registry[name] = cls()
finally:
if force_bound:
del cls.__init__.func_globals[name]
return cls
_
これはどのように機能するかです:
__init__
_が_cls.__dict__
_内にあるかどうかを確認します(常にtrueになる___init__
_属性があるかどうかとは対照的です)。別のクラスから___init__
_メソッドを継承している場合は、おそらく問題ありません(スーパークラスwillが通常の方法でその名前にすでにバインドされているため)。 doは_object.__init__
_では機能しないため、クラスがデフォルトの___init__
_を使用している場合は、このようなことは避けたいです。__init__
_メソッドを検索して、それを_func_globals
_ディクショナリにします。これは、グローバルルックアップ(super
呼び出しで参照されるクラスを見つけるなど)が行われる場所です。これは通常、___init__
_メソッドが最初に定義されたモジュールのグローバルディクショナリです。そのような辞書はaboutであり、register
が戻るとすぐに_cls.__name__
_が挿入されるので、それを自分で早く挿入します。このバージョンのregister
は、デコレータとして呼び出されても、メタクラス(まだメタクラスの適切な使用法ではないと思います)によって呼び出されても機能します。それでも失敗するいくつかのあいまいなケースがあります:
__init__
_メソッドを持っているが、_self.someMethod
_を呼び出すメソッドを継承し、定義されているクラスでsomeMethod
がオーバーライドされている奇妙なクラスを想像できますsuper
を呼び出します。おそらくありそうもない。__init__
_メソッドは、元々別のモジュールで定義されていて、クラスブロックで___init__ = externally_defined_function
_を実行してクラスで使用されている場合があります。ただし、他のモジュールの_func_globals
_属性。これは、一時的なバインディングによって、そのモジュール内のこのクラスの名前の定義が上書きされることを意味します(エラー)。再び、ありそうもない。ハックを追加して、これらの状況で少し堅牢にすることもできますが、Pythonの性質は、この種のハックが可能であり、完全に弾丸にすることは不可能であることの両方です証明。
pythonでは、ここでの答えはうまくいきませんでした。__metaclass__
が機能しませんでした。
以下は、定義時にクラスのすべてのサブクラスを登録するコードです。
registered_models = set()
class RegisteredModel(type):
def __new__(cls, clsname, superclasses, attributedict):
newclass = type.__new__(cls, clsname, superclasses, attributedict)
# condition to prevent base class registration
if superclasses:
registered_models.add(newclass)
return newclass
class CustomDBModel(metaclass=RegisteredModel):
pass
class BlogpostModel(CustomDBModel):
pass
class CommentModel(CustomDBModel):
pass
# prints out {<class '__main__.BlogpostModel'>, <class '__main__.CommentModel'>}
print(registered_models)
Baseクラスを直接呼び出すと、(super()を使用する代わりに)機能するはずです。
def __init__(self):
Base.__init__(self)