Zawartość słownika niszcząco-wiążąca


85

Próbuję „zniszczyć” słownik i skojarzyć wartości z nazwami zmiennych po kluczach. Coś jak

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

Ale ponieważ słowniki nie są uporządkowane, nie ma gwarancji, że params.values()zwrócą wartości w kolejności (a, b). Czy jest na to dobry sposób?


3
Leniwy? Może ... ale oczywiście pokazałem najprostszy przypadek dla ilustracji. Idealnie chciałbym mieć jak dla x w params.items: eval ('% s =% f'% x), ale wydaje mi się, że eval () nie pozwala na przypisania.
hatmatrix

8
@JochenRitzel Jestem całkiem pewien, że większość użytkowników ES6 (JavaScript) lubi nową składnię obiektu destructuring: let {a, b} = params. Poprawia czytelność i jest całkowicie zgodny z każdym Zenem, o którym chcesz rozmawiać.
Andy

8
@Andy Uwielbiam destrukturyzację obiektów w JS. Cóż za czysty, prosty i czytelny sposób na wyodrębnienie niektórych kluczy z dyktu. Przyszedłem tutaj z nadzieją znalezienia czegoś podobnego w Pythonie.
Rotareti

2
Uwielbiam również destrukturyzację obiektów ES6, ale obawiam się, że nie może ona działać w Pythonie z tego samego powodu, dla którego obiekt Map w ES6 nie obsługuje destrukturyzacji. Klucze to nie tylko ciągi znaków w ES6 Map i Python Dict. Poza tym, chociaż uwielbiam niszczenie obiektów w stylu „pluck” w ES6, styl przypisywania nie jest prosty. Co tu się dzieje? let {a: waffles} = params. Rozumienie tego zajmuje kilka sekund, nawet jeśli jesteś do tego przyzwyczajony.
John Christopher Jones,

1
@ naught101 Przydatne sytuacyjnie z przykrymi niespodziankami za kompromisy. Dla użytkowników: W Pythonie każdy obiekt może udostępniać własne metody str / repr. Może to być nawet kuszące, aby zrobić to dla nieco złożonych obiektów kluczy (np. Nazwanych krotek) w celu łatwiejszej serializacji JSON. Teraz drapiesz się po głowie, dlaczego nie możesz zniszczyć klucza po imieniu. Dlaczego to działa w przypadku elementów, ale nie atrybutu? Wiele bibliotek preferuje atrybuty. Dla implementatorów ta funkcja ES6 myli symbole (nazwy dające się powiązać) i ciągi: rozsądne w JavaScript, ale Python ma znacznie bogatsze pomysły w grę. Poza tym wyglądałoby to po prostu brzydko.
John Christopher Jones

Odpowiedzi:


10

Jeśli obawiasz się problemów związanych z korzystaniem z lokalnego słownika i wolisz podążać za swoją oryginalną strategią, Uporządkowane słowniki z kolekcji Pythona 2.7 i 3.1.OrderedDicts umożliwia odzyskanie pozycji ze słownika w kolejności, w jakiej zostały wstawione po raz pierwszy


@Stephen masz szczęście, ponieważ OrderedDicts zostały przeniesione do Pythona 2.7 z Pythona 3.1
joaquin

Czekam na to z niecierpliwością...! Pomyślałem, że zawsze był to punkt zaczepienia dla Pythona dla mnie (że zamówione słowniki nie były załadowane z innymi bateriami)
hatmatrix

4
Obecnie w wersji 3.5+ wszystkie słowniki są uporządkowane. Nie jest to jeszcze „gwarantowane”, co oznacza, że ​​może się zmienić.
Charles Merriam

4
To gwarantowane od 3.6+.
naught101

123
from operator import itemgetter

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

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

Zamiast rozbudowanych funkcji lambda lub rozumienia ze słownika, równie dobrze można użyć wbudowanej biblioteki.


