Aby kontrolować wartość prawdy w instancjach klas zdefiniowanych przez użytkownika, należy korzystać z metod specjalnych __nonzero__ lub __len__. Korzystamy z __len__, jeżeli nasza klasa jest kontenerem, który ma długość (przykład z sieci, Code Like a Pythonist).
class MyContainer(object): """Klasa definiująca pewien kontener.""" def __init__(self, data): self.data = data def __len__(self): """Return my length.""" return len(self.data)
Jeżeli nasza klasa nie jest kontenerem, korzystamy z __bool__ (Py3) i __nonzero__ (Py2).
class MyClass(object): """Klasa nie będąca kontenerem.""" def __init__(self, value): self.value = value def __bool__(self): Py3 """Return my truth value (True or False).""" # This could be arbitrarily complex: return bool(self.value) # Kompatybilność Py2 i Py3. __nonzero__ = __bool__
class Life: def __init__(self, name="NN"): # konstruktor print("Hello {}".format(name)) self.name = name def __str__(self): return self.name def __del__(self): # destruktor print("Goodbye {}".format(self.name)) adam = Life("Adam") # Zamazujemy jedyną referencję do Adama, działa destruktor. adam = Life("Bogdan") print("Before delete") print(adam) # Usuwamy nazwę adam z przestrzeni nazw. # Zamazujemy jedyną referencję do Bogdana, działa destruktor. del adam print("After delete")
# Wypisywane komunikaty. Hello Adam Hello Bogdan Goodbye Adam Before delete Bogdan Goodbye Bogdan After delete
Każda klasa Pythona ma metodę __hash__(), której domyślna implementacja ma postać:
def __hash__(self): return id(self)
User może zaimplementować to na nowo po swojemu. Funkcja hash(X) wywołuje metodę X.__hash__(). Klasy wbudowane Pythona z reguły redefiniują metodę __hash__().
# Przykładowo dla int mamy coś takiego. def __hash__(self): return int(self) # Dla list mamy wyjątek. def __hash__(self): raise TypeError("list objects are unhashable")
Główna idea dotycząca hash jest taka, że jeżeli dwa obiekty mają różny hash, to nie są równe, ale jeżeli mają taki sam hash, to _mogą_ być równe (to jest nazywane "hash collision"). Jedyne wymaganie ze strony Pythona jest aby wartości hash nie zmieniały się po tym, jak zostaną zwrócone przez metodę __hash__().
Trzeba pamiętać, że hashing jest platform-dependent. Stąd w SymPy mogą być różne kolejności zwracanych wyrażeń, co rodzi problemy przy pisaniu testów (np. doctest).
http://stackoverflow.com/questions/17192418/hash-function-in-python
hash() is randomised by default each time you start a new instance
of recent versions (Py3.3+) to prevent dictionary insertion DOS attacks.
If you want something that does hash to the same thing every time,
use one of the hashes in hashlib (import hashlib).
Niech a, b i c będą instancjami pewnej klasy Pythona. Sprawdzamy co się dzieje przy wywołaniu polecenia a == b. Python wywołuje metody w podanej kolejności. Jeżeli dana metoda nie jest zaimplementowana, lub jeżeli zwróci wyjątek NotImplemented, Python przeskakuje ją i próbuje następnej, aż do skutku. Ostatnia linia zawsze kończy się sukcesem.
# Polecenie a == b. a.__eq__(b) b.__eq__(a) a.__cmp__(b) # Py2 b.__cmp__(a) # Py2 id(a) == id(b)
# Polecenie a != b. a.__ne__(b) b.__ne__(a) a.__cmp__(b) # Py2 b.__cmp__(a) # Py2 id(a) != id(b) # tu jest błąd w SymPy
# Polecenie a < b. a.__lt__(b) b.__gt__(a) a.__cmp__(b) # Py2 b.__cmp__(a) # Py2 id(a) < id(b)
# Polecenie a <= b. a.__le__(b) b.__ge__(a) a.__cmp__(b) # Py2 b.__cmp__(a) # Py2 id(a) <= id(b)
# Polecenie a > b. a.__gt__(b) b.__lt__(a) a.__cmp__(b) # Py2 b.__cmp__(a) # Py2 id(a) > id(b)
# Polecenie a >= b. a.__ge__(b) b.__le__(a) a.__cmp__(b) # Py2 b.__cmp__(a) # Py2 id(a) >= id(b)
# Jeżeli napiszemy a in {d: 5} a in set([d, d, d]) set([a, b]) == set([a, b]) # Python najpierw porównuje hash, tzn.: a.__hash__() d.__hash__() # Jeżeli hash(a) != hash(d), to wynik wyrażenia a in {d: 5} # jest natychmiast False. # Jeżeli hash(a) == hash(d), to Python przechodzi przez method # resolution dla operatora == jak pokazano powyżej.
Widać, że w method resolution dla operatorów porównań nie ma hash, więc nie ma znaczenia co zwraca. Ale hash jest używany przy słownikach i zbiorach. Oficjalna dokumentacja Pythona mówi o obiektach hashable i non-hashable. W praktyce wystarczy prześledzić method resolution opisaną tutaj.
Python domyślnie przechowuje atrybuty instancji w słowniku __dict__, co daje możliwość dodawania atrybutów w czasie pracy programu. Przy małych klasach ze znanymi atrybutami może to jednak prowadzić do marnowania pamięci RAM. Można wymusić alokowanie stałej ilości pamięci na atrybuty przy zastosowaniu __slots__ zamiast słownika. Pewne problemy pojawiają się przy korzystaniu z dziedziczenia.
# https://stackoverflow.com/questions/472000/usage-of-slots class ClassWithoutSlots(object): def __init__(self, name, value): self.name = name self.value = value class ClassWithSlots(object): __slots__ = ['name', 'value'] def __init__(self, name, value): self.name = name self.value = value
PEP 367 -- New Super [Py2.6]
PEP 3135 -- New Super [Py3]
https://rhettinger.wordpress.com/2011/05/26/super-considered-super/
https://www.programiz.com/python-programming/methods/built-in/super
W Pythonie 2.2 pojawiła się funkcja wbudowana super(), która działa tylko z klasami w nowym stylu. Return a proxy object that delegates method calls to a parent or sibling class of type. This is useful for accessing inherited methods that have been overridden in a class.
# super(type[, object-or-type]) # Py2.7 # Typowe wywołanie funkcji super(): przy pojedynczym dziedziczeniu można # odwołać się do klasy nadrzędnej bez jawnego użycia jej nazwy. # To będzie działać nawet po zmianie klasy nadrzędnej. #class B(object): # Py2.7, klasy w nowym stylu class B: # Py3 def method(self, arg): print("B: {}".format(arg)) class C(B): def method(self, arg): super(C, self).method(arg) # Py2.7, szuka powyżej C # super(self.__class__, self).method(arg) # Py2.7, bez powtarzania C # super().method(arg) # Py3
# Korzystanie z nowej wersji super() w Pythonie 2.5 wymaga (PEP 367): from __future__ import new_super
Pewne metody klasy mogą tworzyć nowe instancje danej klasy. Powstaje pytanie jak to zrobić, aby w przyszłości ułatwić sobie zmiany w kodzie.
class Time: """Klasa reprezentująca odcinek czasu.""" def __init__(self, s=0): """Zwraca instancję klasy Time.""" self.s = int(s) def __add__(self, other): """Dodawanie odcinkow czasu.""" #return Time(self.s + other.s) # jawnie używamy nazwy "Time" #return type(self)(self.s + other.s) # działa dla klas w nowym stylu return self.__class__(self.s + other.s) # uniwersalne # self.__class__.__name__ to string "Time", np. dla __repr__