Почему python dict.update() не возвращает объект?
Я пытаюсь сделать:
award_dict = {
"url" : "http://facebook.com",
"imageurl" : "http://farm4.static.flickr.com/3431/3939267074_feb9eb19b1_o.png",
"count" : 1,
}
def award(name, count, points, desc_string, my_size, parent) :
if my_size > count :
a = {
"name" : name,
"description" : desc_string % count,
"points" : points,
"parent_award" : parent,
}
a.update(award_dict)
return self.add_award(a, siteAlias, alias).award
Но если в функции было действительно громоздко, и я бы скорее сделал:
return self.add_award({
"name" : name,
"description" : desc_string % count,
"points" : points,
"parent_award" : parent,
}.update(award_dict), siteAlias, alias).award
Почему обновление не возвращает объект, чтобы вы могли цепью?
JQuery делает это для цепочки. Почему это не приемлемо для python?
Ответы
Ответ 1
Python в основном реализует прагматически окрашенный аромат разделение команд-запросов: mutators возвращают None
(с прагматически индуцированными исключениями, такими как pop
; -), поэтому их нельзя путать с аксессуарами (и в том же ключе назначение не является выражением, существует разделение выражения выражения и т.д.).
Это не означает, что не существует много способов слияния вещей, когда вы действительно этого хотите, например, dict(a, **award_dict)
делает новый dict так же, как тот, который вам кажется желательным .update
возвращен - так почему не используйте ЭТО, если вы действительно считаете это важным?
Изменить: btw, не нужно, в вашем конкретном случае, создать a
на этом пути:
dict(name=name, description=desc % count, points=points, parent_award=parent,
**award_dict)
создает единый dict с точно такой же семантикой, что и ваш a.update(award_dict)
(включая, в случае конфликтов, тот факт, что записи в award_dict
переопределяют те, которые вы даете явно, для получения другой семантики, т.е. для имеют явные записи, "выигрывающие" такие конфликты, передают award_dict
как единственный позиционный arg, перед ключевым словом, и лишены формы **
- dict(award_dict, name=name
и т.д. и т.д.).
Ответ 2
API Python, по договоренности, различает процедуры и функции. Функции вычисляют новые значения из своих параметров (включая любой целевой объект); процедуры изменяют объекты и ничего не возвращают (т.е. они возвращают None). Таким образом, процедуры имеют побочные эффекты, функции - нет. update - это процедура, поэтому она не возвращает значение.
Мотивация для этого заключается в том, что в противном случае вы можете получить нежелательные побочные эффекты. Рассмотрим
bar = foo.reverse()
Если реверс (который меняет список на месте) также вернет список, пользователи могут подумать, что обратный возвращает новый список, который присваивается бару, и никогда не замечает, что foo также изменяется. Сделав обратный возврат None, они сразу же узнают, что бар не является результатом разворота, и будет выглядеть более близким, каков эффект обратного.
Ответ 3
>>> dict_merge = lambda a,b: a.update(b) or a
>>> dict_merge({'a':1, 'b':3},{'c':5})
{'a': 1, 'c': 5, 'b': 3}
Обратите внимание, что, возвращая объединенный dict, он изменяет первый параметр на месте. Так dict_merge (a, b) изменит a.
Или, конечно же, вы можете сделать все это inline:
>>> (lambda a,b: a.update(b) or a)({'a':1, 'b':3},{'c':5})
{'a': 1, 'c': 5, 'b': 3}
Ответ 4
не хватает репутации для комментария слева на верхний
@beardc это не похоже на вещь CPython. PyPy дает мне "TypeError: ключевые слова должны быть строками"
Решение с **kwargs
работает только потому, что словарь, который должен быть объединен, имеет ключи строки типа.
то есть.
>>> dict({1:2}, **{3:4})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: keyword arguments must be strings
против
>>> dict({1:2}, **{'3':4})
{1: 2, '3': 4}
Ответ 5
Не то, чтобы это было неприемлемо, а скорее, что dicts
не были реализованы таким образом.
Если вы посмотрите на Django ORM, он широко использует цепочку. Его не обескураживают, вы даже можете унаследовать от dict
и только переопределять update
для обновления и return self
, если вы действительно этого хотите.
class myDict(dict):
def update(self, *args):
dict.update(self, *args)
return self
Ответ 6
Это легко как:
(lambda d: d.update(dict2) or d)(d1)
Ответ 7
как можно ближе к предлагаемому решению, поскольку я мог бы получить
from collections import ChainMap
return self.add_award(ChainMap(award_dict, {
"name" : name,
"description" : desc_string % count,
"points" : points,
"parent_award" : parent,
}), siteAlias, alias).award
Ответ 8
import itertools
dict_merge = lambda *args: dict(itertools.chain(*[d.iteritems() for d in args]))
Ответ 9
Просто попробовал это сам в Python 3.4 (так что не смог использовать синтаксис fancy {**dict_1, **dict_2}
).
Я хотел иметь возможность иметь нестрочные ключи в словарях, а также предоставлять произвольное количество словарей.
Кроме того, я хотел создать новый словарь, поэтому я решил не использовать collections.ChainMap
(вроде того, что я не хотел использовать dict.update
изначально.
Вот что я написал:
def merge_dicts(*dicts):
all_keys = set(k for d in dicts for k in d.keys())
chain_map = ChainMap(*reversed(dicts))
return {k: chain_map[k] for k in all_keys}
merge_maps({'1': 1}, {'2': 2, '3': 3}, {'1': 4, '3': 5})
# {'1': 4, '3': 5, '2': 2}
Ответ 10
Для тех, кто опаздывает на вечеринку, я собрал некоторое время (Py 3.7), показывая, что .update()
основе .update()
выглядят немного (~ 5%) быстрее, когда сохраняются входные данные, и заметно (~ 30%) быстрее, когда просто обновление на месте.
Как обычно, все критерии должны быть взяты с крошкой соли.
def join2(dict1, dict2, inplace=False):
result = dict1 if inplace else dict1.copy()
result.update(dict2)
return result
def join(*items):
iter_items = iter(items)
result = next(iter_items).copy()
for item in iter_items:
result.update(item)
return result
def update_or(dict1, dict2):
return dict1.update(dict2) or dict1
d1 = {i: str(i) for i in range(1000000)}
d2 = {str(i): i for i in range(1000000)}
%timeit join2(d1, d2)
# 258 ms ± 1.47 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit join(d1, d2)
# 262 ms ± 2.97 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit dict(d1, **d2)
# 267 ms ± 2.74 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit {**d1, **d2}
# 267 ms ± 1.84 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Временные интервалы для операций на месте немного сложнее, поэтому их необходимо будет модифицировать вместе с дополнительной операцией копирования (первый тайминг только для справки):
%timeit dd = d1.copy()
# 44.9 ms ± 495 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit dd = d1.copy(); join2(dd, d2)
# 296 ms ± 2.05 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit dd = d1.copy(); join2(dd, d2, True)
# 234 ms ± 1.02 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit dd = d1.copy(); update_or(dd, d2)
# 235 ms ± 1.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Ответ 11
Для тех, кто опаздывает на вечеринку, я собрал время (Py 3.7):
def join2(dict1, dict2, inplace=False):
result = dict1 if inplace else dict1.copy()
result.update(dict2)
return result
def join(*items):
iter_items = iter(items)
result = next(iter_items).copy()
for item in iter_items:
result.update(item)
return result
def update_or(dict1, dict2):
return dict1.update(dict2) or dict1
d1 = {i: str(i) for i in range(1000000)}
d2 = {str(i): i for i in range(1000000)}
%timeit join2(d1, d2)
# 306 ms ± 2.34 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit join(d1, d2)
# 316 ms ± 14.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit dict(d1, **d2)
# 423 ms ± 2.55 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit {**d1, **d2}
# 427 ms ± 3.08 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
join2(inplace=True)
и update_or()
несколько разные звери