Jaki jest związek między modelem danych Pythona a wbudowanymi funkcjami?
Kiedy czytam odpowiedzi Pythona na Stack Overflow, nadal widzę kilka osób mówiących użytkownikom, Aby używali bezpośrednio metod specjalnych metod modelu danych lub atrybutów.
Widzę wtedy sprzeczne Rady (czasami od siebie) mówiące, aby tego nie robić, a zamiast tego używać wbudowanych funkcji i operatorów bezpośrednio.
Dlaczego? Jaka jest zależność pomiędzy specjalnymi metodami "dunder" a atrybutami modelu danych Pythona i wbudowane funkcje ?
Kiedy mam używać specjalnych nazw?
2 answers
Jaki jest związek pomiędzy modelem danych Pythona a wbudowanymi funkcjami?
- wbudowane operatory używają bazowych metod lub atrybutów datamodel.
- wbudowane i operatory mają bardziej eleganckie zachowanie i ogólnie są bardziej kompatybilne do przodu.
- specjalne metody datamodelu są semantycznie niepublicznymi interfejsami.
- wbudowane operatory i operatory językowe mają być interfejsem użytkownika do zachowania realizowane specjalnymi metodami.
W związku z tym, powinieneś używać wbudowanych funkcji i operatorów tam, gdzie to możliwe, zamiast specjalnych metod i atrybutów datamodelu.
[79]}semantycznie wewnętrzne interfejsy API są bardziej narażone na zmiany niż interfejsy publiczne. Chociaż Python nie uważa niczego za "prywatny" i ujawnia wewnętrzne elementy, nie oznacza to, że nadużywanie tego dostępu nie jest dobrym pomysłem. Czynienie tego wiąże się z następującymi zagrożeniami:- możesz znaleźć podczas aktualizacji programu wykonywalnego Pythona lub przełączania się na inne implementacje Pythona (takie jak PyPy, IronPython lub Jython, lub innej nieprzewidzianej implementacji) pojawia się więcej zmian.)
- twoi koledzy prawdopodobnie źle pomyślą o twoich umiejętnościach językowych i sumienności, i uznają to za zapach kodu, doprowadzając Ciebie i resztę twojego kodu do większej kontroli.
- wbudowane funkcje są łatwe do przechwycenia zachowania. Stosowanie specjalnych metod bezpośrednio ogranicza moc Twojego Pythona do introspekcji i debugowania.
W głębi
Wbudowane funkcje i operatory wywołują specjalne metody i używają specjalnych atrybutów w modelu danych Pythona. Są czytelną i konserwowalną okleiną, która ukrywa wnętrza przedmiotów. Ogólnie rzecz biorąc, użytkownicy powinni używać wbudowanych i operatorów podanych w języku, a nie wywoływać specjalnych metod lub używać specjalnych atrybutów bezpośrednio.
Wbudowane funkcje i operatory mogą również mieć zachowanie awaryjne lub bardziej eleganckie niż bardziej prymitywne metody specjalne datamodel. Na przykład:
-
next(obj, default)
pozwala na podanie domyślnej wartości zamiast podnoszeniaStopIteration
, gdy iterator się wyczerpie, podczas gdyobj.__next__()
nie. -
str(obj)
powróci doobj.__repr__()
, gdyobj.__str__()
nie jest dostępna - podczas gdy wywołanieobj.__str__()
bezpośrednio wywoła błąd atrybutu. -
obj != other
fallsback tonot obj == other
w Pythonie 3 Gdy Nie__ne__
- wywołanieobj.__ne__(other)
nie zajęłoby zaleta tego.
(wbudowane funkcje mogą być również łatwo przysłonięte, jeśli jest to konieczne lub pożądane, na globalnym zasięgu modułu lub modułu builtins
, aby jeszcze bardziej dostosować zachowanie.)
Mapowanie wbudowanych operatorów do datamodelu
Tutaj jest mapowanie, z uwagami, wbudowanych funkcji i operatorów do odpowiednich specjalnych metod i atrybutów, których używają lub zwracają-zauważ, że zwykle zasada jest taka, że wbudowana funkcja Zwykle mapuje do nie jest to jednak na tyle spójne, aby uzasadnić podanie tej mapy poniżej: {]}
builtins/ special methods/
operators -> datamodel NOTES (fb == fallback)
repr(obj) obj.__repr__() provides fb behavior for str
str(obj) obj.__str__() fb to __repr__ if no __str__
bytes(obj) obj.__bytes__() Python 3 only
unicode(obj) obj.__unicode__() Python 2 only
format(obj) obj.__format__() format spec optional.
hash(obj) obj.__hash__()
bool(obj) obj.__bool__() Python 3, fb to __len__
bool(obj) obj.__nonzero__() Python 2, fb to __len__
dir(obj) obj.__dir__()
vars(obj) obj.__dict__ does not include __slots__
type(obj) obj.__class__ type actually bypasses __class__ -
overriding __class__ will not affect type
help(obj) obj.__doc__ help uses more than just __doc__
len(obj) obj.__len__() provides fb behavior for bool
iter(obj) obj.__iter__() fb to __getitem__ w/ indexes from 0 on
next(obj) obj.__next__() Python 3
next(obj) obj.next() Python 2
reversed(obj) obj.__reversed__() fb to __len__ and __getitem__
other in obj obj.__contains__(other) fb to __iter__ then __getitem__
obj == other obj.__eq__(other)
obj != other obj.__ne__(other) fb to not obj.__eq__(other) in Python 3
obj < other obj.__lt__(other) get >, >=, <= with @functools.total_ordering
complex(obj) obj.__complex__()
int(obj) obj.__int__()
float(obj) obj.__float__()
round(obj) obj.__round__()
abs(obj) obj.__abs__()
Moduł operator
ma length_hint
, który ma zabezpieczenie zaimplementowane odpowiednią specjalną metodą, jeśli __len__
nie jest zaimplementowany:
length_hint(obj) obj.__length_hint__()
Przerywane Poszukiwania
Przerywane wyszukiwania są kontekstowe. Bez specjalnej implementacji metod, najpierw poszukaj w hierarchii klas deskryptorów danych( takich jak właściwości i szczeliny), a następnie w instancji __dict__
(na przykład zmienne), następnie w hierarchii klas dla deskryptorów Nie-danych (jak metody). Specjalne metody implementują następujące zachowania:
obj.attr obj.__getattr__('attr') provides fb if dotted lookup fails
obj.attr obj.__getattribute__('attr') preempts dotted lookup
obj.attr = _ obj.__setattr__('attr', _) preempts dotted lookup
del obj.attr obj.__delattr__('attr') preempts dotted lookup
Deskryptory
Deskryptory są nieco zaawansowane - możesz pominąć te wpisy i wrócić później-przypomnij sobie, że instancja deskryptora znajduje się w hierarchii klas (jak metody, sloty i właściwości). Deskryptor danych implementuje albo __set__
albo __delete__
:
obj.attr descriptor.__get__(obj, type(obj))
obj.attr = val descriptor.__set__(obj, val)
del obj.attr descriptor.__delete__(obj)
Gdy klasa jest instancjowana (zdefiniowana), następująca metoda deskryptora __set_name__
jest wywoływana, jeśli jakiś deskryptor ma ją poinformować deskryptor o jego nazwie atrybutu. (Jest to nowość w Pythonie 3.6.) cls
jest taki sam jak type(obj)
powyżej, a 'attr'
oznacza nazwę atrybutu:
class cls:
@descriptor_type
def attr(self): pass # -> descriptor.__set_name__(cls, 'attr')
Items (notacja dolna)
Zapis dolny jest również kontekstowy:]}obj[name] -> obj.__getitem__(name)
obj[name] = item -> obj.__setitem__(name, item)
del obj[name] -> obj.__delitem__(name)
Specjalny przypadek dla podklas dict
, __missing__
jest wywoływane, jeśli __getitem__
nie znajdzie klucza:
obj[name] -> obj.__missing__(name)
Operatory
Istnieją również specjalne metody dla +, -, *, @, /, //, %, divmod(), pow(), **, <<, >>, &, ^, |
operatory, na przykład:
obj + other -> obj.__add__(other), fallback to other.__radd__(obj)
obj | other -> obj.__or__(other), fallback to other.__ror__(obj)
W zależności od tego, która z tych wartości jest większa, w zależności od tego, która z tych wartości jest większa.]}
obj += other -> obj.__iadd__(other)
obj |= other -> obj.__ior__(other)
I operacje jednostkowe:
+obj -> obj.__pos__()
-obj -> obj.__neg__()
~obj -> obj.__invert__()
Menedżery Kontekstu
Menedżer kontekstu definiuje __enter__
, która jest wywoływana przy wprowadzaniu bloku kodu (jego wartość zwracana, Zwykle self, jest aliasowana przez as
), i __exit__
, która jest gwarantowana przy opuszczaniu bloku kodu, z informacjami o wyjątkach.
with obj as cm: -> cm = obj.__enter__()
raise Exception('message')
-> obj.__exit__(Exception, Exception('message'), traceback_object)
Jeśli __exit__
otrzyma wyjątek i następnie zwraca wartość false, po opuszczeniu metody będzie ją ponownie uruchamiać.
Jeśli nie ma wyjątku, __exit__
otrzymujeNone
dla tych trzech argumentów, a wartość zwracana jest bez znaczenia:
with obj: -> obj.__enter__()
pass
-> obj.__exit__(None, None, None)
Niektóre Metaklasy Specjalne Metody
Podobnie, klasy mogą mieć specjalne metody (ze swoich metaklas), które wspierają abstrakcyjne klasy bazowe:]}isinstance(obj, cls) -> cls.__instancecheck__(obj)
issubclass(sub, cls) -> cls.__subclasscheck__(sub)
Ważne jest to, że podczas gdy builtins jak next
i bool
nie zmieniają się między Pythonem 2 i 3, podstawowe nazwy implementacji zmieniają się.
W ten sposób korzystanie z wbudowanych oferuje również większą kompatybilność.
Kiedy mam używać specjalnych nazw?
W Pythonie Nazwy zaczynające się od podkreślenia są semantycznie niepublicznymi nazwami dla użytkowników. Podkreśleniem jest sposób Stwórcy powiedzenie: "Ręce precz, nie dotykaj."
Jest to nie tylko kulturowe, ale także w traktowaniu API przez Pythona. gdy pakiet __init__.py
używa import *
do dostarcza API z podpakietu, jeśli podpakiet nie zawiera __all__
, wyklucza Nazwy zaczynające się od podkreślenia. Nie można również wykluczyć podpakietu __name__
.
Narzędzia autocompletion IDE są mieszane pod względem nazw zaczynających się od podkreślenia jako Niepubliczne. Jednak bardzo doceniam, że nie widzę __init__
, __new__
, __repr__
, __str__
, __eq__
, itd. (ani żaden z użytkowników nie stworzył interfejsów niepublicznych) kiedy wpisuję nazwę obiektu i kropka.
Tak twierdzę:
Specjalne metody "dunder" nie są częścią publicznego interfejsu. Unikaj używania ich bezpośrednio.
Kiedy ich używać?Głównym przypadkiem użycia jest implementacja własnego obiektu lub podklasy wbudowanego obiektu.
Staraj się używać ich tylko wtedy, gdy jest to absolutnie konieczne. Oto kilka przykładów:Użyj specjalnego atrybutu __name__
na funkcjach lub klasach
Kiedy udekorujemy funkcja, zazwyczaj otrzymujemy w zamian funkcję wrappera, która ukrywa pomocne informacje o funkcji. Użyjemy dekoratora @wraps(fn)
, aby upewnić się, że nie utracimy tej informacji, ale jeśli potrzebujemy nazwy funkcji, musimy użyć atrybutu __name__
bezpośrednio:
from functools import wraps
def decorate(fn):
@wraps(fn)
def decorated(*args, **kwargs):
print('calling fn,', fn.__name__) # exception to the rule
return fn(*args, **kwargs)
return decorated
Podobnie, wykonuję następujące czynności, gdy potrzebuję nazwy klasy obiektu w metodzie (używanej na przykład w __repr__
):
def get_class_name(self):
return type(self).__name__
# ^ # ^- must use __name__, no builtin e.g. name()
# use type, not .__class__
Używanie specjalnych atrybutów do pisania niestandardowych klas lub podklasowanych builtins
Kiedy chcemy zdefiniować własne zachowanie, musimy użyć nazw modeli danych.
To ma sens, ponieważ jesteśmy wykonawcami, te atrybuty nie są dla nas prywatne.
class Foo(object):
# required to here to implement == for instances:
def __eq__(self, other):
# but we still use == for the values:
return self.value == other.value
# required to here to implement != for instances:
def __ne__(self, other): # docs recommend for Python 2.
# use the higher level of abstraction here:
return not self == other
Jednak, nawet w tym przypadku, nie używamy self.value.__eq__(other.value)
lub not self.__eq__(other)
(zobacz moją odpowiedź tutaj , aby udowodnić, że ta ostatnia może prowadzić do nieoczekiwanego zachowania.) Zamiast tego powinniśmy użyć wyższego poziomu abstrakcji.
Kolejny punkt, w którym musimy użyć specjalnego nazwy metod są wtedy, gdy jesteśmy w implementacji dziecka i chcemy przekazać je rodzicowi. Na przykład:
class NoisyFoo(Foo):
def __eq__(self, other):
print('checking for equality')
# required here to call the parent's method
return super(NoisyFoo, self).__eq__(other)
Podsumowanie
Specjalne metody pozwalają użytkownikom zaimplementować interfejs dla wewnętrznych obiektów.
Używaj wbudowanych funkcji i operatorów, gdzie tylko możesz. Używaj specjalnych metod tylko wtedy, gdy nie ma udokumentowanego publicznego API.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-26 19:50:54
Pokażę użycie, o którym najwyraźniej nie pomyślałeś, skomentuję przykłady, które pokazałeś i argumentuję przeciwko roszczeniu o prywatności z twojej własnej odpowiedzi.
Zgadzam się z Twoją własną odpowiedzią, że na przykład len(a)
powinno być używane, a nie a.__len__()
. Ująłbym to tak: len
istnieje więc możemy go używać, a __len__
istnieje więc len
możemy go używać . Lub jednak to naprawdę działa wewnętrznie, ponieważ len(a)
może być rzeczywiście dużo szybciej , przynajmniej na przykład dla list i ciągi:
>>> timeit('len(a)', 'a = [1,2,3]', number=10**8)
4.22549770486512
>>> timeit('a.__len__()', 'a = [1,2,3]', number=10**8)
7.957335462257106
>>> timeit('len(s)', 's = "abc"', number=10**8)
4.1480574509332655
>>> timeit('s.__len__()', 's = "abc"', number=10**8)
8.01780160432645
Ale poza definiowaniem tych metod w moich klasach do użycia przez wbudowane funkcje i operatory, czasami używam ich również w następujący sposób:]}
Powiedzmy, że muszę dać funkcję filtra jakiejś funkcji i chcę użyć zestawu s
jako filtra. Nie zamierzam tworzyć dodatkowej funkcji lambda x: x in s
ani def f(x): return x in s
. Nie. Mam już idealną funkcję, którą mogę wykorzystać: metodę __contains__
zestawu. To prostsze i bardziej bezpośrednie. I jeszcze szybciej, jak pokazano tutaj (ignoruj, że zapisuję to jako f
tutaj, to tylko dla tego czasu demo):
>>> timeit('f(2); f(4)', 's = {1, 2, 3}; f = s.__contains__', number=10**8)
6.473739433621368
>>> timeit('f(2); f(4)', 's = {1, 2, 3}; f = lambda x: x in s', number=10**8)
19.940786514456924
>>> timeit('f(2); f(4)', 's = {1, 2, 3}\ndef f(x): return x in s', number=10**8)
20.445680107760325
Więc podczas gdy nie bezpośrednio wywołam magiczne metody jak s.__contains__(x)
, czasami przekazuję je gdzieś jaksome_function_needing_a_filter(s.__contains__)
. I myślę, że to jest całkowicie w porządku, i lepiej niż alternatywa lambda / def.
Moje przemyślenia na temat przykładów, które pokazałeś:
-
przykład 1 : zapytany, jak uzyskać rozmiar listy, odpowiedział
items.__len__()
. Nawet bez żadnego rozumowania. My werdykt: to jest po prostu złe. Powinno byćlen(items)
. -
przykład 2 : pierwsza wzmianka
d[key] = value
! A następnie dodaje {[18] } z rozumowaniem "jeśli na klawiaturze brakuje klawiszy z nawiasami kwadratowymi" , co rzadko ma zastosowanie i w co wątpię było poważne. Myślę, że to była tylko stopa w drzwiach na ostatni punkt, wspominając, że w ten sposób możemy wspierać składnię nawiasów kwadratowych w naszych klasach. Co zamienia go z powrotem w sugestię użycia kwadratu nawiasy. -
Przykład 3 : Sugeruje
obj.__dict__
. Źle, jak przykład__len__
. Ale podejrzewam, że po prostu nie wiedziałvars(obj)
, i mogę to zrozumieć, ponieważvars
jest mniej powszechny/znany, a nazwa różni się od "dict" w__dict__
. -
Przykład 4 : Sugeruje
__class__
. Powinno byćtype(obj)
. Podejrzewam, że jest podobna do historii__dict__
, chociaż myślę, żetype
jest bardziej znana.
O prywatności: we własnej odpowiedzi mówisz, że te metody są "semantycznie prywatne". Zdecydowanie się nie zgadzam. Pojedyncze i podwójne przodujące podkreślniki są do tego przeznaczone, ale nie są to specjalne metody "Dunder/magic" modelu danych z podwójnym wiodącym+kończącym podkreślnikami.
- dwie rzeczy, których używasz jako argumentów to importowanie zachowania i autocompletion IDE. Ale importowanie i te specjalne metody to różne obszary, a jeden IDE próbowałem (popularny PyCharm) nie zgadza się z Tobą. Stworzyłem klasę / obiekt za pomocą metod
_foo
i__bar__
i wtedy autocompletion nie zaoferował_foo
, ale zaoferował . A kiedy i tak korzystałem z obu metod, PyCharm ostrzegał mnie tylko o_foo
(nazywając go "chronionym członkiem"), , a nie o o__bar__
. - PEP 8 mówi 'słaby" wewnętrzny wskaźnik " jawnie dla pojedynczego podkreślenia wiodącego, a jawnie dla podwójnego wiodącego podkreślenia wspomina nazwę mangling, a później wyjaśnia, że jest to dla "atrybuty, które nie chcę, aby podklasy używały " . Ale komentarz o Double leading+trailing underscores nic takiego nie mówi.
- strona modelu danych , do której sam link, mówi, że te specjalne nazwy metod to "podejście Pythona do przeciążania operatorów". Nie ma tu nic o prywatności. Słowa private/privacy / protected nie pojawiają się nigdzie na tej stronie.
Polecam również przeczytać ten artykuł autorstwa Andrzeja Montalti o tych metodach, podkreślając, że "konwencja dunder jest przestrzenią nazw zarezerwowaną dla podstawowego zespołu Pythona" i {66]}"nigdy, przenigdy, nie wymyślaj własnych dunderów"{67]} ponieważ {66]} "rdzenny zespół Pythona zarezerwował nieco brzydką przestrzeń nazw dla siebie" {67]}. W przeciwieństwie do innych postaci, które nie są w stanie stworzyć magicznej magii, nie są w stanie stworzyć magicznej magii. Myślę, że Andrew jest na miejscu - to tylko brzydka przestrzeń nazw podstawowego zespołu. I to dla cel przeciążenia operatora, nie o prywatność (nie o to chodzi, ale o moją i stronę modelu danych).
Oprócz artykułu Andrew sprawdziłem jeszcze kilka innych o tych"magicznych"/" dunder " metodach i nie znalazłem żadnej z nich mówiącej o prywatności w ogóle. Nie o to tu chodzi.
Ponownie powinniśmy użyć len(a)
, a nie a.__len__()
. Ale nie z powodu prywatności.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:03:02