
Pilihan baru tips dan pemrograman Python dari umpan @pythonetc saya.
←
Koleksi sebelumnyaJika instance kelas tidak memiliki atribut dengan nama yang diberikan, maka ia mencoba mengakses atribut kelas dengan nama yang sama.
>>> class A: ... x = 2 ... >>> Ax 2 >>> A().x 2
Sebuah instance dapat dengan mudah 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'
Jika Anda ingin instance berperilaku seolah-olah tidak memiliki atribut, meskipun kelas memilikinya, Anda harus membuat deskriptor khusus yang melarang akses dari instance ini:
class ClassOnlyDescriptor: def __init__(self, value): self._value = value self._name = None
Lihat juga bagaimana dekorator Django
classonlymethod
:
https://github.com/django/django/blob/b709d701303b3877387020c1558a590713b09853/django/utils/decorators.py#L6Fungsi yang dideklarasikan di badan kelas tidak dapat diakses ke ruang lingkup kelas ini. Ini karena ruang lingkup ini hanya ada selama pembuatan kelas.
>>> class A: ... x = 2 ... def f(): ... print(x) ... f() ... [...] NameError: name 'x' is not defined
Ini 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
Yang mengejutkan, hal yang sama berlaku untuk pemahaman. Mereka memiliki ruang lingkup mereka sendiri dan mereka juga tidak memiliki akses ke ruang lingkup kelas. Ini sangat logis dalam hal pemahaman generator: kode di dalamnya dieksekusi ketika kelas sudah dibuat.
>>> 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 memberikan akses ke
x
adalah menambahkan ruang lingkup lain (yeah, solusi bodoh):
>>> class A: ... x = 2 ... y = (lambda x=x: [x for _ in range(5)])() ... >>> Ay [2, 2, 2, 2, 2]
Dalam Python,
None
setara dengan
None
, jadi sepertinya Anda dapat memeriksa
None
dengan
==
:
ES_TAILS = ('s', 'x', 'z', 'ch', 'sh') def make_plural(word, exceptions=None): if exceptions == None:
Tapi itu akan menjadi kesalahan. Ya,
None
yang sama dengan
None
, tetapi tidak hanya itu. Objek khusus juga bisa berupa
None
:
>>> class A: ... def __eq__(self, other): ... return True ... >>> A() == None True >>> A() is None False
Satu-satunya cara yang benar untuk membandingkan dengan
None
adalah dengan menggunakan
is None
.
Angka titik apung di Python dapat memiliki nilai NaN. Misalnya, angka seperti itu dapat diperoleh dengan menggunakan
math.nan
.
nan
tidak sama dengan apa pun, termasuk dirinya sendiri:
>>> math.nan == math.nan False
Selain itu, objek NaN tidak unik, Anda dapat memiliki beberapa objek NaN berbeda dari sumber berbeda:
>>> float('nan') nan >>> float('nan') is float('nan') False
Ini berarti, secara umum, Anda 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. Selain itu, Anda dapat menentukan jenis yang dihasilkan, yang dilewatkan ke generator, dan yang dikembalikan menggunakan
return
. Sebagai contoh,
Generator[int, None, bool]
menghasilkan bilangan bulat, mengembalikan Boolean, dan tidak mendukung
g.send()
.
Tetapi contohnya lebih rumit.
chain_while
data dari generator lain sampai salah satu dari mereka mengembalikan nilai yang merupakan sinyal 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, )))
Mengatur anotasi untuk metode pabrik tidak semudah kelihatannya. Hanya ingin menggunakan sesuatu seperti ini:
class A: @classmethod def create(cls) -> 'A': return cls()
Tapi itu salah. Kuncinya adalah bahwa
create
pengembalian bukan
A
, itu mengembalikan
cls
, yang merupakan
A
atau salah satu turunannya. Lihatlah kodenya:
class A: @classmethod def create(cls) -> 'A': return cls() class B(A): @classmethod def create(cls) -> 'B': return super().create()
Hasil pemeriksaan mypy adalah kesalahan
error: Incompatible return value type (got "A", expected "B")
. Sekali lagi, masalahnya adalah
super().create()
dijelaskan sebagai pengembalian
A
, meskipun dalam kasus ini mengembalikan
B
Ini bisa diperbaiki jika annotating
cls
menggunakan
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()
create
sekarang mengembalikan sebuah instance dari kelas
cls
. Namun, penjelasan ini terlalu kabur, 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()
Kami
"Type[AType]" has no attribute "DATA"
kesalahan
"Type[AType]" has no attribute "DATA"
.
Untuk memperbaikinya, Anda harus secara eksplisit mendefinisikan
AType
sebagai subtipe dari
A
Untuk ini,
TypeVar
dengan argumen
bound
digunakan.
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()