Tipps und Tricks von meinem Telegramm-Kanal @pythonetc, August 2019



Es ist eine neue Auswahl von Tipps und Tricks zu Python und Programmierung von meinem Telegramm-Kanal @pythonetc.

Frühere Veröffentlichungen


Wenn 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 # see __set_name__ def __get__(self, instance, owner): if instance is not None: raise AttributeError( f'{instance} has no attribute {self._name}' ) return self._value def __set_name__(self, owner, name): self._name = name class_only = ClassOnlyDescriptor class A: x = class_only(2) print(Ax) # 2 A().x # raises AttributeError 

Siehe auch die Funktionsweise des Django classonlymethod Decorators: https://github.com/django/django/blob/b709d701303b3877387020c1558a590713b09853/django/utils/decorators.py#L6


In 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: # ← ← ← exceptions = {} if word in exceptions: return exceptions[word] elif any(word.endswith(t) for t in ES_TAILS): return word + 'es' elif word.endswith('y'): return word[0:-1] + 'ies' else: return word + 's' exceptions = dict( mouse='mice', ) print(make_plural('python')) print(make_plural('bash')) print(make_plural('ruby')) print(make_plural('mouse', exceptions=exceptions)) 

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

Source: https://habr.com/ru/post/de466317/


All Articles