
Ini adalah pilihan baru tips dan trik tentang Python dan pemrograman dari saluran-Telegram saya @pythonetc.
←
Publikasi sebelumnyaJika 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
Lihat juga bagaimana dekorator
classonlymethod
Django
classonlymethod
bekerja:
https://github.com/django/django/blob/b709d701303b3877387020c1558a590713b09853/django/utils/decorators.py#L6Fungsi 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:
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()