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



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

← Publikasi sebelumnya


pernyataan break menekan pengecualian jika digunakan dalam klausa finally bahkan ketika blok except tidak disajikan:

 for i in range(10): try: 1 / i finally: print('finally') break print('after try') print('after while') 

Keluaran:

 finally after while 

Hal yang sama berlaku untuk continue , namun akhirnya tidak dapat digunakan sampai Python 3.8:

 SyntaxError: 'continue' not supported inside 'finally' clause 


Anda dapat menambahkan karakter Unicode dalam string literal tidak hanya dengan nomornya, tetapi juga dengan namanya.

 >>> '\N{EM DASH}' '—' >>> '\u2014' '—' 

Ini juga kompatibel dengan f-string:

 >>> width = 800 >>> f'Width \N{EM DASH} {width}' 'Width — 800' 


Ada enam metode ajaib untuk objek Python yang menentukan aturan perbandingan:

  • __lt__ untuk <
  • __gt__ untuk >
  • __le__ untuk <=
  • __ge__ untuk >=
  • __eq__ untuk ==
  • __ne__ untuk !=

Jika beberapa metode ini tidak didefinisikan atau mengembalikan NotImplemented , aturan berikut ini diterapkan:

  • a.__lt__(b) sama dengan b.__gt__(a)
  • a.__le__(b) sama dengan b.__ge__(a)
  • a.__eq__(b) sama dengan not a.__ne__(b) (pikiran bahwa a dan b tidak ditukar dalam kasus ini)

Namun, a >= b dan a != b tidak secara otomatis menyiratkan a > b . Dekorator functools.total_ordering membuat semua enam metode berdasarkan __eq__ dan salah satu dari yang berikut: __lt__ , __gt__ , __le__ , atau __ge__ .

 from functools import total_ordering @total_ordering class User: def __init__(self, pk, name): self.pk = pk self.name = name def __le__(self, other): return self.pk <= other.pk def __eq__(self, other): return self.pk == other.pk assert User(2, 'Vadim') < User(13, 'Catherine') 


Terkadang Anda ingin menggunakan versi fungsi yang didekorasi dan tidak didekorasi. Cara termudah untuk mencapai itu adalah dengan melepaskan sintaks dekorator khusus (yang dengan @ ) dan membuat fungsi yang didekorasi secara manual:

 import json def ensure_list(f): def decorated(*args, **kwargs): result = f(*args, **kwargs) if isinstance(result, list): return result else: return [result] return decorated def load_data_orig(string): return json.loads(string) load_data = ensure_list(load_data_orig) print(load_data('3')) # [3] print(load_data_orig('4')) 4 

Atau, Anda dapat menulis dekorator lain, yang menghiasi suatu fungsi sambil mempertahankan versi aslinya dalam atribut asli dari yang baru:

 import json def saving_orig(another_decorator): def decorator(f): decorated = another_decorator(f) decorated.orig = f return decorated return decorator def ensure_list(f): ... @saving_orig(ensure_list) def load_data(string): return json.loads(string) print(load_data('3')) # [3] print(load_data.orig('4')) # 4 

Jika semua dekorator yang bekerja dengan Anda dibuat melalui functools.wraps Anda dapat menggunakan atribut __wrapped__ untuk mengakses fungsi yang tidak didekorasi:

 import json from functools import wraps def ensure_list(f): @wraps(f) def decorated(*args, **kwargs): result = f(*args, **kwargs) if isinstance(result, list): return result else: return [result] return decorated @ensure_list def load_data(string): return json.loads(string) print(load_data('3')) # [3] print(load_data.__wrapped__('4')) # 4 

Namun, __wrapped__ bahwa itu tidak berfungsi untuk fungsi yang didekorasi oleh lebih dari satu dekorator: Anda harus mengakses __wrapped__ untuk setiap dekorator yang diterapkan:

 def ensure_list(f): ... def ensure_ints(f): @wraps(f) def decorated(*args, **kwargs): result = f(*args, **kwargs) return [int(x) for x in result] return decorated @ensure_ints @ensure_list def load_data(string): return json.loads(string) for f in ( load_data, load_data.__wrapped__, load_data.__wrapped__.__wrapped__, ): print(repr(f('"4"'))) 

