I have a Person
class and a Car
class. Each Person
can have multiple Car
objects and each Car
can be shared by more than one Person
. See the code below:
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
When a Person
is created the current Car
instances are added to that Person
object's Car
list my_car
.
Note I am adding all Car
objects in Car
as per minimum reproducible code. In reality this is not necessary because my_car
may contain a subset of the total instances and that is why I am using the append function.
When a Car
instance is created I am storing each Car
in a list so this can be accessed by Person
like below:
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
The problem arises when I want to delete a Car
. If this happens I would like each Person
object's my_car
list to be updated and this is not happening. In other words using the append function is not creating a pointer to each Car
instance but rather it seems it is creating a copy.
The code below is a simple example that shows this:
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))])
How can I obtain my the behavior I desire?
del
deletes that variable, not the underlying object. That object is deleted when its counter of references drops to 0 and garbage collector decides to delete it.
Imagine that every object has a counter. Let's say your cars are list
objects (Only for the sake of simplicity. The concept is the same for every mutable object):
car1 = [1, 2, 3]
Here the counter for object car1
points to is 1. Every time something points to that object, the counter goes up:
car2 = car1
car3 = car1
Now the counter is 3. When you delete a variable using del
the reference goes down, but the object is only deleted when the counter drops to 0 and the garbage collector decides to delete that object:
del car2
The counter is 2 now and car1
and car3
are still pointing to it, trying to alter car1
would alter car3
:
car1.append(4)
print(car3) # output: [1, 2, 3, 4]
Conclusion: When there is more than one reference to a mutable object, all of them can alter that object, but none can actually delete the object on its own. When you collect those cars in a list, you still have a reference to those cars:
car4 = ["Toyota"]
car5 = ["Audi"]
car6 = ["Tesla"]
c = [car4, car5, car6]
my_cars = []
for x in c:
my_cars.append(x)
Here c
and my_cars
both hold references to the same cars. They each point to a different list
object, and each list
object points to each car
object on the behalf of its own. Each, increase the counter by one, thus the counter is increased by 2. You can alter cars using either of the lists:
c[0][0] = "Mercedes-Benz"
print(my_cars[0]) # output: ['Mercedes-Benz']
But as we concluded above, you cannot destroy the object using just one of the references:
del c[0]
print(my_cars) # output is still [['Mercedes-Benz'], ['Audi'], ['Tesla']]. The object still exists.
On the other hand, when you ask c
and my_cars
to point to the same list, they both point to that (one) list
object. Note that there is just one list
object, and any changes to that object affects both variables :
c = [car4, car5, car6]
my_cars = c
del c[0]
print(my_cars) # output: [['Mercedes-Benz'], ['Tesla']]. The first object is removed.
Note that, then the object might still not be deleted if some other variables are still pointing to it:
print(car4) # output: ['Mercedes-Benz']. car4 is still pointing to that car object, thus it is not destroyed.
But the single list
object both c
and my_cars
are pointing to does not contain it anymore.
UPDATE:
You have to rearrange the code somehow to Person.my_cars
get the direct reference of Car.cars
as you obviously intended. Doing so, when it Car.cars
's elements - and thus its bounds to their pointers - has been deleted with del
statement, it will be done with Person.my_cars
referred elements too. When you use list.append()
it will create a new variable and it assigns a pointer to the same address like the original - copied - variable. del
won't eliminate this new bound when you delete the original variable.
Solution 1:
So as a workaround this problem change add_cars method (at least append(), as you wrote this is just a minimal code):
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.
Solution 2:
You can delete the new bounds directly as well.
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
Test:
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))])
Out:
['Toyota', 'BMW', 'Tesla', 'Fiat']
['Toyota', 'BMW', 'Tesla', 'Fiat']
['BMW', 'Tesla', 'Fiat']
['BMW', 'Tesla', 'Fiat']
['BMW', 'Tesla', 'Fiat']
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.