9
To prawdopodobnie powinna być akceptowana odpowiedź, ponieważ jest to najbardziej Pythonowy sposób na zrobienie tego. Możesz nawet rozszerzyć odpowiedź, aby użyć attrgetterz tego samego standardowego modułu biblioteki, który działa dla atrybutów obiektu ( obj.a). Jest to główna różnica w stosunku do JavaScript, gdzie obj.a === obj["a"].
John Christopher Jones

Jeśli klucz nie istnieje w słowniku, KeyErrorwyjątek zostanie podniesiony
Tasawar Hussain

2
Ale teraz dwa razy wpisujesz a i b w zeznaniu destrukturyzacji
Otto

1
@JohnChristopherJones To nie wydaje mi się zbyt naturalne, używanie istniejących rzeczy nie oznacza, że ​​jest to zrozumiałe. Wątpię, by wielu ludzi od razu zrozumiało prawdziwy kod. Z drugiej strony, jak sugerowano, a, b = [d[k] for k in ('a','b')]jest o wiele bardziej naturalny / czytelny (forma jest o wiele bardziej powszechna). To wciąż interesująca odpowiedź, ale nie jest to najprostsze rozwiązanie.
cglacet

@cglacet Myślę, że to zależy od tego, ile razy musisz to przeczytać / zaimplementować. Jeśli rutynowo wybierasz te same 3 klucze, łatwiej jest get_id = itemgetter(KEYS)użyć czegoś podobnego do późniejszego serial, code, ts = get_id(document). Trzeba przyznać, że musisz czuć się komfortowo z funkcjami wyższego rzędu, ale Python ogólnie bardzo dobrze sobie z nimi radzi. Np. Zobacz dokumentację dla dekoratorów, takich jak @contextmanager.
John Christopher Jones

29

Jednym ze sposobów na zrobienie tego z mniejszą liczbą powtórzeń niż sugestia Jochena jest użycie funkcji pomocniczej. Daje to elastyczność, aby wyświetlić nazwy zmiennych w dowolnej kolejności i zniszczyć tylko podzbiór tego, co jest w dyktandzie:

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

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

Ponadto zamiast OrderedDict joaquina można posortować klucze i uzyskać wartości. Jedyne złapanie polega na tym, że musisz podać nazwy zmiennych w porządku alfabetycznym i zniszczyć wszystko w dyktandzie:

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

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

4
Jest to drobna kwestia, ale jeśli zamierzasz przypisać lambdazmienną a, równie dobrze możesz użyć normalnej składni funkcji def.
Arthur Tacca

za uznaniem nie można zrobić tego jak JS, gdzie byłoby to const {a, b} = {a: 1, b: 2}
PirateApp

1
Zaimplementowałeś itemgetterz operatorw standardowej bibliotece. :)
John Christopher Jones

17

Python jest w stanie „zniszczyć” tylko sekwencje, a nie słowniki. Aby więc napisać, co chcesz, będziesz musiał odwzorować potrzebne wpisy w odpowiedniej kolejności. Jeśli chodzi o mnie, najbliższe dopasowanie, jakie mogłem znaleźć, to (niezbyt seksowne):

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

Działa to również z generatorami:

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

Oto pełny przykład:

>>> 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

Może naprawdę chcesz zrobić coś takiego?

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

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

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

Dzięki, ale nie to ...
Destrukturyzuję

10

Oto inny sposób, aby zrobić to podobnie do tego, jak działa przydział destrukturyzujący w JS:

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

To, co zrobiliśmy, to rozpakowanie słownika parametrów na wartości kluczy (używając **) (jak w odpowiedzi Jochena ), a następnie wzięliśmy te wartości w sygnaturze lambda i przypisaliśmy je zgodnie z nazwą klucza - i oto bonus - my zdobądź także słownik wszystkiego, czego nie ma w sygnaturze lambdy, więc gdybyś miał:

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

Po zastosowaniu lambdy pozostała zmienna będzie teraz zawierać: {'c': 3}

