将类的实例附加到列表会生成副本,而不是Python中的引用

时间:2018-11-17 15:35:06

标签: python class object reference instance

我有一个Person类和一个Car类。每个Person可以具有多个Car对象,并且每个Car可以被多个Person共享。请参见下面的代码:

class Person:

    community = []

    def __init__(self, name, id):
        self.name = name
        self.id = id

        Person.community.append(self)
        self.my_cars = []
        self.add_cars()

    def add_cars(self):
        c = Car.get_current_cars()
        for i in range(len(c)):
            self.my_cars.append(c[i])  # This is not making a reference to Car but creating a new copy

创建Person时,会将当前Car实例添加到该Person对象的Car列表my_car中。

请注意,我正在按照最低可复制代码在Car中添加所有Car对象。实际上,这不是必需的,因为my_car可能包含全部实例的子集,这就是为什么我使用了append函数。

创建一个Car实例后,我会将每个Car存储在一个列表中,以便Person可以访问它,如下所示:

class Car:

    cars = []

    def __init__(self, model):
        self.model = model
        Car.cars.append(self)


    @classmethod
    def get_current_cars(cls):
        return cls.cars

    @classmethod
    def delete(cls, name):

        for i in range(len(cls.cars)):
            if cls.cars[i].model == name:
                del cls.cars[i]
                break

当我想删除Car时出现问题。如果发生这种情况,我希望更新每个Person对象的my_car列表,并且不会发生这种情况。换句话说,使用append函数不是在创建指向每个Car实例的指针,而是看起来是在创建副本。

下面的代码是显示此内容的简单示例:

if __name__ == "__main__":
    models = ["Toyota", "BMW", "Tesla", "Fiat"]

    AA = [Car(models[i]) for i in range(len(models))]

    x = Car.get_current_cars()

    A = Person("john", 0)
    B = Person("Liam", 1)

    print([A.my_cars[i].model for i in range(len(A.my_cars))])
    print([B.my_cars[i].model for i in range(len(B.my_cars))])


    Car.delete("Toyota")
    x = Car.get_current_cars()

    print([x[i].model for i in range(len(x))])


    print([A.my_cars[i].model for i in range(len(A.my_cars))])
    print([B.my_cars[i].model for i in range(len(B.my_cars))])

我如何获得自己想要的行为?

2 个答案:

答案 0 :(得分:0)

del删除该变量,而不是基础对象。当该对象的引用计数器降至0时,该对象即被删除,垃圾回收器决定删除该对象。

想象一下,每个对象都有一个计数器。假设您的汽车是list个对象(仅出于简单起见。每个可变对象的概念都是相同的):

car1 = [1, 2, 3]

这里,对象car1所指向的计数器为1。每次有人指向该对象时,计数器就会增加:

car2 = car1
car3 = car1

现在计数器为3。当您使用del删除变量时,引用将关闭,但是仅当计数器降至0并且垃圾回收器决定删除该对象时,该对象才会被删除:

del car2 

计数器现在为2,car1car3仍指向该计数器,尝试更改car1会更改car3

car1.append(4)
print(car3)    # output: [1, 2, 3, 4]

结论:当对一个可变对象的引用不止一个时,所有引用都可以更改该对象,但实际上没有一个可以自己删除该对象。 当您在列表中收集这些汽车时,您仍然可以引用这些汽车:

car4 = ["Toyota"]
car5 = ["Audi"]
car6 = ["Tesla"]

c = [car4, car5, car6]
my_cars = []
for x in c:
    my_cars.append(x)

这里cmy_cars都引用相同的汽车。它们每个都指向不同的list对象,并且每个list对象都代表其自身指向每个car对象。每次将计数器增加一个,因此计数器增加2。您可以使用以下任一列表更改汽车:

c[0][0] = "Mercedes-Benz"
print(my_cars[0])  # output: ['Mercedes-Benz']

但是如上所述,您不能仅使用以下引用之一破坏对象:

del c[0]
print(my_cars)  # output is still [['Mercedes-Benz'], ['Audi'], ['Tesla']]. The object still exists.

另一方面,当您要求cmy_cars指向同一列表时,它们都指向那个(一个)list对象。请注意,只有一个list对象,对该对象的任何更改都会影响两个变量

c = [car4, car5, car6]
my_cars = c

del c[0]
print(my_cars) # output: [['Mercedes-Benz'], ['Tesla']]. The first object is removed.

请注意,如果其他一些变量仍指向该对象,则该对象可能仍不会删除:

print(car4) # output: ['Mercedes-Benz']. car4 is still pointing to that car object, thus it is not destroyed.

但是listc都指向的单个my_cars对象不再包含该对象。

答案 1 :(得分:0)

更新:

您必须按照明显的意图以某种方式重新排列代码,以Person.my_cars直接获得Car.cars的引用。这样做的话,当Car.cars的元素及其指向其指针的边界已被del语句删除时,也将使用Person.my_cars引用的元素来删除它。 当您使用list.append()时,它将创建一个新变量,并将一个指针分配给相同的地址,如原始的-复制的-变量。 删除原始变量时,del不会消除这个新界限。

解决方案1:

因此,作为解决方法,请更改add_cars方法(至少在您编写此代码时为append(),这只是最少的代码):

def add_cars(self):
    self.my_cars = Car.get_current_cars() # you don't need any loop to copy all the cars from Car class's cars.

解决方案2:

您也可以直接删除新边界。

import weakref

class Person:

    community = []
    __refs__ = set()

    def __init__(self, name, id):
        self.name = name
        self.id = id
        self.__refs__.add(weakref.ref(self))

        Person.community.append(self)
        self.my_cars = []
        self.add_cars()

    def add_cars(self):
        c = Car.get_current_cars()
        for i in range(len(c)):
            self.my_cars.append(c[i])  # This is not making a reference to Car but creating a new copy

    @classmethod
    def get_instances(cls):
        for inst_ref in cls.__refs__:
            inst = inst_ref()
            if inst is not None:
                yield inst                   


class Car:

    cars = []

    def __init__(self, model):
        self.model = model
        Car.cars.append(self)

    @classmethod
    def get_current_cars(cls):
        return cls.cars

    @classmethod
    def delete(cls, name):

        for i in range(len(cls.cars)):
            if cls.cars[i].model == name:
                del cls.cars[i]
                for inst in Person.get_instances():
                    del inst.my_cars[i]                                
                break

测试:

if __name__ == "__main__":
    models = ["Toyota", "BMW", "Tesla", "Fiat"]

    AA = [Car(models[i]) for i in range(len(models))]

    x = Car.get_current_cars()

    A = Person("john", 0)
    B = Person("Liam", 1)

    print([A.my_cars[i].model for i in range(len(A.my_cars))])
    print([B.my_cars[i].model for i in range(len(B.my_cars))])


    Car.delete("Toyota")

    x = Car.get_current_cars()

    print([x[i].model for i in range(len(x))])


    print([A.my_cars[i].model for i in range(len(A.my_cars))])
    print([B.my_cars[i].model for i in range(len(B.my_cars))])  

退出:

['Toyota', 'BMW', 'Tesla', 'Fiat']
['Toyota', 'BMW', 'Tesla', 'Fiat']
['BMW', 'Tesla', 'Fiat']
['BMW', 'Tesla', 'Fiat']
['BMW', 'Tesla', 'Fiat']