coolwolf0 - Северный наблюдатель (coolwolf0) wrote,
coolwolf0 - Северный наблюдатель
coolwolf0

Category:

Обманчивая closure, или "не верь глазам своим"

Я уже предупреждал, что записи на тему программирования будут очень узко специфичными? Ну тогда звиняйте, хлопцi, буду говорить на соответствующем языке.

Начнём издалека. В Пайтоне есть такой тип объектов "dictionary" - ассоциативный массив. Аналогичный тип данных имеется в большинстве современных скриптовых языков - в Перле, PHP, JavaScript... Но Пайтон не был бы самим собой, если бы не поставлял изячные методы для работы со своими объектами. Так тот же dictionary можно создать по массиву ключей (ибо Пайтон очень болезненно воспринимает обращение к несуществующему элементу). В простейшем случае это выглядит так:

keys = ['original', 'merged', 'total']
statistics = dict.fromkeys(keys)

В результате получится ассоциативный массив, в котором уже существуют элементы, адресуемые "keys". По умолчанию элементам присваиваются значения None - то есть ничего.

{'total': None, 'original': None, 'merged': None}

Теперь перейдём к конкретике. Я сейчас вплотную занят расширением функционала имеющегося сложнючего скрипта. Суть изменений состоит в сборе статистики. А когда речь идёт о статистике, все эти программистские None не годятся - как прикажете к не-целому типу данных прибавить целое число? Поэтому в первой версии программы я использовал очень милую фичу всё того же метода - задание начальных значений.

keys = ['original', 'merged', 'total']
statistics = dict.fromkeys(keys, 0)

Вуаля - получаем значения "0" и от них можно смело начинать суммировать "рост прироста" или ещё какую статистическую цифирь.

{'total': 0, 'original': 0, 'merged': 0}

Но тут появилась необходимость не только считать, но и выдавать значения данных. То есть пользователь захотел увидеть, "из чего же сделаны наши мальчишки?". Ну ладно, подумал я, научим старый код новым трюкам - будем хранить не счётчик значений, а массив самих значений. Для этого в качестве начальной величины был использован "пустой массив":

keys = ['original', 'merged', 'total']
statistics = dict.fromkeys(keys, [])

И вот тут настал момент, когда программист, увидев полученные данные, начинает лезть на стены и пытаться кого-нибудь убить в особо извращённой форме. Дело в том, что ВСЕ категории статистических данных получили ОДИНАКОВЫЙ набор значений. Уже догадались почему? Ладно, под катом приведу вам намекающий пример и там же разгадку.

[Spoiler (click to open)]

Итак, в Пайтоне, как и в других похожих языках программирования, параметры в подпрограмму передаются по значению, но это действует только для скаляров. Объекты, передают "по значению" свой указатель и в результате принимают на себя все изменения, какие только удумает подпрограмма. То есть передав текстовую строку мы не боимся, что она может испортиться, но во про массив такое сказать нельзя.

Ну, уже догадались? Ладно не буду томить.

Итак, метод "fromkeys" получает в качестве второго аргумента начальное значение, которое будет присвоено всем элементам dictionary. А какое значение я ему передал? Ссылку на пустой объект. Метод же не Спиноза. Он не обязан делать deep copy переданному объекту. Это я себе злой Буратино, подумал, что создал closure, а на деле получил тупую ссылку на тот же объект. В результате все пункты массива статистики ссылаются на один и тот же начальный массив и в него же коллективно гадят.

keys = ['original', 'merged', 'total']
statistics = dict.fromkeys(keys, [])
statistics['total'] += [8]

{'total': [8], 'original': [8], 'merged': [8]}

Мораль: не гонялся бы ты, поп, за дешевизной. Взял бы, да проинициализировал КАЖДЫЙ элемент в цикле - там бы и получилась самая настоящая closure.

keys = ['original', 'merged', 'total']
statistics = {}
for k in keys:
    statistics[k]=[]
statistics['total'] += [8]

{'total': [8], 'original': [], 'merged': []}
Tags: программирование
Subscribe

  • Куда мы катимся...

    Попытался найти аудиокнигу на торрентах - её просто нет. Пошукал по своим старым ссылкам и получил два варианта: либо в онлайне её нет, либо ... нет…

  • Очень плохой отзыв о гостинице

    Гостиницу в Бухаресте мы выбрали по двум критериям: не самый плохой рейтинг и поближе к историческому центру города. Вышло, что этот район мягко…

  • Выезд из Сибиу, переезд в Бухарест

    Так получилось, что накануне вечером попасть на смотровую площадку городской башни Сибиу мы не успели: служащая с немецкой непреклонностью заявила,…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 0 comments