При добавлении экземпляра класса в список создается копия, а не ссылка в Python - PullRequest
0 голосов
/ 17 ноября 2018

У меня есть класс 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 может содержать подмножество общих экземпляров, и поэтому я использую функцию добавления.

Когда создается экземпляр 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 обновлялся, а этого не происходит.Другими словами, использование функции добавления не создает указатель на каждый экземпляр 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 голосов
/ 17 ноября 2018

ОБНОВЛЕНИЕ:

Вы должны каким-то образом переставить код так, чтобы 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']
0 голосов
/ 17 ноября 2018

del удаляет эту переменную, а не базовый объект.Этот объект удаляется, когда его счетчик ссылок падает до 0, и сборщик мусора решает удалить его.

Представьте, что у каждого объекта есть счетчик.Допустим, ваши машины являются list объектами (только для простоты. Концепция одинакова для каждого изменяемого объекта):

car1 = [1, 2, 3]

Здесь счетчик для объекта car1 указывает на 1.Каждый раз, когда что-то указывает на этот объект, счетчик увеличивается:

car2 = car1
car3 = car1

Теперь счетчик равен 3. Когда вы удаляете переменную, используя del, ссылка понижается, но объект удаляется только тогда, когдазначение счетчика падает до 0, и сборщик мусора решает удалить этот объект:

del car2 

Счетчик равен 2, и car1 и car3 все еще указывают на него, попытка изменить 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)

Здесь c и my_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.

С другой стороны, когда вы просите c и my_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.

Но один объект list, на который указывают c и my_cars, больше его не содержит.

...