从元类设置实例变量

时间:2014-02-01 15:39:46

标签: python class oop instance metaclass

给定一个元类或更简单的type(),最后一个参数代表类dict,它是一个负责类变量的类。我想知道,有没有办法在元类中设置实例变量?

我使用元类的原因是我需要收集在类创建时定义的一些变量并处理它们。但是,这个处理的结果需要附加到类的每个实例,而不是类本身,因为结果将是一个列表,它将从一个类的实例到另一个实例不同。

我将提供一个示例,以便更容易关注我。


我有一个Model类,定义如下:

class Model(object):
    __metaclass__ = ModelType

    has_many = None

    def __init__(self, **kwargs):
        self._fields = kwargs

has_many class 变量将在类定义中被任何人认为需要的模型填充。在__init__()中,我只是将模型实例化中提供的关键字分配给实例变量_fields

然后我有ModelType元类:

class ModelType(type):
    def __new__(cls, name, bases, dct):
        if 'has_many' in dct:
            dct['_restricted_fields'].append([has-many-model-name])

我在这里需要做的非常简单。我检查是否有人在其自定义模型类中定义了has_many 变量,然后将变量的内容添加到受限制字段列表中。

现在是重要的一部分。在实例化模型时,我需要_fields 用于实例化模型的关键字参数和受限字段组成,在元类中处理。如果_fieldsModel类中的变量,那么这将非常简单,但由于它是实例变量,所以我不这样做知道如何向其中添加受限制的字段。


有人说,有没有办法实现这个目标?或者有更好的方法来处理这个问题吗? (我想只使用元类并在模型上设置_restricted_fields类变量,然后在类__init__()中使用此变量将受限字段添加到普通字段中,但很快模型类将会混淆代码,在我看来,这些代码应该放在代码的另一部分中。)

5 个答案:

答案 0 :(得分:6)

在此处使用元类不是正确的方法。 元类修改了类创建行为,而不是实例创建行为。您应该使用__init____new__函数来修改实例创建行为。想要使用元类进行此类操作是使用锤子而不是螺丝刀将螺钉拧入墙壁。 ; - )

我建议您使用__new__来实现您想要的效果。来自Python docs

  

__new__()主要用于允许不可变类型的子类(如intstrtuple)来自定义实例创建。它也通常在自定义元类中重写,以自定义类创建。

class MetaModel(type):
    def __new__(cls, name, bases, attrs):
        attrs['_restricted_fields'] = [attrs.get('has_many')]
        return type.__new__(cls, name, bases, attrs)

class Model(object):
    __metaclass__ = MetaModel
    has_many = None
    def __new__(cls, *args, **kwargs):
        instance = object.__new__(cls, *args, **kwargs)
        instance.instance_var = ['spam']
        return instance

class SubModel(Model):
    has_many = True
    def __init__(self):
        # No super call here.
        self.attr = 'eggs'

s = SubModel()
assert s._restricted_fields == [True]  # Added by MetaModel
assert s.instance_var == ['spam']  # Added by Model.__new__
assert s.attr == 'eggs'  # Added by __init__

# instance_var is added per instance.
assert SubModel().instance_var is not SubModel().instance_var

MetaModel负责创建Model个类。它将_restricted_fields类变量添加到由它创建的任何Model类中(该值是包含has_many类变量的列表)。

Model类定义了一个默认的has_many类变量。它还修改了实例创建行为,它为每个创建的实例添加了instance_var属性。

SubModel由您的代码用户创建。它定义了一个__init__函数来修改实例创建。请注意,它不会调用任何超类函数,这不是必需的。 __init__为每个attr实例添加SubClass属性。

答案 1 :(得分:2)

设置实例变量的位置在类的__init__方法中。但是如果你不想让每个使用你的元类的类包含相同的代码,为什么不让元类为类提供自己的__init__方法,包含现有的__init__方法。 :

class MyMetaclass(type):
    def __new__(mcs, name, bases, dct):
        if 'has_many' in dct:
            dct['_restricted_fields'].append(["something"])

        orig_init = dct.get("__init__") # will be None if there was no __init__

        def init_wrapper(self, *args, **kwargs):
            if orig_init:
                orig_init(self, *args, **kwargs)          # call original __init__
            self._fields = getattr(self, "_fields", [])   # make sure _fields exists
            self._fields.extend(["whatever"])             # make our own additions

        dct["__init__"] = init_wrapper   # replace original __init__ with our wrapper

        return type.__new__(mcs, name, bases, dct)

我并不完全明白你想要实现的实际逻辑是什么,但你可以用你真正想做的任何东西替换包装函数中的self._fields.extend(["whatever"])。该上下文中的self是实例,而不是类或元类!包装器函数是一个闭包,因此如果需要,您可以访问mcs中的元类和dct中的类字典。

答案 2 :(得分:1)

如果需要为每个实例设置,那么放置代码来计算它的地方就在__init__

但是,请注意,类变量可通过实例使用,除非被实例变量遮蔽。

答案 3 :(得分:1)

你可以用工厂而不是元类来完成这一切。

工厂可以创建类型,然后创建这些类型的实例 - 但是在初始化时而不是在创建类时创建。这将相关知识保存在一个地方(工厂),但不会试图将两种不同的工作分成一个操作。

OTOH你也可以使用你在课堂创建时通过闭包获得的信息动态编写一个在初始时运行的函数

class ExMeta(type):

        def __new__(cls, name, parents, kwargs):
            kwargs['ADDED_DYNAMICALLY'] = True

            secret_knowledge = 42
            def fake_init(*args, **kwargs):
                print "hello world... my args are:",  args, kwargs
                print "the meaning of the universe is ", secret_knowledge
            kwargs['_post_init'] = fake_init
            return super(ExMeta, cls).__new__(cls, name, parents, kwargs)

class Example(object):
    SOME_ATTR = 1
    SOME_OTHER = 2
    __metaclass__ = ExMeta       

    def __init__(self):
        self._post_init()


bob = Example()
> hello world... my args are: (<__main__.Example object at 0x0000000002425BA8>,) {}
> the meaning of the universe is  42

答案 4 :(得分:0)

更新可能会显示我的想法:

class MetaKlass(object):
    def __new__(cls, *args, **kwargs):
        class_methods = {'foo'}
        _ins = super(MetaKlass, cls).__new__(cls)
        for k, v in kwargs.iteritems():
            if k in class_methods:
                _do = _ins.__getattribute__(k)
                setattr(_ins, k, _do(v)) # self monkey patching:
                                        # classmethod is replaced by its output
            else:
                setattr(_ins, k, v)
        return _ins

    def __init__(self, arg1, arg2, *args, **kwargs):
        self.a = arg1
        self.b = arg2

    @classmethod
    def foo(cls, x):
        return 42

所以我们得到:

>>> from overflow_responses import MetaKlass
>>> klass_dict = {'foo': 1, 'angel': 2}
>>> k = MetaKlass(3, 7, **klass_dict)
>>> k.foo, k.angel, k.a, k.b
(42, 2, 3, 7)

你在寻找这样的东西:

>>> kwd = {'a': 2}  # the 'class variables dict'
>>> class MetaKlass(object):
...     def __new__(cls, kwd):
...             _ins = super(MetaKlass, cls).__new__(cls)
...             for k, v in kwd.iteritems():
...                     setattr(_ins, k, v)
...             return _ins
...
>>> mk = MetaKlass(kwd)
>>> mk.a
2
>>>

代码应该相当直接。当我将一组方法组织到一个类中时,其中许多方法都依赖于一组类值和特定的实例值。