Jak mogę uczynić „perfekcyjną” podklasę dyktku, jak to możliwe? Ostatecznym celem jest prosty dyktando, w którym klawisze są pisane małymi literami.
Wydawałoby się, że powinien istnieć niewielki zestaw prymitywów, które mogę zastąpić, aby to zadziałało, ale według wszystkich moich badań i prób wydaje się, że tak nie jest:
Jeśli zastąpię
__getitem__
/__setitem__
, wtedyget
/set
nie będę działać. Jak sprawić, by działały? Z pewnością nie muszę wdrażać ich indywidualnie?Czy zapobiegam działaniu wytrawiania i czy muszę wdrażać
__setstate__
itp.?Czy powinienem używać mapowania mutablemów (wydaje się, że nie należy używać
UserDict
lubDictMixin
)? Jeśli tak to jak? Dokumenty nie są do końca pouczające.
Oto mój pierwszy krok, get()
nie działa i bez wątpienia istnieje wiele innych drobnych problemów:
class arbitrary_dict(dict):
"""A dictionary that applies an arbitrary key-altering function
before accessing the keys."""
def __keytransform__(self, key):
return key
# Overridden methods. List from
# /programming/2390827/how-to-properly-subclass-dict
def __init__(self, *args, **kwargs):
self.update(*args, **kwargs)
# Note: I'm using dict directly, since super(dict, self) doesn't work.
# I'm not sure why, perhaps dict is not a new-style class.
def __getitem__(self, key):
return dict.__getitem__(self, self.__keytransform__(key))
def __setitem__(self, key, value):
return dict.__setitem__(self, self.__keytransform__(key), value)
def __delitem__(self, key):
return dict.__delitem__(self, self.__keytransform__(key))
def __contains__(self, key):
return dict.__contains__(self, self.__keytransform__(key))
class lcdict(arbitrary_dict):
def __keytransform__(self, key):
return str(key).lower()
źródło
Odpowiedzi:
Możesz napisać obiekt, który zachowuje się
dict
dość łatwo dzięki ABC (Abstract Base Classes) zcollections.abc
modułu. Mówi nawet, jeśli przegapiłeś jakąś metodę, więc poniżej znajduje się minimalna wersja, która wyłącza ABC.Otrzymujesz kilka bezpłatnych metod od ABC:
Nie
dict
podklasowałbym (lub innych wbudowanych) bezpośrednio. Często nie ma to sensu, ponieważ tak naprawdę chcesz zaimplementować interfejsdict
. I właśnie do tego służą ABC.źródło
__keytransform__()
ponieważ narusza przewodnik po stylu PEP 8, który radzi: „Nigdy nie wymyślaj takich nazw; używaj ich tylko zgodnie z dokumentacją” na końcu sekcji Descriptive: Style nazw .if isinstance(t, collections.MutableMapping): print t, "can be used like a dict"
. Nie sprawdzaj typu obiektu, sprawdź interfejs.Przyjęta odpowiedź byłaby moim pierwszym podejściem, ale ponieważ ma pewne problemy, a ponieważ nikt nie zajął się alternatywą, właściwie podklasą a
dict
, zrobię to tutaj.Co jest nie tak z zaakceptowaną odpowiedzią?
Wydaje mi się to dość prostą prośbą:
Przyjęta odpowiedź nie jest właściwie podklasą
dict
, a test na to się nie powiedzie:Idealnie byłoby, gdyby każdy kod sprawdzający typ testowałby oczekiwany interfejs lub abstrakcyjną klasę podstawową, ale jeśli nasze obiekty danych są przekazywane do funkcji, które testują
dict
- i nie możemy „naprawić” tych funkcji, ten kod zawiedzie.Inne sprzeczki, które można zrobić:
fromkeys
.Przyjęta odpowiedź ma również nadmiar
__dict__
- dlatego zajmuje więcej miejsca w pamięci:Właściwie podklasę
dict
Możemy ponownie wykorzystać metody dict poprzez dziedziczenie. Wszystko, co musimy zrobić, to stworzyć warstwę interfejsu, która zapewni, że klucze będą przekazywane do nagrania małymi literami, jeśli są łańcuchami.
Cóż, wdrażanie ich osobno jest wadą tego podejścia i zaletą używania
MutableMapping
(patrz zaakceptowana odpowiedź), ale tak naprawdę nie jest to dużo więcej pracy.Po pierwsze, rozłóżmy różnicę między Pythonem 2 i 3, utwórz singleton (
_RaiseKeyError
), aby upewnić się, że rzeczywiście otrzymujemy argumentdict.pop
, i utwórz funkcję, aby nasze klucze łańcuchowe były pisane małymi literami:Teraz implementujemy - używam
super
z pełnymi argumentami, aby ten kod działał dla Pythona 2 i 3:Używamy podejście niemal bojler-płyta dla dowolnej metody lub specjalną metodą, która odwołuje się kluczowym, ale w inny sposób, przez dziedziczenie, otrzymujemy metody:
len
,clear
,items
,keys
,popitem
, ivalues
za darmo. Choć wymagało to starannego przemyślenia, nie jest łatwo dostrzec, że to działa.(Uwaga:
haskey
przestarzała w Pythonie 2, usunięta w Pythonie 3)Oto niektóre zastosowania:
marynowanie
A pikle podklasy dict są w porządku:
__repr__
Zdefiniowaliśmy
update
i__init__
, ale__repr__
domyślnie masz piękny :__repr__
Warto jednak napisać a, aby poprawić debugowanie kodu. Idealny test toeval(repr(obj)) == obj
. Jeśli jest to łatwe do zrobienia dla twojego kodu, zdecydowanie go polecam:Widzisz, dokładnie tego potrzebujemy do odtworzenia równoważnego obiektu - jest to coś, co może pojawić się w naszych dziennikach lub śladach:
Wniosek
Tak, to jeszcze kilka wierszy kodu, ale mają one być wyczerpujące. Moją pierwszą skłonnością byłoby użyć zaakceptowanej odpowiedzi, a jeśli byłyby z nią jakieś problemy, spojrzałbym na moją odpowiedź - ponieważ jest to trochę bardziej skomplikowane i nie ma ABC, który pomógłby mi poprawnie ustawić interfejs.
Przedwczesna optymalizacja dąży do większej złożoności w poszukiwaniu wydajności.
MutableMapping
jest prostszy - dzięki czemu uzyskuje natychmiastową przewagę, a wszystkie pozostałe są równe. Niemniej jednak, aby przedstawić wszystkie różnice, porównajmy i skontrastujmy.Powinienem dodać, że pojawił się nacisk na umieszczenie podobnego słownika w
collections
module, ale został on odrzucony . Prawdopodobnie powinieneś po prostu zrobić to zamiast tego:Powinno być znacznie łatwiejsze do debugowania.
Porównać i kontrastować
Zaimplementowano 6 funkcji interfejsu z
MutableMapping
(której brakujefromkeys
) i 11 zdict
podklasą. Nie muszę wdrożyć__iter__
lub__len__
, lecz muszę wdrożyćget
,setdefault
,pop
,update
,copy
,__contains__
, ifromkeys
- ale są to dość trywialne, ponieważ mogę używać dziedziczenia dla większości z tych wdrożeń.Te
MutableMapping
narzędzia pewne rzeczy w Pythonie, któredict
wdraża w C - więc spodziewałbym siędict
podklasa być bardziej wydajnych w niektórych przypadkach.Dostajemy darmowe
__eq__
w obu podejściach - oba zakładają równość tylko wtedy, gdy inny dykt jest pisany małymi literami - ale znowu, myślę, żedict
podklasa porówna się szybciej.Podsumowanie:
MutableMapping
jest prostsze z mniejszą liczbą błędów, ale wolniejsze, zajmuje więcej pamięci (patrz redundantny dykt) i kończy się niepowodzeniemisinstance(x, dict)
dict
jest szybsze, zużywa mniej pamięci i przechodziisinstance(x, dict)
, ale ma większą złożoność do wdrożenia.Który jest bardziej idealny? To zależy od twojej definicji ideału.
źródło
__slots__
lub być może ponownie użyć go__dict__
jako sklepu, ale to miesza semantykę, kolejny potencjalny punkt krytyki.ensure_lower
pierwszego argumentu (który zawsze jest kluczem)? Wtedy byłaby taka sama liczba przesłonięć, ale wszystkie miałyby formę__getitem__ = ensure_lower_decorator(super(LowerDict, self).__getitem__)
.copy
- Myślę, że to powinno zrobić, nie? Myślę, że powinien przetestować interfejs - np. Obiekt DataFrame pandy nie jest instancją Mapping (przy ostatnim sprawdzeniu), ale ma elementy / iterity.Moje wymagania były nieco bardziej rygorystyczne:
Początkowo myślałem, aby zastąpić naszą niezgrabną klasę Path podklasą Unicode niewrażliwą na wielkość liter - ale:
some_dict[CIstr(path)]
jest brzydki)Musiałem więc w końcu zanotować dyktandę bez uwzględniania wielkości liter. Dzięki kodowi @AaronHall, który był 10 razy łatwiejszy.
Implikowane kontra jawne nadal stanowi problem, ale gdy opadnie kurz, zmiana nazw atrybutów / zmiennych na początek z ci (i duży gruby komentarz doc wyjaśniający, że ci oznacza rozróżnianie wielkości liter) Myślę, że jest to idealne rozwiązanie - ponieważ czytelnicy kodu muszą być w pełni świadomym, że mamy do czynienia z bazowymi strukturami danych bez rozróżniania wielkości liter. Mam nadzieję, że to naprawi niektóre trudne do odtworzenia błędy, które, jak podejrzewam, sprowadzają się do rozróżnienia wielkości liter.
Komentarze / poprawki mile widziane :)
źródło
__repr__
powinien użyć klasy nadrzędnej,__repr__
aby przekazać test eval (repr (obj)) == obj (nie sądzę, że teraz tak się dzieje) i nie polegać na nim__str__
.total_ordering
dekorator klas - który wyeliminuje 4 metody z podklasy Unicode. Ale podklasa dict wygląda bardzo sprytnie. : PCIstr.__repr__
, w twoim przypadku, może przejść test repr z bardzo niewielkim kłopotem i powinno sprawić, że debugowanie będzie znacznie przyjemniejsze. Dodałbym również__repr__
do twojego słownika. Zrobię to w mojej odpowiedzi, aby zademonstrować.__slots__
w CIstr - robi różnicę w wydajności (CIstr nie ma być podklasowany ani faktycznie używany poza LowerDict, powinien być statyczną zagnieżdżoną klasą końcową). Nadal nie jestem pewien, jak elegancko rozwiązać problem repr (żądło może zawierać kombinację znaków'
i"
cytatów)Wszystko, co musisz zrobić, to
LUB
Przykładowe użycie do mojego osobistego użytku
Uwaga : testowane tylko w python3
źródło
Po wypróbowaniu obu dwóch najważniejszych propozycji zdecydowałem się na podejrzanie wyglądającą środkową trasę dla Pythona 2.7. Może 3 jest zdrowsze, ale dla mnie:
których naprawdę nienawidzę, ale wydaje się, że pasują do moich potrzeb, a mianowicie:
**my_dict
dict
, pomija twój kod . Wypróbuj to.isinstance(my_dict, dict)
dict
Jeśli chcesz się odróżnić od innych, osobiście używam czegoś takiego (chociaż zaleciłbym lepsze nazwy):
Tak długo, jak musisz tylko rozpoznać siebie wewnętrznie, w ten sposób trudniej jest przypadkowo zadzwonić
__am_i_me
ze względu na munging pythona (zmiana nazwy na_MyDict__am_i_me
dowolną wywołanie poza tą klasą). Nieco bardziej prywatne niż_method
s, zarówno w praktyce, jak i kulturowo.Jak dotąd nie mam żadnych skarg poza poważnie wyglądającym
__class__
obejściem. Byłbym podekscytowany, gdybym usłyszał o wszelkich problemach napotykanych przez innych, nie do końca rozumiem konsekwencje. Ale do tej pory nie miałem żadnych problemów, co pozwoliło mi na migrację dużej ilości kodu o średniej jakości w wielu lokalizacjach bez konieczności wprowadzania jakichkolwiek zmian.Jako dowód: https://repl.it/repls/TraumaticToughCockatoo
Zasadniczo: skopiuj bieżącą opcję nr 2 , dodaj
print 'method_name'
wiersze do każdej metody, a następnie spróbuj tego i obserwuj wynik:Zobaczysz podobne zachowanie dla innych scenariuszy. Powiedz, że twój fałszywy
dict
jest opakowaniem wokół innego typu danych, więc nie ma rozsądnego sposobu na przechowywanie danych w back-dict;**your_dict
będzie pusty, niezależnie od tego, co robi każda inna metoda.Działa to poprawnie
MutableMapping
, ale gdy tylko odziedziczysz podict
nim, staje się niekontrolowane.Edycja: jako aktualizacja, działa bez problemu od prawie dwóch lat, na kilkuset tysiącach (eh, może to być kilka milionów) linii skomplikowanego, opartego na dziedzictwie Pythona. Jestem z tego całkiem zadowolony :)
Edycja 2: najwyraźniej źle skopiowałem to lub coś dawno temu.
@classmethod __class__
nie działa dlaisinstance
czeków -@property __class__
robi: https://repl.it/repls/UnitedScientificSequenceźródło
**your_dict
będzie pusty” (jeśli podklasujeszdict
)? Nie widziałem żadnych problemów z rozpakowywaniem dykt ...**your_dict
nie wykonuje kodu, więc nie można wyprowadzić niczego „specjalnego”. Np. Nie można liczyć „odczytów”, ponieważ nie wykonuje on kodu liczącego odczyty. MutableMapping wykonuje pracę dla tego (użyj go, jeśli to możliwe!), Ale to nieisinstance(..., dict)
tak, że nie mógł z niego korzystać. tak, starsze oprogramowanie.**your_dict
, ale uważam, że to bardzo interesująceMutableMapping
.**some_dict
jest dość powszechny. Przynajmniej zdarza się bardzo często w dekoratorów, więc jeśli masz jakikolwiek , jesteś natychmiast zagrożone pozornie niemożliwej złe zachowanie, jeśli nie stanowią dla niego.def __class__()
wydaje się , że sztuczka nie działa z Pythonem 2 lub 3, przynajmniej dla przykładowego kodu w pytaniu Jak zarejestrować implementację abc.MutableMapping jako podklasę dict? (zmodyfikowany, by działał w dwóch wersjach). Chcęisinstance(SpreadSheet(), dict)
wrócićTrue
.