В python 3.{2-7} странно клонируются сложносочиненные субъекты классов

Итак мы клонируем субъекты класса smthng в список instlst, после чего видим, что список глубины 1 lst1 склонировался как надо в то время как список большей глубины lst2 перезаписывает себя.

При сравнении lst2 из разных субъектов в instlst через is всегда дает False , но lst2 при этом меняется вместе с "предшественниками", хотя по моей логике так быть не должно.

Сердечно прошу объяснить мне как работает эта техномагия, является ли багом, и как мне клонировать списки глубиной 3 внутри класса, так чтоб они сохранялись

P.S deepcopy и @classmethod пробовал, не помогают

class smthng:
    def __init__(self, ):
        self.lst1=['a ' for a in range(0,5)]
        self.lst2=[['a ' for a in range(0,5)],['appendix']]#,['a ' for a in range(0,2)]]
        self.mn=5
        self.junk='SomeRandomJunk'
    def cln(self,scnd):
        self.lst1=[]
        self.lst1=self.lst1+scnd.lst1
        self.lst2=[]
        self.lst2=self.lst2+scnd.lst2
        self.mn=scnd.mn
        self.junk=''
        self.junk=self.junk+scnd.junk
        #same for other junk
mninst=smthng()
instlst=[]
for i in range(0,3):
    for u in range(0,3):
        instlst=instlst+[smthng()]
        instlst[len(instlst)-1].cln(mninst)
        instlst[len(instlst)-1].lst1[1]=i
        instlst[len(instlst)-1].lst1[2]=u
        instlst[len(instlst)-1].lst2[0][1]=i
        instlst[len(instlst)-1].lst2[0][2]=u
    print('depth 1 all goes well')
    for u in range(0,len(instlst)):
        print('instlst main is',instlst[u].lst1,'i=',i)
    print('depth 2 previous instances get overwritten')
    for u in range(0,len(instlst)):
        print('instlst main is',instlst[u].lst2,'i=',i) 
    #mninst.cln1(instlst[len(instlst)-1])

а вот и вывод на Python 3.7 смотрите сами #Same code, different result

depth 1 all goes well
instlst main is ['a ', 0, 0, 'a ', 'a '] i= 0
instlst main is ['a ', 0, 1, 'a ', 'a '] i= 0
instlst main is ['a ', 0, 2, 'a ', 'a '] i= 0
depth 2 previous instances get overwritten
instlst main is [['a ', 0, 2, 'a ', 'a '], ['appendix']] i= 0
instlst main is [['a ', 0, 2, 'a ', 'a '], ['appendix']] i= 0
instlst main is [['a ', 0, 2, 'a ', 'a '], ['appendix']] i= 0
depth 1 all goes well
instlst main is ['a ', 0, 0, 'a ', 'a '] i= 1
instlst main is ['a ', 0, 1, 'a ', 'a '] i= 1
instlst main is ['a ', 0, 2, 'a ', 'a '] i= 1
instlst main is ['a ', 1, 0, 'a ', 'a '] i= 1
instlst main is ['a ', 1, 1, 'a ', 'a '] i= 1
instlst main is ['a ', 1, 2, 'a ', 'a '] i= 1
depth 2 previous instances get overwritten
instlst main is [['a ', 1, 2, 'a ', 'a '], ['appendix']] i= 1
instlst main is [['a ', 1, 2, 'a ', 'a '], ['appendix']] i= 1
instlst main is [['a ', 1, 2, 'a ', 'a '], ['appendix']] i= 1
instlst main is [['a ', 1, 2, 'a ', 'a '], ['appendix']] i= 1
instlst main is [['a ', 1, 2, 'a ', 'a '], ['appendix']] i= 1
instlst main is [['a ', 1, 2, 'a ', 'a '], ['appendix']] i= 1
depth 1 all goes well
instlst main is ['a ', 0, 0, 'a ', 'a '] i= 2
instlst main is ['a ', 0, 1, 'a ', 'a '] i= 2
instlst main is ['a ', 0, 2, 'a ', 'a '] i= 2
instlst main is ['a ', 1, 0, 'a ', 'a '] i= 2
instlst main is ['a ', 1, 1, 'a ', 'a '] i= 2
instlst main is ['a ', 1, 2, 'a ', 'a '] i= 2
instlst main is ['a ', 2, 0, 'a ', 'a '] i= 2
instlst main is ['a ', 2, 1, 'a ', 'a '] i= 2
instlst main is ['a ', 2, 2, 'a ', 'a '] i= 2
depth 2 previous instances get overwritten
instlst main is [['a ', 2, 2, 'a ', 'a '], ['appendix']] i= 2
instlst main is [['a ', 2, 2, 'a ', 'a '], ['appendix']] i= 2
instlst main is [['a ', 2, 2, 'a ', 'a '], ['appendix']] i= 2
instlst main is [['a ', 2, 2, 'a ', 'a '], ['appendix']] i= 2
instlst main is [['a ', 2, 2, 'a ', 'a '], ['appendix']] i= 2
instlst main is [['a ', 2, 2, 'a ', 'a '], ['appendix']] i= 2
instlst main is [['a ', 2, 2, 'a ', 'a '], ['appendix']] i= 2
instlst main is [['a ', 2, 2, 'a ', 'a '], ['appendix']] i= 2
instlst main is [['a ', 2, 2, 'a ', 'a '], ['appendix']] i= 2

