Защо .append() не работи в този списък?

Имам обект scene, който е екземпляр на клас Scene и има списък children, който връща:

[<pythreejs.pythreejs.Mesh object at 0x000000002E836A90>, <pythreejs.pythreejs.SurfaceGrid object at 0x000000002DBF9F60>, <pythreejs.pythreejs.Mesh object at 0x000000002E8362E8>, <pythreejs.pythreejs.AmbientLight object at 0x000000002E8366D8>, <pythreejs.pythreejs.DirectionalLight object at 0x000000002E836630>]

Ако искам да актуализирам този списък с point, който има тип:

<class 'pythreejs.pythreejs.Mesh'>

Трябва да изпълня:

scene.children = list(scene.children) + [point]

Обикновено бих изпълнил:

scene.children.append(point)

Въпреки това, докато и двата подхода добавят point, само първият действително актуализира списъка и произвежда очаквания изход (т.е. воксели в решетка). Защо?

Пълният код може да бъде намерен тук.


person nluigi    schedule 10.09.2015    source източник
comment
Не, append трябва да работи. Ако не стане, става нещо много странно и трябва да публикувате минимално възпроизводим пример.   -  person Kevin    schedule 10.09.2015
comment
Разбирам, за съжаление имам малък проблем с създаването на минимален пример, при който възниква този проблем :(... В момента се опитвам да разбера дали правя нещо глупаво, което причинява този проблем   -  person nluigi    schedule 10.09.2015
comment
Единственото обяснение, за което мога да се сетя, е, че scene.children всъщност не е списък, това е някакъв друг клас, който имплементира append, но не се държи по начина, по който би се държал списък. Ако поставите print(type(scene.children)) на ред непосредствено пред scene.children.append(point), какъв е резултатът?   -  person Kevin    schedule 10.09.2015
comment
@Kevin - това връща <type 'list'>; имайте предвид, че scene е и екземпляр на клас Scene и children е списъкът   -  person nluigi    schedule 10.09.2015
comment
Как проверявате съдържанието на scene.children, за да видите дали point присъства в него? Веднага след реда append проверявате ли, или по-късно?   -  person Kevin    schedule 10.09.2015
comment
Как изпълнявате този код?   -  person pacholik    schedule 10.09.2015
comment
@pacholik - това е файл от бележник на ipython   -  person nluigi    schedule 10.09.2015
comment
@Kevin - както се оказа, point всъщност се добавя, тъй като point in scene.children връща True след добавяне. Резултатът обаче е, че в случай scene.children = list(scene.children) + [point] изходът е както се очаква (воксели се добавят към мрежата), докато в случай scene.children.append(point) не е. Това наистина е объркващо :(...   -  person nluigi    schedule 10.09.2015
comment
Може да е проблем със споделянето на справка. Ако съществуват две променливи, които препращат към един и същи списък, тогава appending към него ще промени съдържанието на списъка на двете променливи. Но ако направите scene.children = some_new_value, тогава scene.children ще сочи към нов списък, а другата променлива ще сочи към стария.   -  person Kevin    schedule 10.09.2015
comment
Възможно ли е scene.children да е свойство, което връща копие на списъка, използван вътрешно? Ако нямате източника или документацията, опитайте print(scene.children is scene.children)   -  person Niklas R    schedule 10.09.2015


Отговори (2)


Предполагам, че проблемът ви се дължи на това, че children е property (или друг дескриптор), а не обикновен атрибут на екземпляра Scene, с който взаимодействате. Можете да получите списък с деца или да присвоите нов списък с деца към атрибута, но списъците, с които работите, всъщност не са начинът, по който класът следи своите деца вътрешно. Ако промените списъка, който получавате от scene.children, модификациите не се отразяват в класа.

Един от начините да тествате това би бил да запазите списъка от scene.children няколко пъти в различни променливи и да видите дали всички те са един и същ списък или не. Опитвам:

a = scene.children
b = scene.children
c = scene.children

print(id(a), id(b), id(c))

Подозирам, че ще получите различни ids за всеки списък.

Ето един клас, който демонстрира същия проблем, който виждате:

class Test(object):
    def __init__(self, values=()):
        self._values = list(values)

    @property
    def values(self):
        return list(self._values)

    @values.setter
    def values(self, new_values):
        self._values = list(new_values)

Всеки път, когато проверявате свойството values, ще получите нов (копиран) списък.

Не мисля, че има поправка, която да е коренно различна от това, което сте открили, че работи. Можете да рационализирате малко нещата, като използвате:

scene.children += [point]

Поради начина, по който работи операторът += в Python, това разширява списъка и след това го пренасочва обратно към scene.children (a += b е еквивалентно на a = a.__iadd__(b), ако методът __iadd__ съществува).

person Blckknght    schedule 10.09.2015
comment
Предложеният оператор за печат връща същото id за a, b и c. Освен това scene.children+=[point] не дава същия резултат като scene.children=list(scene.children)+[point]. Вероятно обаче сте прави, че scene.children не е прост списък - person nluigi; 10.09.2015
comment
Всъщност според изходния код: github.com/jovyan/pythreejs/ blob/master/pythreejs/, авторът на кода очевидно също има проблеми с получаването на списък с екземпляри: TODO: figure out how to get a list of instances of Object3d - person nluigi; 10.09.2015
comment
А, добре, може би използва повторно обекта на списъка по някакъв начин. Как точно се държи версията +=? Не променя ли нищо в обекта scene? Пропускането на list извикването във вашия код и просто извършването на scene.children = scene.children + [point] работи ли? - person Blckknght; 10.09.2015
comment
И двата метода += като .append() изглежда добавят point към списъка, но няма добавен воксел към сцената при двукратно щракване. Пропускането на повикването list върши работа. - person nluigi; 10.09.2015

Според този проблем се оказва, че това е traitlets проблем. Модифицирането на елементи от self.children не задейства известие за събитие, освен ако не е дефиниран нов списък.

person nluigi    schedule 13.09.2015