Nội dung từ điển hủy cấu trúc-ràng buộc


84

Tôi đang cố gắng 'hủy cấu trúc' một từ điển và liên kết các giá trị với các tên biến sau các khóa của nó. Cái gì đó như

params = {'a':1,'b':2}
a,b = params.values()

Nhưng vì từ điển không được sắp xếp theo thứ tự, không có gì đảm bảo rằng params.values()sẽ trả về các giá trị theo thứ tự (a, b). Có cách nào hay để làm điều này không?


3
Lười biếng? Có lẽ ... nhưng tất nhiên tôi đã chỉ ra trường hợp đơn giản nhất để minh họa. Lý tưởng nhất là tôi muốn có like cho x trong params.items: eval ('% s =% f'% x) nhưng tôi đoán eval () không cho phép gán.
hatmatrix

7
@JochenRitzel Tôi khá chắc chắn hầu hết người dùng của ES6 (JavaScript) thích cú pháp đối tượng destructuring mới: let {a, b} = params. Nó nâng cao khả năng đọc và hoàn toàn phù hợp với bất cứ điều gì Zen bạn muốn nói đến.
Andy

8
@Andy Tôi đối tượng tình yêu destructuring trong JS. Thật là một cách sạch sẽ, đơn giản và dễ đọc để trích xuất một số khóa từ một câu lệnh. Tôi đến đây với hy vọng tìm được thứ gì đó tương tự bằng Python.
Rotareti 17/02/17

2
Tôi cũng thích cấu trúc đối tượng ES6, nhưng tôi e rằng nó không thể hoạt động trong Python vì lý do tương tự đối tượng Bản đồ của ES6 không hỗ trợ cấu trúc hủy. Các phím không chỉ là chuỗi trong ES6 Map và Python dict. Ngoài ra, mặc dù tôi yêu thích kiểu cấu trúc đối tượng "pluck" trong ES6, nhưng kiểu gán này không đơn giản. Những gì đang xảy ra ở đây? let {a: waffles} = params. Phải mất vài giây để tìm ra nó ngay cả khi bạn đã quen với nó.
John Christopher Jones

1
@ naught101 Hữu ích về mặt tình huống với những bất ngờ khó chịu để đánh đổi. Đối với người dùng: Trong Python, bất kỳ đối tượng nào cũng có thể cung cấp các phương thức str / repr của riêng nó. Nó thậm chí có thể hấp dẫn để làm điều này cho các đối tượng chính hơi phức tạp (ví dụ: các bộ giá trị được đặt tên) để tuần tự hóa JSON dễ dàng hơn. Bây giờ bạn đang gãi đầu tại sao bạn không thể phá hủy một khóa theo tên. Ngoài ra, tại sao điều này hoạt động cho các mục nhưng không hoạt động cho các mục? Rất nhiều thư viện thích tập tin đính kèm. Đối với người triển khai, tính năng ES6 này gây nhầm lẫn giữa các ký hiệu (tên có thể ràng buộc) và chuỗi: hợp lý trong JavaScript nhưng Python có nhiều ý tưởng phong phú hơn trong cách chơi. Ngoài ra, nó sẽ chỉ trông xấu xí.
John Christopher Jones

Câu trả lời:


10

Nếu bạn sợ trong những vấn đề liên quan đến việc sử dụng của người dân địa phương điển và bạn thích làm theo chiến lược ban đầu của bạn, Từ điển Ordered từ python 2.7 và 3.1 collections.OrderedDicts phép bạn khôi phục bạn từ điển mục theo thứ tự mà họ lần đầu tiên được chèn


@Stephen bạn là người may mắn vì OrderedDicts đã được chuyển đến python 2,7 từ python 3.1
Joaquin

Tôi mong đợi nó...! Tư tưởng đó là luôn luôn là một điểm bám cho Python cho tôi (mà điển ra lệnh không đến nạp với pin khác)
hatmatrix