Update В принципе решением данной проблемы является код написанный ниже, но все еще не понимаю почему при копировании списка глубины 2 способом для глубины 1 при сравнении instlst[a].lst2[b] is instlst[c].lst2[b] при a,c >0 and a,c <len(instlst)-1 and a!=c выдавало False хотя по факту это были ссылки на 1 и тот объект, что меня и запутало.

И еще мне любопытно существует ли реализация этой функции на питоне для произвольной глубины и можно ли как-то менять глубину клонирования в deepcopy потому что у меня оно клонировало правильно лишь до глубины 1 внутри класса на Python 3.{2-7}

P.s ответ на комментарий append вместо сложения списков породит лишние квадратные скобки ; += не использовал во избежании появления еще каких-нибудь глитчей

#глубина 1
self.lst1=[]
self.lst1=self.lst1+scnd.lst1
#глубина 2
for i in range(0,len(self.lst2)):
    self.lst2[i]=[]
    self.lst2[i]=self.lst2[i]+scnd.lst2[i]
#глубина 3
for i in range(0,len(self.lst3)):
    for u in range(0,len(self.lst3[i])):    
        self.lst3[i][u]=[]
        self.lst3[i][u]=self.lst3[i][u]+scnd.lst3[i][u]

Ответы (1 шт):

Автор решения: gil9red

У вас не происходит копирования списка.

Вы создаете список, складывайте его с другим списком, в результате создается новый список с элементами первого и второго списков.

Заметьте, тут не будет копирования, просто элементы одного списка будут перенесены в другой. А для изменяемых объектов (таких как списки) это означает, что объект при таком раскладе будет в обоих списках одновременно, т.к. там нет копирования объекта, там будет копия ссылки на объект.

Суть проблемы:

    self.lst1=[]
    self.lst1=self.lst1+scnd.lst1
    self.lst2=[]
    self.lst2=self.lst2+scnd.lst2

Наглядный пример:

l2 = [['FOO']]
print(id(l2[0]), l2[0])
# 2421659886216 ['FOO']

l1 = []
l1 = l1 + l2
print(id(l1[0]), l1[0])
# 2421659886216 ['FOO']

Как тут видно, хоть объект ['FOO'] попал в другой список, он остался тем же объектом. Это видно по его id. И если вы, например, в l1 поменяете список ['FOO'], то он поменяется в l2, т.к. там тоже ссылка на объект.

l1[0].append('BAR')
print(l1, l2)
# [['FOO', 'BAR']] [['FOO', 'BAR']]

print(l1[0] is l2[0])
# True

Решением будет сделать копию списков

  • Используйте модуль copy, у него есть отличный метод deepcopy
  • Для добавления в список используйте append

Пример:

import copy

class smthng:
    def __init__(self, ):
        self.lst1 = ['a ' for _ in range(5)]
        self.lst2 = [
            ['a ' for _ in range(5)],
            ['appendix']
        ]
        self.mn = 5
        self.junk = 'SomeRandomJunk'

    def cln(self, scnd):
        self.lst1 = copy.deepcopy(scnd.lst1)
        self.lst2 = copy.deepcopy(scnd.lst2)
        self.mn = scnd.mn
        self.junk = scnd.junk

mninst = smthng()
instlst = []
for i in range(3):
    for u in range(3):
        obj = smthng()
        obj.cln(mninst)
        obj.lst1[1] = i
        obj.lst1[2] = u
        obj.lst2[0][1] = i
        obj.lst2[0][2] = u

        instlst.append(obj)
        
    print('depth 1 all goes well')
    for x in instlst:
        print('instlst main is', x.lst1, 'i=', i)

    print('depth 2 previous instances get overwritten')
    for x in instlst:
        print('instlst main is', x.lst2, 'i=', i)

    print('\n' + '-' * 100 + '\n')

Результат:

depth 1 all goes well
instlst main is ['a ', 0, 0, 'a ', 'a '] i= 0
instlst main is ['a ', 0, 1, 'a ', 'a '] i= 0
instlst main is ['a ', 0, 2, 'a ', 'a '] i= 0
depth 2 previous instances get overwritten
instlst main is [['a ', 0, 0, 'a ', 'a '], ['appendix']] i= 0
instlst main is [['a ', 0, 1, 'a ', 'a '], ['appendix']] i= 0
instlst main is [['a ', 0, 2, 'a ', 'a '], ['appendix']] i= 0

