【问题标题】:After overriding __new__ method in a class new object creation doesn't work as it's supposed to在类中覆盖 __new__ 方法后,新对象创建无法正常工作
【发布时间】:2014-02-05 04:23:43
【问题描述】:

我有一个奇怪的问题,我无法解决。我正在尝试使用https://github.com/Climbcare/unleashed 将一些订单推送到 Unleashed API。它具有以下由另一个类继承的 Python 生成器类:

class MetaResource(type):
    def __new__(mcs, name, bases, dct):
        """
        Looks for attributes whith a `__resourcefield__`  attribute and adds them to  `        __resourcefields__`.
        Replace the attribute with a property so its value can be directly accessed.
        """

        dct['__resourcefields__'] = {}
        dct['__embeddedresources__'] = {}

        for attr_name, attr in dct.iteritems():
            if hasattr(attr, '__resourcefield__') and attr.__resourcefield__:
                dct['__resourcefields__'][attr_name] = attr
                attr.__fieldname__ = attr_name
                attr.__parentresource__ = name
            elif hasattr(attr, '__metaclass__') and attr.__metaclass__ == mcs:
                dct['__embeddedresources__'][attr_name] = attr

        return super(MetaResource, mcs).__new__(mcs, name, bases, dct)

    def __init__(cls, name, bases, dct):
        cls.guess_endpoint()
        cls.convert_fields()
        cls.convert_embedded_resources()
        super(MetaResource, cls).__init__(name, bases, dct)

这个类被这个类扩展了:

class UnleashedResource(object):
    __metaclass__ = MetaResource

    # Override if necessary
    __endpoint__ = None

    # Created by metaclass
    __resourcefields__ = {}
    __embeddedresources__ = {}

    def __repr__(self):
        return json.dumps(
            self.to_dict(),
            sort_keys=True,
            indent=4,
            separators=(',', ': ')
        )

    def from_dict(self, dict_val):
        """
        Set all the resource's field values from a dictionary.
        """
        if not dict_val:
            return

        for field, value in dict_val.iteritems():
            if hasattr(self, field):
                setattr(self, field, value)

还有一个扩展 UnleashedResource 类的类:

class Product(UnleashedResource):
    __endpoint__ = 'Products'

    AverageLandPrice = fields.FieldNullableDecimal()
    Barcode = fields.FieldString()
    BinLocation = fields.FieldString()
    CanAutoAssemble = fields.FieldBoolean()
    ...

如您所见,MetaResource 是 UnleashedResource 类的元类

奇怪的是,当您创建 Product() 类的多个实例时,所有这些实例都是相同的,即使它们具有不同的内存 ID。例如:

p1 = Product()
p2 = Product()

对 p1 的任何更改也将存在于 p2 中。并且打印 Product() 也会给我同样的东西。

我阅读了 metaclass,但仍然一无所获。

看看这个:

>>> from unleashed.resources.product import Product
>>> p1 = Product()
>>> type(p1)
<class 'unleashed.resources.product.Product'>
>>> p1.Barcode = 12345
>>> p2 = Product()
>>> p2.Barcode
12345
>>> id(p1)
4414711632
>>> id(p2)
4414356560
>>> Product().Barcode
12345
>>> id(Product())
4414711568
>>> type(Product())
<class 'unleashed.resources.product.Product'>
>>> 

非常感谢任何帮助。

【问题讨论】:

  • 复杂总是带来麻烦。可能有一种更简单、更清晰、更直接的方法。
  • 元类创建的属性是类属性,在所有实例之间共享。请参阅有关此问题的大量先前问题,例如this one。为什么要在元类中执行此操作,而不是仅使用常规类并将该数据放入每个实例的实例属性中?

标签: python django


【解决方案1】:

这绝对是一个棘手的问题,但我想我已经弄清楚这里发生了什么。元类正在获取所有作为“字段”的类属性并将它们转换为属性。

现在通常当您分配给实例上的属性时,它将始终是实例属性,即使还有同名的类属性也是如此。但是,如果您的类属性是定义了 setter 的属性,则分配给该属性(无论是来自类还是实例)将调用 setter 函数。此处的最终结果是,通过实例分配给类属性将导致所有实例的该属性都发生更改。

想到这一点的最简单方法是,分配给属性并不是真正的分配,而是一种突变。在下面的代码中,f1.foof2.foo 是同一个对象,修改一个对象会修改另一个对象,这不足为奇:

class Foo(object):
    test = []

f1 = Foo()
f2 = Foo()
f1.test.append(1)  # f1.test is actually Foo.test, so class attribute changes
print f2.test      # f2.test is also Foo.test, so this prints [1]

同样的原则也适用于属性和描述符,例如:

class Test(object):
    def __init__(self, val=None):
        self.val = val

    def __get__(self, obj, type=None):
        return self.val

    def __set__(self, obj, val):
        self.val = val

class Foo(object):
    test = Test()

f1 = Foo()
f2 = Foo()
f1.test = 'abc'  # calls Foo.test.__set__
print f2.test    # prints 'abc', because Foo.test was modified above

【讨论】:

  • 我相信Product 应该是一个ORM 风格的类,它创建与类变量同名的实例变量。也许元类旨在实现这一点,但它没有按预期工作?
  • f2.class_attr 是 abc,试试看。 Python 对类/实例属性有奇怪的行为,具体取决于是否使用了可变/不可变类型。您为 class_attr 分配了一个不可变的字符串,因此它的行为更像一个实例属性。
  • 糟糕,是的,我之前的回答是错误的。做了更多调查,看起来元类正在将字段转换为属性,这解释了这种行为。我编辑的答案现在应该解释发生了什么。
  • @jpmc26 - 没错。这是一种尝试,如果你能摆脱一个实例,它就会很好地工作。
  • @F.J - 感谢您的详细解释。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多