4
Hiện tại, trong 3.5+, tất cả các từ điển đều được đặt hàng. Điều này chưa được "đảm bảo", có nghĩa là nó có thể thay đổi.
Charles Merriam

3
Nó được đảm bảo từ 3.6+.
naught101 25/09/09

118
from operator import itemgetter

params = {'a': 1, 'b': 2}

a, b = itemgetter('a', 'b')(params)

Thay vì các hàm lambda phức tạp hoặc đọc hiểu từ điển, cũng có thể sử dụng một thư viện tích hợp sẵn.


9
Đây có lẽ nên là câu trả lời được chấp nhận, vì đó là cách Pythonic tốt nhất để làm điều đó. Bạn thậm chí có thể mở rộng câu trả lời để sử dụng attrgettertừ cùng một mô-đun thư viện chuẩn, hoạt động cho các thuộc tính đối tượng ( obj.a). Đây là một điểm khác biệt chính với JavaScript, ở đâu obj.a === obj["a"].
John Christopher Jones

Nếu khóa không tồn tại, KeyErrorngoại lệ từ điển sẽ được nâng lên
Tasawar Hussain

2
Nhưng bạn đang gõ a và b hai lần trong câu lệnh hủy
Otto

1
@JohnChristopherJones Điều đó có vẻ không tự nhiên đối với tôi, việc sử dụng những thứ hiện có không có nghĩa là nó làm cho nó dễ hiểu. Tôi nghi ngờ nhiều người sẽ hiểu ngay lập tức trong một mã thực. Mặt khác, như được đề xuất a, b = [d[k] for k in ('a','b')]là cách tự nhiên / dễ đọc hơn (hình thức phổ biến hơn). Đây vẫn là một câu trả lời thú vị, nhưng đó không phải là giải pháp đơn giản nhất.
cglacet

@cglacet Tôi nghĩ nó phụ thuộc vào số lần bạn phải đọc / thực hiện điều đó. Nếu bạn thường xuyên chọn ra 3 phím giống nhau, thì việc get_id = itemgetter(KEYS)sử dụng sau này serial, code, ts = get_id(document)sẽ đơn giản hơn. Phải thừa nhận rằng bạn phải cảm thấy thoải mái với các hàm bậc cao hơn, nhưng Python nói chung rất thoải mái với chúng. Ví dụ: xem tài liệu dành cho người trang trí như thế nào @contextmanager.
John Christopher Jones

28

Một cách để làm điều này mà ít lặp lại hơn gợi ý của Jochen là sử dụng hàm trợ giúp. Điều này mang lại sự linh hoạt để liệt kê các tên biến của bạn theo bất kỳ thứ tự nào và chỉ hủy cấu trúc một tập hợp con của những gì có trong dict:

pluck = lambda dict, *args: (dict[arg] for arg in args)

things = {'blah': 'bleh', 'foo': 'bar'}
foo, blah = pluck(things, 'foo', 'blah')

Ngoài ra, thay vì OrderDict của joaquin, bạn có thể sắp xếp các khóa và lấy các giá trị. Điều bắt buộc duy nhất là bạn cần chỉ định tên biến của mình theo thứ tự bảng chữ cái và hủy cấu trúc mọi thứ trong dict:

sorted_vals = lambda dict: (t[1] for t in sorted(dict.items()))

things = {'foo': 'bar', 'blah': 'bleh'}
blah, foo = sorted_vals(things)

4
Đây là một câu hỏi nhỏ, nhưng nếu bạn định gán lambdamột biến, bạn cũng có thể sử dụng cú pháp hàm bình thường với def.
Arthur Tacca

ủng hộ không thể bạn làm điều này như JS nơi nó sẽ là const {a, b} = {a: 1, b: 2}
PirateApp

1
Bạn đã triển khai itemgettertừ operatortrong thư viện chuẩn. :)
John Christopher Jones

16

