web-dev-qa-db-ja.com

基本クラスから派生クラスを動的に作成するにはどうすればよいですか

たとえば、次のような基本クラスがあります。

class BaseClass(object):
    def __init__(self, classtype):
        self._type = classtype

このクラスから、他のいくつかのクラス、たとえば.

class TestClass(BaseClass):
    def __init__(self):
        super(TestClass, self).__init__('Test')

class SpecialClass(BaseClass):
    def __init__(self):
        super(TestClass, self).__init__('Special')

次のように、新しいクラスを現在のスコープに入れる関数呼び出しによって、これらのクラスを動的に作成するニース、Pythonの方法はありますか?

foo(BaseClass, "My")
a = MyClass()
...

なぜこれが必要なのかというコメントと質問があるので、派生クラスはすべて、コンストラクターが以前に未定義の引数をとるという違いを除いて、まったく同じ内部構造を持っています。したがって、たとえば、MyClassはキーワードaを取り、クラスTestClassのコンストラクターはbcを取ります。

inst1 = MyClass(a=4)
inst2 = MyClass(a=5)
inst3 = TestClass(b=False, c = "test")

ただし、クラスのタイプを入力引数として使用しないでください。

inst1 = BaseClass(classtype = "My", a=4)

私はこれを機能させましたが、他の方法、つまり動的に作成されたクラスオブジェクトを好むでしょう。

79
Alex

このコードを使用すると、動的な名前とパラメーター名を使用して新しいクラスを作成できます。 __init__のパラメーター検証では、タイプなどの他の検証が必要な場合、またはそれらが必須であることを確認する必要がある場合、そこにロジックを追加するだけで、不明なパラメーターは許可されません。

class BaseClass(object):
    def __init__(self, classtype):
        self._type = classtype

def ClassFactory(name, argnames, BaseClass=BaseClass):
    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            # here, the argnames variable is the one passed to the
            # ClassFactory call
            if key not in argnames:
                raise TypeError("Argument %s not valid for %s" 
                    % (key, self.__class__.__name__))
            setattr(self, key, value)
        BaseClass.__init__(self, name[:-len("Class")])
    newclass = type(name, (BaseClass,),{"__init__": __init__})
    return newclass

そして、これは例えば次のように機能します:

>>> SpecialClass = ClassFactory("SpecialClass", "a b c".split())
>>> s = SpecialClass(a=2)
>>> s.a
2
>>> s2 = SpecialClass(d=3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 8, in __init__
TypeError: Argument d not valid for SpecialClass

名前のスコープに動的な名前を挿入するように求めているのがわかります。現在、 that はPythonの良い習慣とはみなされていません。 、コーディング時に既知、またはデータ-ランタイムで学習された名前は「変数」よりも「データ」です-

したがって、クラスを辞書に追加して、そこから使用することができます。

name = "SpecialClass"
classes = {}
classes[name] = ClassFactory(name, params)
instance = classes[name](...)

また、デザインでスコープ内に名前が絶対に必要な場合は、同じことを行いますが、任意の辞書の代わりに globals() 呼び出しによって返される辞書を使用します。

name = "SpecialClass"
globals()[name] = ClassFactory(name, params)
instance = SpecialClass(...)

(実際にクラスファクトリ関数が呼び出し元のグローバルスコープに名前を動的に挿入することは可能ですが、それはさらに悪い習慣であり、Python実装間で互換性がありません。それを行う方法 sys._getframe(1) で呼び出し側の実行フレームを取得し、f_globals属性でフレームのグローバル辞書にクラス名を設定します。

update、tl; dr:この回答は一般的になりましたが、それでも質問本体に非常に限定的です。 Pythonの "基本クラスから派生クラスを動的に作成する" の方法に関する一般的な答えは、新しいクラス名を渡すtypeの単純な呼び出しです。 、新しいクラスのベースクラスと__dict__ボディを持つタプル-次のように:

>>> new_class = type("NewClassName", (BaseClass,), {"new_method": lambda self: ...})

update
これを必要とする人は、 dill プロジェクトもチェックする必要があります-pickleが通常のオブジェクトに対して行うのと同じように、クラスをpickleしたりunpickleしたりできると主張し、私のテストのいくつかでそれを実行しました。

122
jsbueno

type() は、クラス(および特定のサブクラス)を作成する関数です。

def set_x(self, value):
    self.x = value

SubClass = type('SubClass', (BaseClass,), {'set_x': set_x})
# (More methods can be put in SubClass, including __init__().)

obj = SubClass()
obj.set_x(42)
print obj.x  # Prints 42
print isinstance(obj, BaseClass)  # True
74
Eric O Lebigot