Почему python dict.update () не возвращает объект?

139

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

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

Пол Тарьян
источник
14
* TL; DRnewdict = dict(dict001, **dict002)
dreftymac
2
@dreftymac, это не работает в понимании, хотя.
alancalvitti
@alancalvitti Да, это действительно одно из важных замечаний.
dreftymac

Ответы:

219

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

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

Изменить : кстати, нет необходимости, в вашем конкретном случае, чтобы создать 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т. д. и т. д.).

Алекс Мартелли
источник
Ну, это создаст другой словарь после того, как я должен был сделать. Я хотел создать dict, а затем добавить кучу других значений, а затем передать его функции.
Пол Тарьян
@Paul, и это именно то, что вы делаете - с двумя утверждениями (гораздо более читаемыми, чем вложенный способ, который вы хотели), которые для вас «казались действительно громоздкими». Редактирование моего ответа, чтобы показать, как избежать создания aвообще, между прочим,
Алекс Мартелли
1
Оригинальное решение не является надежным. Если award_dict содержит ключи, уже указанные, SyntaxError будет брошен для повторного аргумента ключевого слова. Решение jamylak dict (itertools.chain (d1.iteritems (), .. d <n> .iteritems ())) не только работает в случае, когда словари имеют дублирующиеся ключи, но также легко позволяет объединить несколько словарей с dicts позже в цепь имеет приоритет для конечного значения.
Мэтт
2
Кроме того, если ключи в award_dict не строковые, интерпретатор выдастTypeError
kunl
3
dict(old_dict, old_key=new_value)не будет бросать несколько значений для ключевого слова и возвращать новый dict.
Charmy
35

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

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

bar = foo.reverse()

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

Мартин против Лёвиса
источник
1
Спасибо. Почему бы не повернуть вспять и дать возможность не делать это на месте? Производительность? делать reverse(foo)чувствует себя странно.
Пол Тарьян
Добавление параметра было бы неуместным: это изменило бы природу метода в зависимости от параметра. Однако методы должны действительно иметь фиксированные возвращаемые типы (к сожалению, в некоторых случаях это правило нарушается). Легко создать обратную копию: просто сделайте копию (используя bar=foo[:]), а затем восстановите копию.
Мартин против Лёвиса
3
Я думаю, что причина в явности. В bar = foo.reverse(), вы могли бы подумать, что fooэто не изменено. Чтобы избежать путаницы, у вас есть и то, foo.reverse()и другое bar = reversed(foo).
Роберто Бонваллет
Что не так с изменением природы параметра на основе параметра?
Жюльен
22

Это легко как:

(lambda d: d.update(dict2) or d)(d1)
Костя Головешко
источник
15
>>> 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.

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

>>> (lambda a,b: a.update(b) or a)({'a':1, 'b':3},{'c':5})
{'a': 1, 'c': 5, 'b': 3}
Криспин Веллингтон
источник
10
-1 lambdaне следует использовать , как , что, вместо того, чтобы использовать обычную функцию defвместо
jamylak
8
Даже не нужно лямбда, просто используйтеa.update(b) or a
Pycz
10

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

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

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

т.е.

>>> 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не было реализовано таким образом.

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

class myDict(dict):
    def update(self, *args):
        dict.update(self, *args)
        return self
Эстебан Кюбер
источник
Спасибо, это может исправлять dict, я просто хотел знать, почему dict () не разрешил эту функцию сама (поскольку это так просто, как вы демонстрируете). Патч Django диктует вот так?
Пол Тарьян
2

как можно ближе к вашему предложенному решению

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
Матус
источник
1

Для тех, кто опаздывает на вечеринку, я собрал время (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)
norok2
источник
0
import itertools
dict_merge = lambda *args: dict(itertools.chain(*[d.iteritems() for d in args]))
Matt
источник
0

Просто пробовал это сам в 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}
халява
источник