Python2(有六个)元类和带参数的Fields

时间:2016-12-20 11:13:32

标签: python-2.7 metaclass

我正在创建一个应该使用字段的元类。

基于互联网上的消息来源,在StackOverflow上,我已经走到了这一步:

元类

def getmethod(attrname):
    def _getmethod(self):
        return getattr(self, "__"+attrname).get()
    return _getmethod


def setmethod(attrname):
    def _setmethod(self, value):
        return getattr(self, "__"+attrname).set(value)
    return _setmethod


class Metaclass(type):
    def __new__(cls, name, based, attrs):
        ndict = {}
        for attr in attrs:
            if isinstance(attrs[attr], Field):
                ndict['__'+attr] = attrs[attr]
                ndict[attr] = property(getmethod(attr), setmethod(attr))
        return super(Metaclass, cls).__new__(cls, name, based, ndict)

模型

class Model(six.with_metaclass(Metaclass)):
    foo = CharField()

    def __init__(self, *args, **kwargs):
        pass

字段

class Field(object):
    def __init__(self, required=False, *args, **kwargs):
        self.required = required
        self.name = None

    def set(self, value):
        self.value = value

    def get(self):
        return self.value

    def set_name(self, name):
        self.name = name

CharField

class CharField(Field):
    def __init__(self, required=False, max_length=0, min_length=0, *args, **kwargs):
        self.max_length = max_length
        self.min_length = min_length
        super(CharField, self).__init__(required, args, kwargs)

现在我创建了Model

的子类
class Product(Model):
    name = CharField()

并创建Product

的实例
if __name__ == '__main__':
    p = Product()

这很好用。

我甚至可以添加或更改产品名称

if __name__ == '__main__':
    p = Product()
    p.name = "Another beautiful product"

但是,当我想使用name作为参数时:

if __name__ == '__main__':
    p = Product(name="Another beautiful product")

引发错误:TypeError: object() takes no parameters

调试时我可以看到创建了Metaclass的实例,但是当到达行return super(Metaclass, cls).__new__(cls, name, based, ndict)时会出现错误。

有人可以帮助我吗?

1 个答案:

答案 0 :(得分:0)

在您的代码中,您创建了一组有趣的协作Field类,可以将其作为丰富的自动属性。 (*见下文)。

但是您的元类代码只担心Field属性,并且不会自动执行类实例化时传递的参数。当您编写Product(name="my name")之类的代码时,“name ='my name'”参数会传递到类的__init__方法和__new__方法中。通常是Python特殊情况__init____new__,因此未声明__init____new__的参数不会传递给基类(object)。可能使用six.with_metaclass正在破坏这种机制 - 而你的name正在进入object的构造函数(这会触发你所看到的错误)。

您可以通过自定义__new__(或__init__来解决这个问题,如果您可以完全消除元类,请参阅下文) - 只需使用您的**kwargs参数,设置任何字段发送的地方,调用对象的__new__没有那些无关的参数:

class Model(object):
    def __init__(self, **kwargs):
         for key, value in kwargs.items():
              if isinstance(getattr(self.__class__, key, None), Field):
                  setattr(self, key, value)

(见下文 - 如果你仍然需要一个“六”的元类,你可能需要 将此代码写在__new__中,而不是__init__

(*)现在 - 除了你的主要问题 - 因为你需要比Python的属性允许更丰富的属性,你不应该使用它 - 看看Descriptor Protocol如何工作 - 基本上,通过创建你的定义名为Field__get__的方法的__set__类,您可以从字段中获得所需的其他功能,而无需根据该功能(对于{{1}根据需要调用自动工作,您仍然需要自定义基础__init__或元类