Почему python dict.update () не возвращает объект? - PullRequest
114 голосов
/ 21 сентября 2009

Я пытаюсь сделать:

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 делает это для создания цепочки. Почему это не приемлемо в питоне?

Ответы [ 10 ]

187 голосов
/ 21 сентября 2009

Python в основном реализует прагматически окрашенный вариант разделения команд и запросов : мутаторы возвращают None (с прагматически вызванными исключениями, такими как pop ;-), поэтому их невозможно спутать с аксессорами (и в том же духе, присваивание не является выражением, разделение оператора-выражения существует и т. д.).

Это не значит, что не так много способов объединить вещи, когда вы действительно хотите, например, dict(a, **award_dict) создает новый диктант, очень похожий на тот, который вы, кажется, желаете, чтобы .update вернулся - так почему не использовать это, если вы действительно чувствуете, что это важно?

Редактировать : кстати, в вашем конкретном случае нет необходимости создавать a по пути, либо:

dict(name=name, description=desc % count, points=points, parent_award=parent,
     **award_dict)

создает один диктовку с точно такой же семантикой, что и ваша a.update(award_dict) (включая, в случае конфликтов, тот факт, что записи в award_dict переопределяют те, которые вы даете явно; чтобы получить другую семантику, т. Е. иметь явные записи, "выигравшие" в таких конфликтах, передать award_dict в качестве единственного позиционного аргумента, перед ключевыми словами и лишенного формы ** - dict(award_dict, name=name и т. д. и т.д.).

33 голосов
/ 21 сентября 2009

API Python, по соглашению, различает процедуры и функции. Функции вычисляют новые значения из их параметров (включая любой целевой объект); процедуры изменяют объекты и ничего не возвращают (т.е. они возвращают None). Таким образом, процедуры имеют побочные эффекты, а функции - нет. update - это процедура, поэтому она не возвращает значение.

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

bar = foo.reverse()

Если реверс (который переворачивает список на месте) также вернет список, пользователи могут подумать, что реверс возвращает новый список, который присваивается bar, и никогда не заметят, что foo также модифицируется. Делая обратный возврат None, они сразу распознают, что гистограмма не является результатом разворота, и будут смотреть ближе, каков эффект обратного хода.

13 голосов
/ 24 мая 2012
>>> 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_merge (a, b) изменит a.

Или, конечно, вы можете сделать все это встроенным:

>>> (lambda a,b: a.update(b) or a)({'a':1, 'b':3},{'c':5})
{'a': 1, 'c': 5, 'b': 3}
8 голосов
/ 14 января 2016

недостаточно репутации для комментария, оставленного сверху ответа

@ beardc, похоже, это не CPython. PyPy дает мне «TypeError: ключевые слова должны быть строками»

Решение с **kwargs работает только потому, что объединяемый словарь имеет только ключи типа string .

1012 * т.е. *

>>> 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 голосов
/ 14 ноября 2018

Это просто как:

(lambda d: d.update(dict2) or d)(d1)
5 голосов
/ 21 сентября 2009

Дело не в том, что это неприемлемо, а в том, что dicts не был реализован таким образом.

Если вы посмотрите на ORM Джанго, он широко использует цепочку. Его не обескураживают, вы даже можете наследовать от dict и только переопределять update для обновления и return self, если вы действительно этого хотите.

class myDict(dict):
    def update(self, *args):
        dict.update(self, *args)
        return self
2 голосов
/ 02 ноября 2016

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

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
0 голосов
/ 26 июня 2019

Для тех, кто опаздывает на вечеринку, я собрал некоторое время (Py 3.7), показывая, что методы, основанные на .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)
0 голосов
/ 23 июня 2017

Просто сам пробовал это в Python 3.4 (поэтому не смог использовать причудливый синтаксис {**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}
0 голосов
/ 18 апреля 2013
import itertools
dict_merge = lambda *args: dict(itertools.chain(*[d.iteritems() for d in args]))
...