Profile

coolwolf0: (Default)
coolwolf0

June 2025

S M T W T F S
1234 567
891011121314
15161718192021
22232425262728
2930     

Custom Text

Apr. 19th, 2020

Я уже предупреждал, что записи на тему программирования будут очень узко специфичными? Ну тогда звиняйте, хлопц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, [])

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



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

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

Итак, метод "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': []}

Expand Cut Tags

No cut tags

Style Credit

OSZAR »