Python chỉ có thể "hủy cấu trúc" các chuỗi, không phải từ điển. Vì vậy, để viết những gì bạn muốn, bạn sẽ phải ánh xạ các mục nhập cần thiết theo một trình tự thích hợp. Đối với bản thân tôi, kết quả phù hợp nhất mà tôi có thể tìm thấy là (không sexy lắm):

a,b = [d[k] for k in ('a','b')]

Điều này cũng hoạt động với máy phát điện:

a,b = (d[k] for k in ('a','b'))

Đây là một ví dụ đầy đủ:

>>> d = dict(a=1,b=2,c=3)
>>> d
{'a': 1, 'c': 3, 'b': 2}
>>> a, b = [d[k] for k in ('a','b')]
>>> a
1
>>> b
2
>>> a, b = (d[k] for k in ('a','b'))
>>> a
1
>>> b
2

10

Có lẽ bạn thực sự muốn làm điều gì đó như thế này?

def some_func(a, b):
  print a,b

params = {'a':1,'b':2}

some_func(**params) # equiv to some_func(a=1, b=2)

Cảm ơn, nhưng không phải là ... Tôi destructuring trong vòng một chức năng
hatmatrix

10

Đây là một cách khác để làm điều đó tương tự như cách hoạt động của một nhiệm vụ hủy cấu trúc trong JS:

params = {'b': 2, 'a': 1}
a, b, rest = (lambda a, b, **rest: (a, b, rest))(**params)

Những gì chúng tôi đã làm là giải nén từ điển params thành các giá trị khóa (sử dụng **) (như trong câu trả lời của Jochen ), sau đó chúng tôi lấy các giá trị đó trong chữ ký lambda và gán chúng theo tên khóa - và đây là một phần thưởng - chúng tôi cũng có được một từ điển về bất cứ điều gì không có trong chữ ký của lambda, vì vậy nếu bạn có:

params = {'b': 2, 'a': 1, 'c': 3}
a, b, rest = (lambda a, b, **rest: (a, b, rest))(**params)

Sau khi lambda đã được áp dụng, biến còn lại bây giờ sẽ chứa: {'c': 3}

Hữu ích để loại bỏ các khóa không cần thiết khỏi từ điển.

Hi vọng điêu nay co ich.


Thật thú vị, mặt khác, tôi cảm thấy nó sẽ tốt hơn trong một chức năng. Có thể bạn sẽ sử dụng nó một vài lần và theo cách đó, bạn cũng sẽ có tên trên đó. (khi tôi nói hàm, ý tôi không phải là hàm lambda).
cglacet

3

thử đi

d = {'a':'Apple', 'b':'Banana','c':'Carrot'}
a,b,c = [d[k] for k in ('a', 'b','c')]

kết quả:

a == 'Apple'
b == 'Banana'
c == 'Carrot'

3

Cảnh báo 1: như đã nêu trong tài liệu, điều này không được đảm bảo hoạt động trên tất cả các triển khai Python:

Chi tiết triển khai CPython: Hàm này dựa vào hỗ trợ khung ngăn xếp Python trong trình thông dịch, điều này không được đảm bảo tồn tại trong tất cả các triển khai của Python. Nếu chạy trong một triển khai mà không hỗ trợ khung ngăn xếp Python, hàm này trả về Không có.

Cảnh báo 2: hàm này làm cho mã ngắn hơn, nhưng nó có thể mâu thuẫn với triết lý của Python là càng rõ ràng càng tốt. Hơn nữa, nó không giải quyết các vấn đề được John Christopher Jones chỉ ra trong các nhận xét, mặc dù bạn có thể tạo một hàm tương tự hoạt động với các thuộc tính thay vì khóa. Đây chỉ là một minh chứng rằng bạn có thể làm điều đó nếu bạn thực sự muốn!