Przydatne do pomijania niepotrzebnych kluczy ze słownika.

Mam nadzieję że to pomoże.


Co ciekawe, z drugiej strony czuję, że w funkcji byłoby lepiej. Prawdopodobnie użyjesz tego kilka razy i w ten sposób będziesz mieć również na nim nazwę. (kiedy mówię o funkcji, mam na myśli, a nie o funkcji lambda).
cglacet

3

Spróbuj tego

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

wynik:

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

3

Ostrzeżenie 1: jak stwierdzono w dokumentacji, nie ma gwarancji, że będzie to działać na wszystkich implementacjach Pythona:

Szczegóły implementacji CPythona: ta funkcja opiera się na obsłudze ramek stosu Pythona w interpreterach, co nie jest gwarantowane we wszystkich implementacjach Pythona. Jeśli działa w implementacji bez obsługi ramek stosu Pythona, ta funkcja zwraca wartość None.

Ostrzeżenie 2: ta funkcja skraca kod, ale prawdopodobnie jest sprzeczna z filozofią Pythona polegającą na byciu tak jednoznacznym, jak to tylko możliwe. Co więcej, nie rozwiązuje problemów wskazanych przez Johna Christophera Jonesa w komentarzach, chociaż możesz stworzyć podobną funkcję, która będzie działać z atrybutami zamiast kluczy. To tylko demonstracja, że ​​możesz to zrobić, jeśli naprawdę chcesz!

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'

To rozwiązanie pozwala na wybranie niektórych kluczy, a nie wszystkich, jak w JavaScript. Jest również bezpieczny dla słowników dostarczanych przez użytkowników


1

Cóż, jeśli chcesz je mieć na zajęciach, zawsze możesz to zrobić:

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

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

Miły. Dzięki, ale wydaje się, że jest to sposób na zmianę składni wywołania z d ['a'] na da? I być może dodanie metod, które mają niejawny dostęp do tych parametrów ...
hatmatrix

0

Na podstawie odpowiedzi @ShawnFumo wymyśliłem to:

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

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

(Zwróć uwagę na kolejność pozycji w dyktandzie)


0

Ponieważ słowniki gwarantują zachowanie kolejności wstawiania w Pythonie> = 3.7 , oznacza to, że obecnie jest to całkowicie bezpieczne i idiomatyczne:

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

Wynik:

1
2

2
Świetny! Od mojego wpisu zajęło im tylko 9 lat. :)
hatmatrix

1
Problem polega na tym, że nie możesz tego zrobić b, a = params.values(), ponieważ używa kolejności, a nie nazw.
Corman

1
@ruohola Na tym polega problem z tym rozwiązaniem. To zależy od kolejności słowników nazwisk, a nie samych imion, dlatego zgodziłem się na to.
Corman

1
A to sprawia, że ​​jest to złe rozwiązanie, jeśli zależy to od kolejności kluczy. itemgetterto najbardziej pythonowy sposób na zrobienie tego. To nie zadziała w Pythonie 3.6 lub starszym, a ponieważ zależy od kolejności, na początku może być mylące.
Corman

-1

Nie wiem, czy to dobry styl, ale

locals().update(params)

da rade. Masz wtedy wszystko a, bco było w twoim paramsdyktandzie, dostępne jako odpowiednie zmienne lokalne.


2
Należy pamiętać, że może to stanowić duży problem z bezpieczeństwem, jeśli słownik „parametrów” jest w jakikolwiek sposób udostępniany przez użytkownika i nie jest odpowiednio filtrowany.
Jacek Konieczny

9
Cytując docs.python.org/library/functions.html#locals : Uwaga: Zawartość tego słownika nie powinna być modyfikowana; zmiany nie mogą wpływać na wartości zmiennych lokalnych i wolnych używanych przez interpreter.
Jochen Ritzel

4
Nauczyłem się lekcji. Dzięki, chłopaki.
Johannes Charra
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.