
Es ist eine neue Auswahl von Tipps und Tricks zu Python und Programmierung von meinem Telegramm-Kanal @pythonetc.
←
Frühere VeröffentlichungenWenn eine Instanz einer Klasse kein Attribut mit dem angegebenen Namen hat, versucht sie, auf das Klassenattribut mit demselben Namen zuzugreifen.
>>> class A: ... x = 2 ... >>> Ax 2 >>> A().x 2
Es ist ziemlich einfach für eine Instanz, ein Attribut zu haben, das eine Klasse nicht hat, oder das Attribut mit dem anderen Wert:
>>> class A: ... x = 2 ... def __init__(self): ... self.x = 3 ... self.y = 4 ... >>> A().x 3 >>> Ax 2 >>> A().y 4 >>> Ay AttributeError: type object 'A' has no attribute 'y'
Wenn es jedoch nicht so einfach ist, wenn Sie möchten, dass sich eine Instanz so verhält, als hätte sie kein Attribut, obwohl die Klasse es hat. Um dies zu erreichen, müssen Sie einen benutzerdefinierten Deskriptor erstellen, der keinen Zugriff von der Instanz aus zulässt:
class ClassOnlyDescriptor: def __init__(self, value): self._value = value self._name = None
Siehe auch die Funktionsweise des Django
classonlymethod
Decorators:
https://github.com/django/django/blob/b709d701303b3877387020c1558a590713b09853/django/utils/decorators.py#L6In einem Klassenkörper deklarierte Funktionen können den Klassenbereich nicht sehen. Dies ist sinnvoll, da der Klassenbereich nur während der Klassenerstellung vorhanden ist.
>>> class A: ... x = 2 ... def f(): ... print(x) ... f() ... [...] NameError: name 'x' is not defined
Das ist normalerweise kein Problem: Methoden werden innerhalb einer Klasse deklariert, nur um Methoden zu werden und später aufgerufen zu werden:
>>> class A: ... x = 2 ... def f(self): ... print(self.x) ... >>> >>> >>> A().f() 2
Etwas überraschend gilt das Gleiche für das Verständnis. Sie haben ihre eigenen Bereiche und können auch nicht auf den Klassenbereich zugreifen. Das ist für das Verständnis des Generators wirklich sinnvoll: Sie werten Ausdrücke aus, nachdem die Klassenerstellung bereits abgeschlossen ist.
>>> class A: ... x = 2 ... y = [x for _ in range(5)] ... [...] NameError: name 'x' is not defined
Verständnis hat jedoch keinen Zugang zu sich
self
. Die einzige Möglichkeit, es zum Laufen zu bringen, besteht darin, einen weiteren Bereich hinzuzufügen (ja, das ist hässlich):
>>> class A: ... x = 2 ... y = (lambda x=x: [x for _ in range(5)])() ... >>> Ay [2, 2, 2, 2, 2]
In Python ist
None
gleich
None
sodass Sie
None
mit
==
nach
None
==
:
ES_TAILS = ('s', 'x', 'z', 'ch', 'sh') def make_plural(word, exceptions=None): if exceptions == None:
Dies ist jedoch eine falsche Vorgehensweise.
None
ist in der Tat gleich
None
, aber es ist nicht das einzige, was ist. Benutzerdefinierte Objekte können auch gleich
None
sein:
>>> class A: ... def __eq__(self, other): ... return True ... >>> A() == None True >>> A() is None False
Der einzig richtige Weg, um mit
None
zu vergleichen,
is None
.
Python-Floats können NaN-Werte haben. Sie können eine mit
math.nan
.
nan
ist nichts gleich sich selbst:
>>> math.nan == math.nan False
Außerdem ist das NaN-Objekt nicht eindeutig. Sie können mehrere verschiedene NaN-Objekte aus verschiedenen Quellen verwenden:
>>> float('nan') nan >>> float('nan') is float('nan') False
Das bedeutet, dass Sie NaN im Allgemeinen nicht als Wörterbuchschlüssel verwenden können:
>>> d = {} >>> d[float('nan')] = 1 >>> d[float('nan')] = 2 >>> d {nan: 1, nan: 2}
typing
können Sie den Typ für Generatoren definieren. Sie können zusätzlich angeben, welcher Typ ausgegeben wird, welcher Typ an einen Generator gesendet werden kann und was zurückgegeben wird.
Generator[int, None, bool]
ist ein Generator, der Ganzzahlen liefert, einen booleschen Wert zurückgibt und
g.send()
nicht unterstützt.
Hier ist ein etwas komplizierteres Beispiel.
chain_while
gibt von anderen Generatoren nach, bis einer von ihnen etwas zurückgibt, das ein Signal zum Stoppen gemäß der
condition
ist:
from typing import Generator, Callable, Iterable, TypeVar Y = TypeVar('Y') S = TypeVar('S') R = TypeVar('R') def chain_while( iterables: Iterable[Generator[Y, S, R]], condition: Callable[[R], bool], ) -> Generator[Y, S, None]: for it in iterables: result = yield from it if not condition(result): break def r(x: int) -> Generator[int, None, bool]: yield from range(x) return x % 2 == 1 print(list(chain_while( [ r(5), r(4), r(3), ], lambda x: x is True, )))
Das Annotieren einer Factory-Methode ist nicht so einfach, wie es scheint. Der unmittelbare Drang besteht darin, so etwas zu verwenden:
class A: @classmethod def create(cls) -> 'A': return cls()
Dies ist jedoch nicht richtig. Der Haken ist,
create
gibt kein
A
, sondern eine Instanz von cls, die
A
oder einer ihrer Nachkommen ist. Schauen Sie sich diesen Code an:
class A: @classmethod def create(cls) -> 'A': return cls() class B(A): @classmethod def create(cls) -> 'B': return super().create()
Das Ergebnis der mypy-Prüfung ist ein
error: Incompatible return value type (got "A", expected "B")
. Auch hier ist das Problem
super().create()
ist mit Annotationen versehen, um
A
während es in diesem Fall eindeutig
B
zurückgibt.
Sie können dies beheben, indem Sie cls mit TypeVar kommentieren:
AType = TypeVar('AType') BType = TypeVar('BType') class A: @classmethod def create(cls: Type[AType]) -> AType: return cls() class B(A): @classmethod def create(cls: Type[BType]) -> BType: return super().create()
Jetzt gibt
create
die Instanz der Klasse
cls
. Diese Anmerkungen sind jedoch zu locker. Wir haben die Information verloren, dass
cls
ein Subtyp von
A
:
AType = TypeVar('AType') class A: DATA = 42 @classmethod def create(cls: Type[AType]) -> AType: print(cls.DATA) return cls()
Der Fehler ist
"Type[AType]" has no attribute "DATA"
.
Um dies zu beheben, müssen Sie
AType
explizit als Subtyp von
A
mit dem
bound
Argument von
TypeVar
:
AType = TypeVar('AType', bound='A') BType = TypeVar('BType', bound='B') class A: DATA = 42 @classmethod def create(cls: Type[AType]) -> AType: print(cls.DATA) return cls() class B(A): @classmethod def create(cls: Type[BType]) -> BType: return super().create()