Python正在为多个实例添加值

时间:2017-01-18 12:14:35

标签: python

我为列表创建了一个描述符。

经过测试,似乎每次我将一个值附加到一个实例的列表中时,它也会被添加到另一个实例中。
甚至更奇怪,在单元测试中它会一直附加到列表中,而不是在每次测试时重置。

我的描述符主要类:

class Field(object):
    def __init__(self, type_, name, value=None, required=False):
        self.type = type_
        self.name = "_" + name
        self.required = required
        self._value = value

    def __get__(self, instance, owner):
        return getattr(instance, self.name, self.value)

    def __set__(self, instance, value):
        raise NotImplementedError

    def __delete__(self, instance):
        raise AttributeError("Can't delete attribute")

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, value):
        self._value = value if value else self.type()

描述符列表类:

class ListField(Field):
    def __init__(self, name, value_type):
        super(ListField, self).__init__(list, name, value=[])
        self.value_type = value_type

    def __set__(self, instance, value):
        if not isinstance(value, list):
            raise TypeError("{} must be a list".format(self.name))
        setattr(instance, self.name, value)

    def __iter__(self):
        for item in self.value:
            yield item

    def __len__(self):
        return len(self.value)

    def __getitem__(self, item):
        return self.value[item]

    def append(self, value):
        if not isinstance(value, self.value_type):
            raise TypeError("Value is list {} must be of type {}".format(self.name, self.value_type))
        self.value.append(value)

单元测试:

# Class I created solely for testing purposes
class ListTestClass(object):
    l = ListField("l", int)


class TestListFieldClass(unittest.TestCase):
    def setUp(self):
        self.listobject = ListTestClass()

    def test_add(self):
        # The first number is added to the list
        self.listobject.l.append(2)

    def test_multiple_instances(self):
        # This test works just fine
        l1 = ListField("l1", int)
        l2 = ListField("l2", int)

        l1.append(1)
        l2.append(2)

        self.assertEqual(l1[0], 1)
        self.assertEqual(l2[0], 2)

    def test_add_multiple(self):
        # This test works just fine
        l1 = ListField("l1", int)
        l1.append(1)
        l1.append(2)

        self.assertEqual(l1[0], 1)
        self.assertEqual(l1[1], 2)

    def test_add_error(self):
        # This test works just fine
        with self.assertRaises(TypeError):
            l1 = ListField("l1", int)
            l1.append("1")

    def test_overwrite_list(self):
        # This test works just fine
        l1 = ListField("l1", int)
        l1 = []

        l1.append(1)

    def test_overwrite_error(self):
        # This test works just fine
        l1 = ListTestClass()
        l1.l.append(1)

        with self.assertRaises(TypeError):
            l1.l = "foo"

    def test_multiple_model_instances(self):
        # I create 2 more instances of ListTestClass
        l1 = ListTestClass()
        l2 = ListTestClass()

        l1.l.append(1)
        l2.l.append(2)

        self.assertEqual(l1.l[0], 1)
        self.assertEqual(l2.l[0], 2)

最后一次测试失败

Failure
Traceback (most recent call last):
  File "/home/user/project/tests/test_fields.py", line 211, in test_multiple_model_instances
    self.assertEqual(l1.l[0], 1)
AssertionError: 2 != 1

当我查看l1.1l2.l的值时,它们都有一个包含[2, 1, 2]的列表

我在这里缺少什么?

我查看了内存地址,似乎列表都指向同一个对象。

class ListFieldTest(object):
    lf1 = ListField("lf1", int)


class TestClass(object):
    def __init__(self):
        l1 = ListFieldTest()
        l2 = ListFieldTest()

        l1.lf1.append(1)
        l2.lf1.append(2)

        print(l1.lf1)
        print(l2.lf1)

        print(hex(id(l1)))
        print(hex(id(l2)))
        print(hex(id(l1.lf1)))
        print(hex(id(l2.lf1)))

打印

[1, 2]
[1, 2]
0x7f987da018d0 --> Address for l1 
0x7f987da01910 --> Address for l2
0x7f987d9c4bd8 --> Address for l1.lf1
0x7f987d9c4bd8 --> Address for l2.lf1

3 个答案:

答案 0 :(得分:2)

ListTestClass.l是一个类属性,因此它由类的所有实例共享。相反,您应该创建一个实例属性,例如在__init__方法中:

class ListTestClass(object):
    def __init__(self):
        self.l = ListField("l", int)

类似的评论适用于ListFieldTest。您的代码中可能存在其他类似的问题,我还没有仔细检查过它。

答案 1 :(得分:0)

根据this source,正确的形式是

class ListTestClass(object):
    l_attrib = ListField("l", int)
    def __init__(self)
        self.l = l_attrib

答案 2 :(得分:0)

感谢@PM 2Ring和火山,我找到了答案。

最后,这对于值类型非常有用:

class IntTestClass(object):
    i = IntegerField("i")

但是对于无效的参考类型(如列表),您必须添加新列表

class ListTestClass(object):
    l = ListField("l", int)

    def __init__(self):
        self.l = []