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

Python2 (with six) metaclass and Fields with parameters

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

根据 Internet 上的资源和 Whosebug 上的资料,我已经走到这一步了:

元类

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

字符字段

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

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

有人可以帮我吗?

在您的代码中,您创建了一组有趣的协作 Field class,它们可以用作丰富的自动属性。 (* 请参阅下文)。

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

你可以通过自定义 __new__ 来解决这个问题(或者 __init__ 如果你完全可以摆脱 metaclass,见下文)- 只需消耗你的 **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)

(见下文——如果你仍然需要一个带有 "six" 的元class,你可能需要 将此代码写在 __new__ 中,而不是 __init__)

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