Keluaran:

 [4] ['4'] '4' 

@saving_orig disebutkan di atas menerima dekorator lain sebagai argumen. Bagaimana jika dekorator itu dapat parametrized? Nah, karena dekorator yang diparameterisasi adalah fungsi yang mengembalikan dekorator yang sebenarnya, kasing ini ditangani secara otomatis:

 import json from functools import wraps def saving_orig(another_decorator): def decorator(f): decorated = another_decorator(f) decorated.orig = f return decorated return decorator def ensure_ints(*, default=None): def decorator(f): @wraps(f) def decorated(*args, **kwargs): result = f(*args, **kwargs) ints = [] for x in result: try: x_int = int(x) except ValueError: if default is None: raise else: x_int = default ints.append(x_int) return ints return decorated return decorator @saving_orig(ensure_ints(default=0)) def load_data(string): return json.loads(string) print(repr(load_data('["2", "3", "A"]'))) print(repr(load_data.orig('["2", "3", "A"]'))) 

Dekorator @saving_orig tidak benar-benar melakukan apa yang kita inginkan jika ada lebih dari satu dekorator yang diterapkan pada suatu fungsi. Kita harus memanggil orig untuk setiap dekorator seperti:

 import json from functools import wraps def saving_orig(another_decorator): def decorator(f): decorated = another_decorator(f) decorated.orig = f return decorated return decorator def ensure_list(f): ... def ensure_ints(*, default=None): ... @saving_orig(ensure_ints(default=42)) @saving_orig(ensure_list) def load_data(string): return json.loads(string) for f in ( load_data, load_data.orig, load_data.orig.orig, ): print(repr(f('"X"'))) 

Keluaran:

 [42] ['X'] 'X' 

Kami dapat memperbaikinya dengan mendukung sejumlah dekorator saving_orig sebagai argumen saving_orig :

 def saving_orig(*decorators): def decorator(f): decorated = f for d in reversed(decorators): decorated = d(decorated) decorated.orig = f return decorated return decorator ... @saving_orig( ensure_ints(default=42), ensure_list, ) def load_data(string): return json.loads(string) for f in ( load_data, load_data.orig, ): print(repr(f('"X"'))) 

Keluaran:

 [42] 'X' 

Solusi lain adalah membuat saving_orig cukup pintar untuk melewatkan orig dari satu fungsi yang didekorasi:

 def saving_orig(another_decorator): def decorator(f): decorated = another_decorator(f) if hasattr(f, 'orig'): decorated.orig = f.orig else: decorated.orig = f return decorated return decorator @saving_orig(ensure_ints(default=42)) @saving_orig(ensure_list) def load_data(string): return json.loads(string) 

Jika dekorator yang Anda tulis menjadi terlalu rumit, mungkin masuk akal untuk mengubahnya dari fungsi ke kelas dengan metode __call__

 class SavingOrig: def __init__(self, another_decorator): self._another = another_decorator def __call__(self, f): decorated = self._another(f) if hasattr(f, 'orig'): decorated.orig = f.orig else: decorated.orig = f return decorated saving_orig = SavingOrig 

Baris terakhir memungkinkan Anda berdua untuk memberi nama kelas dengan case unta dan menyimpan nama dekorator dalam case ular.

Alih-alih memodifikasi fungsi yang didekorasi, Anda dapat membuat kelas yang dapat dipanggil lainnya untuk mengembalikan instansnya alih-alih fungsi:

 class CallableWithOrig: def __init__(self, to_call, orig): self._to_call = to_call self._orig = orig def __call__(self, *args, **kwargs): return self._to_call(*args, **kwargs) @property def orig(self): if isinstance(self._orig, type(self)): return self._orig.orig else: return self._orig class SavingOrig: def __init__(self, another_decorator): self._another = another_decorator def __call__(self, f): return CallableWithOrig(self._another(f), f) saving_orig = SavingOrig 

Lihat seluruh kode di sini

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


All Articles