基本クラスから派生クラスを動的に作成する方法


92

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

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キーワードaTestClass受け取ります。bc

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

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

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

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


念のため、提供された引数に応じてインスタンスのタイプを変更したいですか?私が与えたように、aそれは常にありMyClassTestClass決してかかりませんa?3つの引数をすべて宣言するだけでBaseClass.__init__()なく、デフォルトですべてに設定するのはなぜNoneですか?def __init__(self, a=None, b=None, C=None)
acattle 2013年

使用する可能性のあるすべての引数がわからないため、基本クラスでは何も宣言できません。私はそれぞれ5つの異なる引数を持つ30の異なるクラスを持つかもしれないので、コンストラクターで150の引数を宣言することは解決策ではありません。
Alex

回答:


141

このコードにより、動的な名前とパラメーター名を使用して新しいクラスを作成できます。のパラメーター検証で__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

動的な名前を名前付けスコープに挿入することを求めているようです-今では、これは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: ...})

更新
これを必要とする誰もがもチェックする必要がありディルのプロジェクトを-それは酸洗いすることができると主張化したりunpickle化したり、クラスピクルスは、通常のオブジェクトにないと同じように、そして私のテストのいくつかで、それまで住んでいました。


2
私の記憶が正しければ、新しいクラスがサブクラス化されたときによりうまく機能する、BaseClass.__init__()より一般的なのほうが良いでしょうsuper(self.__class__).__init__()。(参照:rhettinger.wordpress.com/2011/05/26/super-considered-super
Eric O Lebigot

@EOL:静的に宣言されたクラスの場合-しかし、Superへの最初のパラメーターとしてハードコードする実際のクラス名がないため、多くのダンスが必要になります。super上記に置き換えて、動的に作成されたクラスのサブクラスを作成して理解してください。そして、その一方で、この場合は、呼び出し元の汎用オブジェクトとしてベースクラスを持つことができます __init__
jsbueno 2013年

今、私は提案された解決策を見る時間がありましたが、それは私が望むものではありません。まず、それは1つの引数で呼び出されたよう__init__に見えBaseClassますが、実際にはBaseClass.__init__常にキーワード引数の任意のリストを受け取ります。次に、上記の解決策では、許可されているすべてのパラメーター名を属性として設定しますが、これは私が望むものではありません。すべての引数はに行く必要BaseClassがありますが、派生クラスを作成するときに私が知っている引数はどれですか。私はおそらく質問を更新するか、より明確にするためにより正確な質問をするでしょう。
Alex

@jsbueno:右は、使用してsuper()私が言及したができますTypeError: must be type, not SubSubClass。私が正しく理解している場合、これはの最初の引数selfに由来します__init__()。これはSubSubClasstypeオブジェクトが期待される場所です。これsuper(self.__class__)は、バインドされていないスーパーオブジェクトであるという事実に関連しているようです。その__init__()方法は何ですか?そのようなメソッドがtypeの最初の引数を必要とするかどうかはわかりませんtype。説明してもらえますか?(注意:変数の署名があるsuper()ため、ここでは実際に私のアプローチは意味をなさない__init__()。)
Eric O Lebigot 2013年

1
@EOL:主な問題は、実際に因数分解クラスの別のサブクラスを作成する場合です。self.__ class__は、「スーパー」が呼び出されたクラスではなく、そのサブクラスを参照します-無限再帰が発生します。
jsbueno 2013年

89

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

Python 2.7を使用してこの例を理解しようとしたとき、私はTypeErrorそれを言った__init__() takes exactly 2 arguments (1 given)。ギャップを埋めるために何か(何か?)を追加することで十分であることがわかりました。たとえば、obj = SubClass('foo')エラーなしで実行されます。
DaveL17

ので、これは正常ですSubClassのサブクラスでBaseClass問題にしてBaseClass(パラメータ取りclasstypeで、'foo'あなたの例では)。
エリックOレビゴット2017年

-2

動的属性値を持つクラスを作成するには、以下のコードをチェックアウトしてください。NB。これはpythonプログラミング言語のコードスニペットです

def create_class(attribute_data, **more_data): # define a function with required attributes
    class ClassCreated(optional extensions): # define class with optional inheritance
          attribute1 = adattribute_data # set class attributes with function parameter
          attribute2 = more_data.get("attribute2")

    return ClassCreated # return the created class

# use class

myclass1 = create_class("hello") # *generates a class*
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.