31

Python 2.7で動的にクラスを生成しようとしていますが、クラスオブジェクトからメタクラスに引数を簡単に渡すことができるかどうか疑問に思っています。

私はこの投稿を読みました。これは素晴らしいですが、質問に完全には答えていません。私がしている現在:

def class_factory(args, to, meta_class):
    Class MyMetaClass(type):
        def __new__(cls, class_name, parents, attrs):
            attrs['args'] = args
            attrs['to'] = to
            attrs['eggs'] = meta_class

    class MyClass(object):
        metaclass = MyMetaClass
        ...

しかし、これは私が次のことをする必要があります

MyClassClass = class_factory('spam', 'and', 'eggs')
my_instance = MyClassClass()

これを行うためのよりクリーンな方法はありますか?

4

2 に答える 2

52

質問はPython2.7に関するものであり、すでに優れた回答がありますが、Python 3.3についても同じ質問があり、このスレッドはGoogleで見つけた回答に最も近いものでした。私はPythonのドキュメントを掘り下げて、Python 3.xのより良い解決策を見つけました。そして、Python 3.xバージョンを探している他の人のために、私の発見を共有しています。

Python3.xでメタクラスに引数を渡す

Pythonの公式ドキュメントを調べたところ、Python 3.xには、欠陥がないわけではありませんが、メタクラスに引数を渡すネイティブメソッドが用意されていることがわかりました。

クラス宣言にキーワード引数を追加するだけです。

class C(metaclass=MyMetaClass, myArg1=1, myArg2=2):
  pass

...そしてそれらは次のようにあなたのメタクラスに渡されます:

class MyMetaClass(type):

  @classmethod
  def __prepare__(metacls, name, bases, **kwargs):
    #kwargs = {"myArg1": 1, "myArg2": 2}
    return super().__prepare__(name, bases, **kwargs)

  def __new__(metacls, name, bases, namespace, **kwargs):
    #kwargs = {"myArg1": 1, "myArg2": 2}
    return super().__new__(metacls, name, bases, namespace)
    #DO NOT send "**kwargs" to "type.__new__".  It won't catch them and
    #you'll get a "TypeError: type() takes 1 or 3 arguments" exception.

  def __init__(cls, name, bases, namespace, myArg1=7, **kwargs):
    #myArg1 = 1  #Included as an example of capturing metaclass args as positional args.
    #kwargs = {"myArg2": 2}
    super().__init__(name, bases, namespace)
    #DO NOT send "**kwargs" to "type.__init__" in Python 3.5 and older.  You'll get a
    #"TypeError: type.__init__() takes no keyword arguments" exception.

and (Python 3.5以前。「UPDATE」を参照)kwargsの呼び出しを除外する必要があります。そうしないと、引数が多すぎるために例外が発生します。つまり、この方法でメタクラス引数を渡す場合は、常に実装し、カスタムキーワード引数が基本クラスとメソッドに到達しないようにする必要があります。 余分なキーワード引数を適切に処理しているようです(したがって、私が知らない機能がある場合に備えて、例でそれらを渡すのはなぜですか)。したがって、定義はオプションです。type.__new__type.__init__TypeErrorMyMetaClass.__new__MyMetaClass.__init__type.__new__type.__init__type.__prepare__**kwargstype.__prepare__

アップデート

Python 3.6では、表示typeが調整され、type.__init__追加のキーワード引数を適切に処理できるようになりました。それでも定義する必要がありますtype.__new__(例外をスローしTypeError: __init_subclass__() takes no keyword argumentsます)。

壊す

Python 3では、クラス属性ではなくキーワード引数を使用してメタクラスを指定します。

class MyClass(metaclass=MyMetaClass):
  pass

このステートメントは、大まかに次のように解釈されます。

MyClass = metaclass(name, bases, **kwargs)

...ここmetaclassで、は渡した「metaclass」引数の値、nameはクラスの文字列名('MyClass')、は渡した基本クラス(この場合はbases長さゼロのタプル)、はキャプチャされていないキーワードです。引数(この場合は空)。()kwargsdict {}

これをさらに分解すると、ステートメントは大まかに次のように解釈されます。

namespace = metaclass.__prepare__(name, bases, **kwargs)  #`metaclass` passed implicitly since it's a class method.
MyClass = metaclass.__new__(metaclass, name, bases, namespace, **kwargs)
metaclass.__init__(MyClass, name, bases, namespace, **kwargs)

...クラス定義に渡したキャプチャされていないキーワード引数kwargsは常にどこにありますか。dict

上記の例を分解すると、次のようになります。

class C(metaclass=MyMetaClass, myArg1=1, myArg2=2):
  pass

...大まかに次のように変換されます。

namespace = MyMetaClass.__prepare__('C', (), myArg1=1, myArg2=2)
#namespace={'__module__': '__main__', '__qualname__': 'C'}
C = MyMetaClass.__new__(MyMetaClass, 'C', (), namespace, myArg1=1, myArg2=2)
MyMetaClass.__init__(C, 'C', (), namespace, myArg1=1, myArg2=2)

この情報のほとんどは、「クラス作成のカスタマイズ」に関するPythonのドキュメントからのものです。

于 2014-08-07T19:57:18.730 に答える
17

はい、それを行う簡単な方法があります。メタクラスの__new__()メソッドでは、最後の引数として渡されたクラスディクショナリをチェックインするだけです。classステートメントで定義されているものはすべてそこにあります。例えば:

class MyMetaClass(type):
    def __new__(cls, class_name, parents, attrs):
        if 'meta_args' in attrs:
            meta_args = attrs['meta_args']
            attrs['args'] = meta_args[0]
            attrs['to'] = meta_args[1]
            attrs['eggs'] = meta_args[2]
            del attrs['meta_args'] # clean up
        return type.__new__(cls, class_name, parents, attrs)

class MyClass(object):
    __metaclass__ = MyMetaClass
    meta_args = ['spam', 'and', 'eggs']

myobject = MyClass()

from pprint import pprint
pprint(dir(myobject))
print myobject.args, myobject.to, myobject.eggs

出力:

['__class__',
 '__delattr__',
 '__dict__',
 '__doc__',
 '__format__',
 '__getattribute__',
 '__hash__',
 '__init__',
 '__metaclass__',
 '__module__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'args',
 'eggs',
 'to']
spam and eggs

アップデート

メタクラスを指定するための構文がPython3で互換性のない方法で変更されたため、上記のコードはPython2でのみ機能します。

Python 3で動作させる(Python 2では動作しない)のは非常に簡単で、次のように定義を変更するだけで済みますMyClass

class MyClass(metaclass=MyMetaClass):
    meta_args = ['spam', 'and', 'eggs']

メタクラスを明示的に呼び出し、その基本クラスとして作成されたクラスを使用する「オンザフライ」の基本クラスを作成することで、構文の違いを回避し、Python2と3の両方で機能するコードを生成することもできます。定義されています。

class MyClass(MyMetaClass("NewBaseClass", (object,), {})):
    meta_args = ['spam', 'and', 'eggs']

Python 3のクラス構築も変更され、引数を渡す他の方法を可能にするサポートが追加されました。場合によっては、ここに示す手法よりもそれらを使用する方が簡単な場合があります。それはすべてあなたが達成しようとしていることに依存します。

新しいバージョンのPythonでのプロセスの説明については、@JohnCrawfordの詳細な回答を参照してください。

于 2012-12-07T12:55:43.837 に答える