----------------------------------------------------------------------------------------------------

depth 1 all goes well
instlst main is ['a ', 0, 0, 'a ', 'a '] i= 1
instlst main is ['a ', 0, 1, 'a ', 'a '] i= 1
instlst main is ['a ', 0, 2, 'a ', 'a '] i= 1
instlst main is ['a ', 1, 0, 'a ', 'a '] i= 1
instlst main is ['a ', 1, 1, 'a ', 'a '] i= 1
instlst main is ['a ', 1, 2, 'a ', 'a '] i= 1
depth 2 previous instances get overwritten
instlst main is [['a ', 0, 0, 'a ', 'a '], ['appendix']] i= 1
instlst main is [['a ', 0, 1, 'a ', 'a '], ['appendix']] i= 1
instlst main is [['a ', 0, 2, 'a ', 'a '], ['appendix']] i= 1
instlst main is [['a ', 1, 0, 'a ', 'a '], ['appendix']] i= 1
instlst main is [['a ', 1, 1, 'a ', 'a '], ['appendix']] i= 1
instlst main is [['a ', 1, 2, 'a ', 'a '], ['appendix']] i= 1

----------------------------------------------------------------------------------------------------

depth 1 all goes well
instlst main is ['a ', 0, 0, 'a ', 'a '] i= 2
instlst main is ['a ', 0, 1, 'a ', 'a '] i= 2
instlst main is ['a ', 0, 2, 'a ', 'a '] i= 2
instlst main is ['a ', 1, 0, 'a ', 'a '] i= 2
instlst main is ['a ', 1, 1, 'a ', 'a '] i= 2
instlst main is ['a ', 1, 2, 'a ', 'a '] i= 2
instlst main is ['a ', 2, 0, 'a ', 'a '] i= 2
instlst main is ['a ', 2, 1, 'a ', 'a '] i= 2
instlst main is ['a ', 2, 2, 'a ', 'a '] i= 2
depth 2 previous instances get overwritten
instlst main is [['a ', 0, 0, 'a ', 'a '], ['appendix']] i= 2
instlst main is [['a ', 0, 1, 'a ', 'a '], ['appendix']] i= 2
instlst main is [['a ', 0, 2, 'a ', 'a '], ['appendix']] i= 2
instlst main is [['a ', 1, 0, 'a ', 'a '], ['appendix']] i= 2
instlst main is [['a ', 1, 1, 'a ', 'a '], ['appendix']] i= 2
instlst main is [['a ', 1, 2, 'a ', 'a '], ['appendix']] i= 2
instlst main is [['a ', 2, 0, 'a ', 'a '], ['appendix']] i= 2
instlst main is [['a ', 2, 1, 'a ', 'a '], ['appendix']] i= 2
instlst main is [['a ', 2, 2, 'a ', 'a '], ['appendix']] i= 2

UPD. deepcopy копирует и объекты, и поля в копируемых объектах, даже если те объекты сами находятся в списках:

import copy


class Foo:
    def __init__(self):
        self.a = 1
        self.b = 2
        self.c = [1, 2, 3, [4, 5, 6]]

    def __repr__(self):
        return f'Foo[{self.a}, {self.b}, {self.c}, {id(self.c)}, {id(self.c[-1])}]'


foo = Foo()
print(foo)
# Foo[1, 2, [1, 2, 3, [4, 5, 6]], 2432577922504, 2432577922568]

foo2 = copy.deepcopy(foo)
print(foo2)
# Foo[1, 2, [1, 2, 3, [4, 5, 6]], 2432577922376, 2432577922312]

print()

items = [foo, foo, foo]
print(items)
# [Foo[1, 2, [1, 2, 3, [4, 5, 6]], 2432577922504, 2432577922568], Foo[1, 2, [1, 2, 3, [4, 5, 6]], 2432577922504, 2432577922568], Foo[1, 2, [1, 2, 3, [4, 5, 6]], 2432577922504, 2432577922568]]

items2 = copy.deepcopy(items)
print(items2)
# [Foo[1, 2, [1, 2, 3, [4, 5, 6]], 2432577922120, 2432577922056], Foo[1, 2, [1, 2, 3, [4, 5, 6]], 2432577922120, 2432577922056], Foo[1, 2, [1, 2, 3, [4, 5, 6]], 2432577922120, 2432577922056]]

Но есть интересная вещь, в списке, имеющий один объект в нескольких элементах (items = [foo, foo, foo]) в копии списка будет тоже одна копия объекта, вместо ожидаемых нескольких копий от того объекта.

Это не проблема, если там будут разные объекты, для проверки сделаем разные объекты через копии:

items = [copy.deepcopy(foo), copy.deepcopy(foo), copy.deepcopy(foo)]
→ Ссылка