Kiat dan trik dari saluran Telegram saya @pythonetc, Agustus 2019



Ini adalah pilihan baru tips dan trik tentang Python dan pemrograman dari saluran-Telegram saya @pythonetc.

Publikasi sebelumnya


Jika instance kelas tidak memiliki atribut dengan nama yang diberikan, ia mencoba mengakses atribut kelas dengan nama yang sama.

>>> class A: ... x = 2 ... >>> Ax 2 >>> A().x 2 

Cukup sederhana untuk instance memiliki atribut yang tidak dimiliki kelas atau memiliki atribut dengan nilai yang berbeda:

 >>> 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' 

Namun, jika itu tidak sesederhana itu, jika Anda ingin instance berperilaku seperti itu tidak memiliki atribut meskipun kelas memilikinya. Untuk mewujudkannya, Anda harus membuat deskriptor khusus yang tidak memungkinkan akses akses dari instance:

 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 

Lihat juga bagaimana dekorator classonlymethod Django classonlymethod bekerja: https://github.com/django/django/blob/b709d701303b3877387020c1558a590713b09853/django/utils/decorators.py#L6


Fungsi yang dideklarasikan dalam badan kelas tidak dapat melihat ruang lingkup kelas. Masuk akal karena ruang lingkup kelas hanya ada selama pembuatan kelas.

 >>> class A: ... x = 2 ... def f(): ... print(x) ... f() ... [...] NameError: name 'x' is not defined 

Itu biasanya bukan masalah: metode dideklarasikan di dalam kelas hanya untuk menjadi metode dan dipanggil kemudian:

 >>> class A: ... x = 2 ... def f(self): ... print(self.x) ... >>> >>> >>> A().f() 2 

Agak mengherankan, hal yang sama berlaku untuk pemahaman. Mereka memiliki cakupan sendiri dan tidak dapat mengakses ruang lingkup kelas juga. Itu benar-benar masuk akal untuk pemahaman generator: mereka mengevaluasi ekspresi setelah pembuatan kelas selesai.

 >>> class A: ... x = 2 ... y = [x for _ in range(5)] ... [...] NameError: name 'x' is not defined 

Namun, pemahaman tidak memiliki akses ke self . Satu-satunya cara untuk membuatnya bekerja adalah dengan menambahkan satu cakupan lagi (ya, itu jelek):

 >>> class A: ... x = 2 ... y = (lambda x=x: [x for _ in range(5)])() ... >>> Ay [2, 2, 2, 2, 2] 



Dalam Python, None yang sama dengan None sehingga sepertinya Anda dapat memeriksa None dengan == :

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

Ini adalah hal yang salah untuk dilakukan. None yang benar-benar sama dengan None , tetapi bukan hanya itu saja. Objek khusus mungkin sama dengan None juga:

 >>> class A: ... def __eq__(self, other): ... return True ... >>> A() == None True >>> A() is None False 

Satu-satunya cara yang tepat untuk membandingkan dengan None adalah menggunakan is None .


Python floats dapat memiliki nilai NaN. Anda bisa mendapatkannya dengan math.nan . nan tidak sama dengan apa pun termasuk dirinya sendiri:

 >>> math.nan == math.nan False 

Juga, objek NaN tidak unik, Anda dapat memiliki beberapa objek NaN yang berbeda dari sumber yang berbeda:

 >>> float('nan') nan >>> float('nan') is float('nan') False 

Itu berarti bahwa Anda secara umum tidak dapat menggunakan NaN sebagai kunci kamus:

 >>> d = {} >>> d[float('nan')] = 1 >>> d[float('nan')] = 2 >>> d {nan: 1, nan: 2} 


typing memungkinkan Anda untuk menentukan tipe untuk generator. Anda juga dapat menentukan jenis apa yang dihasilkan, jenis apa yang dapat dikirim ke generator dan apa yang dikembalikan. Generator[int, None, bool] adalah generator yang menghasilkan bilangan bulat, mengembalikan nilai boolean dan tidak mendukung g.send() .

Berikut ini contoh yang sedikit lebih rumit. chain_while menghasilkan dari generator lain sampai salah satunya mengembalikan sesuatu yang merupakan sinyal untuk berhenti sesuai dengan fungsi condition :

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


Membuat anotasi metode pabrik tidak sesederhana kelihatannya. Desakan segera adalah untuk menggunakan sesuatu seperti ini:

 class A: @classmethod def create(cls) -> 'A': return cls() 

Namun, itu bukan hal yang benar untuk dilakukan. Tangkapannya adalah, create tidak mengembalikan A , ia mengembalikan instance dari cls yang A atau keturunannya. Lihatlah kode ini:

 class A: @classmethod def create(cls) -> 'A': return cls() class B(A): @classmethod def create(cls) -> 'B': return super().create() 

Hasil pemeriksaan mypy adalah error: Incompatible return value type (got "A", expected "B") . Sekali lagi, masalahnya super().create() dijelaskan untuk mengembalikan A sementara itu jelas mengembalikan B dalam kasus ini.

Anda bisa memperbaikinya dengan menjelaskan cls dengan TypeVar:

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

Sekarang create mengembalikan instance kelas cls . Namun, penjelasan ini terlalu longgar, kami kehilangan informasi bahwa cls adalah subtipe A :

 AType = TypeVar('AType') class A: DATA = 42 @classmethod def create(cls: Type[AType]) -> AType: print(cls.DATA) return cls() 

Kesalahannya adalah "Type[AType]" has no attribute "DATA" .

Untuk memperbaikinya Anda harus secara eksplisit mendefinisikan AType sebagai subtipe A dengan argumen bound 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/id466317/


All Articles