@Pythonetc kompilasi, Agustus 2019



Pilihan baru tips dan pemrograman Python dari umpan @pythonetc saya.

Koleksi sebelumnya


Jika 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 # 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 Django classonlymethod : https://github.com/django/django/blob/b709d701303b3877387020c1558a590713b09853/django/utils/decorators.py#L6


Fungsi 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: # ← ← ← 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)) 

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

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


All Articles