def destructure(dict_):
    if not isinstance(dict_, dict):
        raise TypeError(f"{dict_} is not a dict")
    # the parent frame will contain the information about
    # the current line
    parent_frame = inspect.currentframe().f_back

    # so we extract that line (by default the code context
    # only contains the current line)
    (line,) = inspect.getframeinfo(parent_frame).code_context

    # "hello, key = destructure(my_dict)"
    # -> ("hello, key ", "=", " destructure(my_dict)")
    lvalues, _equals, _rvalue = line.strip().partition("=")

    # -> ["hello", "key"]
    keys = [s.strip() for s in lvalues.split(",") if s.strip()]

    if missing := [key for key in keys if key not in dict_]:
        raise KeyError(*missing)

    for key in keys:
        yield dict_[key]
In [5]: my_dict = {"hello": "world", "123": "456", "key": "value"}                                                                                                           

In [6]: hello, key = destructure(my_dict)                                                                                                                                    

In [7]: hello                                                                                                                                                                
Out[7]: 'world'

In [8]: key                                                                                                                                                                  
Out[8]: 'value'

Giải pháp này cho phép bạn chọn một số khóa, không phải tất cả, như trong JavaScript. Nó cũng an toàn cho từ điển do người dùng cung cấp


1

Chà, nếu bạn muốn những điều này trong một lớp học, bạn luôn có thể làm điều này:

class AttributeDict(dict):
    def __init__(self, *args, **kwargs):
        super(AttributeDict, self).__init__(*args, **kwargs)
        self.__dict__.update(self)

d = AttributeDict(a=1, b=2)

Đẹp. Cảm ơn, nhưng có vẻ như một cách để thay đổi cú pháp cuộc gọi từ d ['a'] thành da? Và có lẽ thêm các phương pháp đó có ngầm truy cập vào các thông số ...
hatmatrix

0

Dựa trên câu trả lời @ShawnFumo, tôi đã nghĩ ra điều này:

def destruct(dict): return (t[1] for t in sorted(dict.items()))

d = {'b': 'Banana', 'c': 'Carrot', 'a': 'Apple' }
a, b, c = destruct(d)

(Chú ý thứ tự các mặt hàng trong dict)


0

Vì các từ điển được đảm bảo giữ thứ tự chèn của chúng trong Python> = 3.7 , điều đó có nghĩa là ngày nay việc làm này hoàn toàn an toàn và dễ hiểu:

params = {'a': 1, 'b': 2}
a, b = params.values()
print(a)
print(b)

Đầu ra:

1
2

2
Tuyệt quá! Họ chỉ mất 9 năm kể từ khi tôi đăng bài. :)
hatmatrix

1
Vấn đề là bạn không thể làm được b, a = params.values(), vì nó sử dụng thứ tự không phải tên.
Corman

1
@ruohola Đó là vấn đề với giải pháp này. Điều này phụ thuộc vào thứ tự của từ điển cho tên, không phải chính tên, đó là lý do tại sao tôi từ chối điều này.
Corman

1
Và điều đó làm cho nó trở thành một giải pháp tồi nếu nó dựa vào thứ tự của các phím. itemgetterlà cách khó khăn nhất để làm điều này. Điều này sẽ không hoạt động trên Python 3.6 trở xuống và vì nó phụ thuộc vào thứ tự, nên lúc đầu nó có thể gây nhầm lẫn.
Corman

-1

Tôi không biết liệu đó có phải là phong cách tốt hay không, nhưng

locals().update(params)

sẽ thực hiện thủ thuật. Sau đó a, bạn có , bvà bất cứ thứ gì trong paramsmệnh lệnh của bạn có sẵn dưới dạng các biến cục bộ tương ứng.


2
Xin lưu ý rằng đây có thể là một vấn đề lớn về bảo mật nếu từ điển 'params' do người dùng cung cấp theo bất kỳ cách nào và không được lọc đúng cách.
Jacek Konieczny

9
Để trích dẫn docs.python.org/library/functions.html#locals : Lưu ý: Không nên sửa đổi nội dung của từ điển này; các thay đổi có thể không ảnh hưởng đến giá trị của các biến cục bộ và biến tự do được trình thông dịch sử dụng.
Jochen Ritzel

4
Rút ra bài học. Cảm ơn mọi người.
Johannes Charra
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.