Tại sao một python dict.update () trả lại đối tượng?


139

Tôi đang cố gắng làm:

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

Nhưng nếu cảm thấy thực sự cồng kềnh trong chức năng, và tôi sẽ thực hiện:

        return self.add_award({
            "name" : name,
            "description" : desc_string % count,
            "points" : points,
            "parent_award" : parent,
        }.update(award_dict), siteAlias, alias).award

Tại sao cập nhật không trả về đối tượng để bạn có thể xâu chuỗi?

JQuery làm điều này để làm chuỗi. Tại sao nó không được chấp nhận ở python?


14
* TL; DRnewdict = dict(dict001, **dict002)
dreftymac

2
@dreftymac, điều đó không hoạt động trong lĩnh vực hiểu.
alancalvitti

@alancalvitti Vâng, đó thực sự là một cảnh báo hợp lệ để chỉ ra.
dreftymac

Câu trả lời:


219

Python chủ yếu thực hiện một hương vị thực tế của phân tách truy vấn lệnh : trình biến đổi trở lại None(với các ngoại lệ được tạo ra một cách thực tế như pop;-) vì vậy chúng có thể bị nhầm lẫn với các hàm truy cập (và trong cùng một hướng, phép gán không phải là biểu thức, câu lệnh tách biệt -expression là có, và vv).

Điều đó không có nghĩa là không có nhiều cách để hợp nhất mọi thứ khi bạn thực sự muốn, ví dụ, dict(a, **award_dict)tạo ra một chính tả mới giống như điều bạn mong muốn được .updatetrả lại - vậy tại sao bạn không sử dụng THAT nếu bạn thực sự cảm thấy nó quan trọng ?

Chỉnh sửa : btw, không cần, trong trường hợp cụ thể của bạn, để tạo atrên đường đi, hoặc:

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

tạo ra một lệnh chính xác với cùng một ngữ nghĩa như của bạn a.update(award_dict)(bao gồm, trong trường hợp có xung đột, thực tế là các mục award_dictghi đè lên những gì bạn đang đưa ra một cách rõ ràng; để có được các ngữ nghĩa khác, nghĩa là có các mục rõ ràng "chiến thắng" các xung đột đó, vượt qua award_dictnhư là đối số vị trí duy nhất , trước các từ khóa và bereft của **mẫu - dict(award_dict, name=namev.v.).


Chà, điều đó sẽ tạo ra một từ điển khác sau khi tôi phải làm một. Tôi muốn tạo ra một dict, và sau đó thêm một loạt các giá trị khác, và sau đó đưa nó vào một hàm.
Paul Tarjan

@Paul, và đó chính xác là những gì bạn đang làm - với hai câu lệnh (dễ đọc hơn nhiều so với cách lồng nhau mà bạn muốn) mà theo bạn "cảm thấy thực sự cồng kềnh". Chỉnh sửa câu trả lời của tôi để chỉ ra cách tránh tạo ahoàn toàn, btw,
Alex Martelli

1
Giải pháp ban đầu không mạnh mẽ. Nếu Prize_dict chứa các khóa đã được chỉ định, SyntaxError sẽ được ném cho một đối số từ khóa lặp lại. dict giải pháp của jamylak (itertools.chain (d1.iteritems (), .. d <n> .iteritems ())) không chỉ hoạt động trong trường hợp từ điển có khóa trùng lặp mà còn dễ dàng cho phép bạn hợp nhất nhiều từ điển với từ điển sau này trong chuỗi được ưu tiên cho giá trị cuối cùng.
Matt

2
Ngoài ra, nếu các khóa trong Prize_dict không phải là chuỗi, trình thông dịch sẽ ném mộtTypeError
kunl

3
dict(old_dict, old_key=new_value)sẽ không ném nhiều giá trị cho từ khóa và trả về dict mới.
Charmy

35

API của Python, theo quy ước, phân biệt giữa các thủ tục và hàm. Các hàm tính toán các giá trị mới trong số các tham số của chúng (bao gồm mọi đối tượng đích); thủ tục sửa đổi các đối tượng và không trả lại bất cứ điều gì (tức là chúng trả về Không). Vì vậy, các thủ tục có tác dụng phụ, chức năng không. cập nhật là một thủ tục, do đó nó không trả về giá trị.

Động lực để làm theo cách đó là nếu không, bạn có thể nhận được tác dụng phụ không mong muốn. Xem xét

bar = foo.reverse()

Nếu đảo ngược (đảo ngược danh sách tại chỗ) cũng sẽ trả về danh sách, người dùng có thể nghĩ rằng ngược lại trả về một danh sách mới được gán cho thanh và không bao giờ nhận thấy rằng foo cũng được sửa đổi. Bằng cách thực hiện trả lại ngược Không, họ ngay lập tức nhận ra rằng thanh đó không phải là kết quả của sự đảo ngược và sẽ xem xét kỹ hơn về tác động của việc đảo ngược là gì.


1
Cảm ơn bạn. Tại sao không đảo ngược cũng cung cấp tùy chọn để không làm điều đó tại chỗ? Hiệu suất? làm reverse(foo)cảm thấy kỳ lạ.
Paul Tarjan

Thêm một tùy chọn sẽ không phù hợp: nó sẽ thay đổi bản chất của phương thức tùy thuộc vào một tham số. Tuy nhiên, các phương thức nên thực sự có các kiểu trả về cố định (không may, có trường hợp quy tắc này bị phá vỡ). Thật dễ dàng để tạo một bản sao được hoàn nguyên: chỉ cần tạo một bản sao (sử dụng bar=foo[:]), sau đó hoàn nguyên bản sao.
Martin v. Löwis

3
Tôi nghĩ rằng lý do là nhân chứng. Trong bar = foo.reverse(), bạn có thể nghĩ rằng fookhông được sửa đổi. Để tránh nhầm lẫn, bạn có cả foo.reverse()bar = reversed(foo).
Roberto Bonvallet

Có gì sai khi thay đổi bản chất của một tham số dựa trên một tham số?
Julien


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}

