给定一个元类或更简单的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
由 用于实例化模型的关键字参数和受限字段组成,在元类中处理。如果_fields
是Model
类中的类变量,那么这将非常简单,但由于它是实例变量,所以我不这样做知道如何向其中添加受限制的字段。
有人说,有没有办法实现这个目标?或者有更好的方法来处理这个问题吗? (我想只使用元类并在模型上设置_restricted_fields
类变量,然后在类__init__()
中使用此变量将受限字段添加到普通字段中,但很快模型类将会混淆代码,在我看来,这些代码应该放在代码的另一部分中。)
答案 0 :(得分:6)
在此处使用元类不是正确的方法。 元类修改了类创建行为,而不是实例创建行为。您应该使用__init__
或__new__
函数来修改实例创建行为。想要使用元类进行此类操作是使用锤子而不是螺丝刀将螺钉拧入墙壁。 ; - )
我建议您使用__new__
来实现您想要的效果。来自Python docs:
__new__()
主要用于允许不可变类型的子类(如int
,str
或tuple
)来自定义实例创建。它也通常在自定义元类中重写,以自定义类创建。
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
>>>
代码应该相当直接。当我将一组方法组织到一个类中时,其中许多方法都依赖于一组类值和特定的实例值。