#python #list
Вопрос:
Мне что-то непонятно при добавлении объекта в список python. Поскольку исходный код слишком сложен и слишком длинен, чтобы публиковать его здесь, я разработал минимальный пример, чтобы показать эффект. Чтобы объяснить это, мне удалось переработать код, чтобы он функционировал должным образом и давал правильный результат. Но это потребовало от меня много времени для отладки. Причина, по которой это происходит, мне до сих пор не ясна. Поэтому я очень рад, если вы сможете объяснить мне, почему это происходит.
Ладно, пошли.
Вот рабочая версия кода. В конце вы можете видеть, что он выдает результат и работает так, как ожидалось:
import numpy as np
class foo():
def __init__(self):
self.x = np.random.random()
def move(self, value):
self.x = value
p = []
p2 = []
for _ in range(10):
z = foo()
z.move(4)
p2.append(z)
p = p2
for i in range(10):
print("Print x: {}".format(p[i].x))
Запустив код, я получаю следующий вывод (который по прошествии времени отличается, так как я вызываю функцию генератора случайных чисел). Но это работает!:
Print x: 4.18111236900313
Print x: 4.399997493230335
Print x: 4.594324823463079
Print x: 4.8205743285019045
Print x: 4.125578603746895
Print x: 4.430324972670274
Print x: 4.135255992051397
Print x: 4.9479568256336295
Print x: 4.789025666744436
Print x: 4.261426793909385
Сейчас я покажу вам первую версию кода, которая вызвала у меня много головной боли и много гнева. Пожалуйста, обратите внимание, что я немного изменил второй цикл for, потому что нашел его более логичным для себя. Я вызываю метод move()
напрямую и append()
в одной строке, а не в двух.
Вот версия:
import numpy as np
class foo():
def __init__(self):
self.x = np.random.random()
def move(self, value):
self.x = value
p = []
p2 = []
for _ in range(10):
z = foo()
p2.append(z.move(4)) # Here is the only change to the code!
p = p2
for i in range(10):
print("Print x: {}".format(p[i].x))
Но, запустив этот код, я получаю следующую ошибку:
AttributeError: 'NoneType' object has no attribute 'x'
и затем я обнаруживаю, что списки p и p2 не имеют элементов в качестве элементов.
Почему это?
Я очень рад понять, почему…
Редактировать
Как было предложено ниже, я понял, что move()
метод ничего не возвращает.
Поэтому я изменил код следующим образом:
import numpy as np
class foo():
def __init__(self):
self.x = np.random.random()
def move(self, value):
output = foo()
output.x = value
return output # <= Here the change
p = []
p2 = []
for _ in range(10):
z = foo()
p2.append(z.move(4))
p = p2
for i in range(10):
print("Print x: {}".format(p[i].x))
И это работает!
Еще одна важная вещь, которую мы узнали сегодня…
Комментарии:
1.
foo.move
не возвращаетсяfoo
; он возвращаетсяNone
(неявно, потому что нетreturn
оператора.
Ответ №1:
Создание копии объекта в первую очередь помогает контролировать ситуацию (неизменяемые простые объекты данных более полезны для удобства обслуживания, чем изменяемые объекты).
def move_immutable(self, value):
c = copy.copy(self) # add also "import copy" to the beginning of the file
c.x = value
return c
Если вы действительно хотите, чтобы все оставалось изменяемым, вы могли бы вместо вышеперечисленного сделать это в цикле вместо p2.добавить(z.переместить(4)):
z = foo()
z.move(4)
p2.append(z)
Комментарии:
1. Необходимо
import copy
импортировать каждый раз при вызовеmove_immutable
??2. Весь импорт должен быть в начале файла, но он там для ясности. Я немного отредактирую.
Ответ №2:
Я думаю, что вы действительно можете спросить, почему в приложении хранится то, что возвращает метод, а не объект. Это связано с тем, что при использовании метода в присваивании он немедленно выполняется, и вместо него используется возвращаемое значение.
Вот почему вы можете передавать методы функциям, которые предназначены только для приема возвращаемого ими значения.
Пример:
def square(x):
return x*x
def print_variable(variable_to_print):
print(variable_to_print)
print_variable(square(2))
В этом примере print_variable не получает квадрат(2), он получает значение 4.
Вот почему move должен вернуть себя, чтобы обновить x на месте так, как вы его используете.
Более простым и, возможно, менее спорным способом достижения этой цели может быть простое выполнение этого метода при создании экземпляра.
class foo(x=None):
def __init__(self):
self.x = np.random.random()
if x:
self.x = x
def move(self, value):
self.x = value
p = []
p2 = []
for _ in range(10):
p2.append(foo(4))
p = p2
Ответ №3:
Ваш z.move()
метод возвращается None
.
Если вы хотите изменить это поведение, измените его на возврат self
:
def move(self, value):
self.x = value
return self
Комментарии:
1. Это «исправляет» проблему, но нарушает довольно разумное соглашение о том, что метод должен либо изменять свой объект, либо возвращать значение, но не то и другое вместе.
2. цитата из «конвенции», пожалуйста?