Lưu ý rằng cũng như trả về dict đã hợp nhất, nó sửa đổi tham số đầu tiên tại chỗ. Vì vậy, dict_merge (a, b) sẽ sửa đổi a.

Hoặc, tất nhiên, bạn có thể làm tất cả nội tuyến:

>>> (lambda a,b: a.update(b) or a)({'a':1, 'b':3},{'c':5})
{'a': 1, 'c': 5, 'b': 3}

10
-1 lambdakhông nên được sử dụng như vậy, thay vì sử dụng chức năng thông thường defthay vì
jamylak

8
Thậm chí không cần lambda, chỉ cần sử dụnga.update(b) or a
Pycz 7/07/17

10

không đủ danh tiếng để bình luận trên câu trả lời hàng đầu

@beardc điều này dường như không phải là điều CPython. PyPy cho tôi "TypeError: từ khóa phải là chuỗi"

Giải pháp **kwargschỉ hoạt động vì từ điển được sáp nhập chỉ có các khóa có kiểu chuỗi .

I E

>>> dict({1:2}, **{3:4})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: keyword arguments must be strings

đấu với

>>> dict({1:2}, **{'3':4})
{1: 2, '3': 4}

5

Không phải là nó không được chấp nhận, mà là không dictsđược thực hiện theo cách đó.

Nếu bạn nhìn vào ORM của Django, nó sẽ sử dụng nhiều chuỗi. Nó không nản lòng, bạn thậm chí có thể kế thừa dictvà chỉ ghi đè updateđể thực hiện cập nhật và return self, nếu bạn thực sự muốn nó.

class myDict(dict):
    def update(self, *args):
        dict.update(self, *args)
        return self

Cảm ơn bạn, điều này có thể vá lỗi chính tả, tôi chỉ muốn biết tại sao dict () không cho phép chức năng này (vì nó dễ như bạn chứng minh). Liệu Django vá dict như thế này?
Paul Tarjan

2

càng gần với giải pháp đề xuất của bạn càng tốt

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

Đối với những người đến muộn trong bữa tiệc, tôi đã sắp xếp thời gian cùng nhau (Py 3.7), cho thấy .update()các phương thức dựa trên trông nhanh hơn một chút (~ 5%) khi đầu vào được bảo quản và nhanh hơn đáng kể (~ 30%) khi chỉ cập nhật tại chỗ .

Như thường lệ, tất cả các điểm chuẩn nên được thực hiện với một hạt muối.

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)

Thời gian cho các hoạt động tại chỗ phức tạp hơn một chút, do đó, nó sẽ cần phải được sửa đổi cùng với một hoạt động sao chép bổ sung (thời gian đầu tiên chỉ để tham khảo):

%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
import itertools
dict_merge = lambda *args: dict(itertools.chain(*[d.iteritems() for d in args]))

0

Chỉ cần thử bản thân mình trong Python 3.4 (vì vậy không thể sử dụng {**dict_1, **dict_2}cú pháp ưa thích ).

Tôi muốn có thể có các khóa không phải chuỗi trong từ điển cũng như cung cấp một lượng từ điển tùy ý.

Ngoài ra, tôi muốn tạo một từ điển mới vì vậy tôi đã chọn không sử dụng collections.ChainMap(lý do dict.updateban đầu tôi không muốn sử dụng .

Đây là những gì tôi đã kết thúc bằng văn bản:

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}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.