Список Python должен быть пустым при инициализации экземпляра класса, но это не так.Зачем? - PullRequest
5 голосов
/ 27 декабря 2010

Я хотел бы создать экземпляры класса, содержащие список, который по умолчанию пуст; вместо того, чтобы позже установить этот список в окончательный полный список, я хотел бы последовательно добавлять элементы в него. Вот пример кода, иллюстрирующего это:

#!/usr/bin/python

class test:
    def __init__(self, lst=[], intg=0):
        self.lista   = lst
        self.integer = intg

name_dict = {}
counter   = 0

for name in ('Anne', 'Leo', 'Suzy'):
    counter += 1

    name_dict[name] = test()
    name_dict[name].integer += 1
    name_dict[name].lista.append(counter)

    print name, name_dict[name].integer, name_dict[name].lista

Когда я запустил вышеупомянутую программу, я ожидал получить

Энн 1 [1]
Лев 1 [2]
Сьюзи 1 [3]

как я и предполагал, lista всегда инициализируется пустым списком.

Вместо этого я получил следующее:

Энн 1 [1]
Лев 1 [1, 2]
Сьюзи 1 [1, 2, 3]

Если я заменим self.lista = lst на self.lista = [], он будет работать нормально, точно так же, как когда я добавляю строку name_dict[name].lista = [] в цикл for.

Почему содержимое списков предыдущих объектов сохраняется, а их значения integer - нет? Я довольно новичок в Python, поэтому было бы здорово, если бы кто-то мог указать мне, где мои мысли / предположения сбились с пути.

Заранее большое спасибо за ваши ответы.

Ответы [ 4 ]

12 голосов
/ 27 декабря 2010

Очень плохая идея использовать изменяемый объект в качестве значения по умолчанию, как вы делаете здесь:

def __init__(self, lst=[], intg=0):
     # ...

Измените его на:

def __init__(self, lst=None, intg=0):
     if lst is None:
         lst = []
     # ...

Причина, по которойваша версия не работает в том, что пустой список создается только один раз, когда функция определена, а не каждый раз, когда функция вызывается.

В некоторых реализациях Python вы можете увидеть значение значений по умолчаниюфункция путем проверки значения func_defaults:

print test.__init__.func_defaults
name_dict[name] = test()
# ...

Вывод:

([],)
Anne 1 [1]
([1],)
Leo 1 [1, 2]
([1, 2],)
Suzy 1 [1, 2, 3] 
4 голосов
/ 27 декабря 2010

Проблема заключается в этой строке:

def __init__(self, lst=[], intg=0):

Вы не должны использовать список в качестве аргумента по умолчанию. При первом вызове __init__ без указания lst интерпретатор Python определит пустой список []. Последующие вызовы функции будут работать в том же списке , если lst не указан, без объявления нового списка. Это вызывает странные проблемы.

Вместо этого следует использовать значение по умолчанию None и добавить проверку в начале функции:

def __init__(self, lst=None, intg=0):
    if lst is None:
        lst = []

См. в этом посте для получения более подробной информации. Цитирую пост:

Аргументы по умолчанию оцениваются в время определения функции, поэтому они постоянный через звонки. Это имеет некоторые интересная (и запутанная) сторона последствия. Пример:

>>> def foo(d=[]):
...     d.append('a')
...     return d

Если ты не пробовал это раньше, ты вероятно ожидать, что foo всегда вернется ['a']: должно начинаться с пустого список, добавьте к нему «а» и вернитесь. Вот что на самом деле делает:

>>> foo() ['a']
>>> foo() ['a', 'a']
>>> foo() ['a', 'a', 'a']

Это потому, что значение по умолчанию для d распределяется, когда функция создано, а не когда это называется. каждый время вызова функции, значение все еще болтается с последнего вызов. Это становится еще более странным, если вы бросать темы в смесь. Если два разные потоки исполняют функция в то же время, и один из они изменяют аргумент по умолчанию, они оба увидят изменения.

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

>>> def foo2(d=0):
...     d += 1
...     return d

тогда он всегда вернет 1. (Разница здесь в том, что в foo2, переменная d переназначается, в то время как в foo его значение было изменено.)

1 голос
/ 27 декабря 2010

Проблема заключается в аргументе конструктора по умолчанию. Вам следует прочитать этот вопрос, чтобы найти ответ на свой вопрос: «Наименьшее удивление» в Python: изменяемый аргумент по умолчанию

0 голосов
/ 12 августа 2017

Python оценивает аргументы по умолчанию один раз при определении функции:

def init_a():
    """Initialize a."""
    print("init_a")
    return 1


def test(a_parameter=init_a()):
    """A test function."""
    print("Execute test")

print("whatever")
test()
test()

дает

init_a
whatever
Execute test
Execute test

Таким образом, ваш список определяется один раз.Вы используете тот же список, что и раньше.Вот почему вы должны использовать шаблон

def a_function(a_parameter=None):  # Create 'None' once
    if a_parameter is None:
        a_parameter = []  # Create a new list - each time
...