2013-04-20 2 views
4

Я новичок в Python и, используя его для написания небольшого приложения, столкнулся с проблемой. Поскольку я не могу найти ничего подобного в Google, я, возможно, пропустил некоторые базовые концепции Python. Было бы здорово, если бы кто-то из вас, ребята, мог мне это объяснить.Параметры конструктора, повторно используемые для нового объекта?

Итак, вот минимальный рабочий пример. Пожалуйста, проигнорируйте имена классов и переменных, как сказано, это просто искусственный пример, но он показывает, что я имею в виду. Вы можете c & p в свою IDE и запустить его для себя.

class State(object): 
    def __init__(self, wrappers = []): 
     self.wrappers = wrappers 

    def add(self, name, items): 
     lst = KeyValList(name) 
     for item in items: 
      lst.add(item, items[item]) 
     self.wrappers.append(Wrapper(lst)) 

class Wrapper(object): 
    def __init__(self, kv_lst): 
     self.kv_lst = kv_lst 

class KeyValList(object): 
    def __init__(self, name, kvals = []): 
     self.name = str(name) 
     self.kvals = kvals 

    def add(self, key, value): 
     self.kvals.append(KeyVal(key, value)) 

class KeyVal(object): 
    def __init__(self, key, val): 
     self.key = str(key) 
     self.val = val 

Хорошо, это определение классов, которые я использую.
Справа внизу я пишу следующее:

state = State() 
kv = { "key1" : "1", "key2" : "2", "key3" : "3" } 
state.add("1st", kv) 
kv = { "keyX" : "X", "keyY" : "Y", "keyZ" : "Z" } 
state.add("2nd", kv) 

for wrap in state.wrappers: 
    print wrap.kv_lst.name, "-->", 
    for kv in wrap.kv_lst.kvals: 
     print "%s:%s" % (kv.key, kv.val), 
    print "" 

Как программист Java я ожидал бы следующий вывод:

1st --> key3:3 key2:2 key1:1 
2nd --> keyZ:Z keyY:Y keyX:X 

Однако, с помощью этой программы в Python, я получаю следующий вывод вместо:

1st --> key3:3 key2:2 key1:1 keyZ:Z keyY:Y keyX:X 
2nd --> key3:3 key2:2 key1:1 keyZ:Z keyY:Y keyX:X 

Почему в обоих списках содержатся все пары ключ/значение?

Я ожидаю, что проблема где-то в KeyValList классе, так как при отладке, когда метод state.add() вызываются в основной программе 2-го времени параметр KeyValList конструктора kvals содержит бывшие пары. но как это может быть? Я не предоставляю ничего этому параметру kvals, не следует ли его инициализировать пустым списком из-за значения по умолчанию для параметров?

PS: Я использую Python 2.7.3 для Windows.

+3

[изменяемые аргументы по умолчанию] (http://stackoverflow.com/questions/1132941/least-astonishment-in-python-the-mutable-default-argument) – katrielalex

ответ

1

Вместо использования изменяемых аргументов по умолчанию используйте Нет в качестве заполнителя и ваши функции/методы создают новые контейнеры для каждого вызова.

Изменить это:

class State(object): 
    def __init__(self, wrappers = []): 
     self.wrappers = wrappers 

Для этого:

class State(object): 
    def __init__(self, wrappers = None): 
     if wrappers is None: 
      wrappers = [] 
     self.wrappers = wrappers 

Существует хорошее объяснение этого вопроса в this blogpost.

+1

Большое спасибо, что мне очень помогло. Также ссылка, которую вы предоставили, очень хорошо объясняет «проблему». Когда я читал это, я сначала подумал, что это довольно странно в Python, но позже объясняется, почему это действительно имеет смысл. Благодарю. – Matthias

2

Это, по сути, экземпляр очень распространенной и, скорее, антиинтуитивной функции Python, mutable default argument. Суть в том, что аргументы по умолчанию - это свойства объекта функции , и, таким образом, если вы мутируете их в одном вызове функции, вы мутируете их для всех вызовов функций.

Этот пример немного сложнее стандартной переменной переменной аргумента по умолчанию, поскольку вы также передаете один и тот же объект как атрибут и экземпляр класса. Но в вашем коде есть только один объект списка - тот, который создан [], - и, следовательно, его мутация через атрибут экземпляра такая же, как и его изменение на объекте функции.

Другими словами, когда вы пишете

self.kvals.append(...) 

вы добавив некоторые данные для конкретного объекта списка. Какой объект списка? Тот, который создается [] в определении функции и, таким образом, тот же, который использует Python в качестве аргумента по умолчанию для функции.

+0

Спасибо, что, конечно, объясняет, в чем проблема в моем случае. Теперь я должен забить это в свой мозг, чтобы запомнить его на будущее :) – Matthias

Смежные вопросы