繁体   English   中英

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

[英]Appending an instance of a class to a list makes a copy rather than a reference in Python

我有一个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))])

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

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.

但是cmy_cars都指向的单个list对象不再包含它。

更新:

您必须以某种方式将代码重新排列为Car.cars ,这显然是您想要的,直接获得了Person.my_cars的引用。 这样做时,如果使用del语句删除了Car.cars的元素及其指针的边界,则也将使用Person.my_cars引用的元素来完成此操作。 当您使用list.append() ,它将创建一个新变量,并且将一个指针分配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']

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM