@Pythonetc September 2018


Ini adalah koleksi keempat tips dan pemrograman Python dari umpan @pythonetc saya.


Pilihan sebelumnya:



Override dan Overload


Ada dua konsep yang mudah membingungkan: overriding dan overloading.


Overriding terjadi ketika kelas anak mendefinisikan metode yang sudah disediakan oleh kelas induk, dan dengan demikian menggantikannya. Dalam beberapa bahasa, perlu untuk secara eksplisit menandai metode override (dalam C # pengubah override digunakan), dan dalam beberapa bahasa ini dilakukan seperti yang diinginkan ( @Override annotation in Java). Python tidak memerlukan penggunaan pengubah khusus dan tidak memberikan tanda standar metode tersebut (demi keterbacaan, seseorang menggunakan dekorator kustom @override , yang tidak melakukan apa-apa).


Kelebihan adalah cerita lain. Istilah ini mengacu pada situasi di mana ada beberapa fungsi dengan nama yang sama, tetapi dengan tanda tangan yang berbeda. Overloading dimungkinkan di Java dan C ++, sering digunakan untuk memberikan argumen default:


 class Foo { public static void main(String[] args) { System.out.println(Hello()); } public static String Hello() { return Hello("world"); } public static String Hello(String name) { return "Hello, " + name; } } 

Python tidak mendukung pencarian fungsi berdasarkan tanda tangan, hanya berdasarkan nama. Tentu saja, Anda dapat menulis kode yang secara eksplisit menganalisis jenis dan jumlah argumen, tetapi ini akan terlihat canggung, dan praktik ini sebaiknya dihindari:


 def quadrilateral_area(*args): if len(args) == 4: quadrilateral = Quadrilateral(*args) elif len(args) == 1: quadrilateral = args[0] else: raise TypeError() return quadrilateral.area() 

Jika Anda membutuhkan petunjuk jenis, gunakan modul typing dengan dekorator @overload :


 from typing import overload @overload def quadrilateral_area( q: Quadrilateral ) -> float: ... @overload def quadrilateral_area( p1: Point, p2: Point, p3: Point, p4: Point ) -> float: ... 

Vivifikasi Otomatis


collections.defaultdict memungkinkan Anda membuat kamus yang mengembalikan nilai default jika kunci yang diminta hilang (alih-alih melempar KeyError ). Untuk membuat defaultdict Anda harus memberikan tidak hanya nilai default, tetapi pabrik nilai-nilai tersebut.


Jadi Anda dapat membuat kamus dengan kamus tersarang yang jumlahnya hampir tak terbatas, yang memungkinkan Anda untuk menggunakan konstruksi seperti d[a][b][c]...[z] .


 >>> def infinite_dict(): ... return defaultdict(infinite_dict) ... >>> d = infinite_dict() >>> d[1][2][3][4] = 10 >>> dict(d[1][2][3][5]) {} 

Perilaku ini disebut "auto-vivification," sebuah istilah yang berasal dari Perl.


Instansiasi


Instansiasi objek melibatkan dua langkah penting. Pertama, metode __new__ dipanggil dari kelas, yang membuat dan mengembalikan objek baru. Kemudian Python memanggil metode __init__ dari itu, yang menetapkan keadaan awal objek ini.


Namun, __init__ tidak akan dipanggil jika __new__ mengembalikan objek yang bukan turunan dari kelas asli. Dalam hal ini, objek dapat dibuat oleh kelas lain, yang berarti __init__ telah dipanggil pada objek:


 class Foo: def __new__(cls, x): return dict(x=x) def __init__(self, x): print(x) # Never called print(Foo(0)) 

Ini juga berarti bahwa Anda tidak boleh membuat instance kelas yang sama di __new__ menggunakan konstruktor reguler ( Foo(...) ). Hal ini dapat menyebabkan eksekusi berulang __init__ , atau bahkan rekursi yang tak terbatas.


Rekursi tak terbatas:


 class Foo: def __new__(cls, x): return Foo(-x) # Recursion 

Eksekusi ganda __init__ :


 class Foo: def __new__(cls, x): if x < 0: return Foo(-x) return super().__new__(cls) def __init__(self, x): print(x) self._x = x 

Cara yang benar:


 class Foo: def __new__(cls, x): if x < 0: return cls.__new__(cls, -x) return super().__new__(cls) def __init__(self, x): print(x) self._x = x 

Operator [] dan irisan


Dengan Python, Anda bisa mengganti operator [] dengan mendefinisikan metode __getitem__ . Jadi, misalnya, Anda bisa membuat objek yang secara virtual berisi elemen berulang yang tak terhingga:


 class Cycle: def __init__(self, lst): self._lst = lst def __getitem__(self, index): return self._lst[ index % len(self._lst) ] print(Cycle(['a', 'b', 'c'])[100]) # 'b' 

Hal yang tidak biasa di sini adalah bahwa operator [] mendukung sintaks unik. Dengan menggunakannya Anda bisa mendapatkan tidak hanya [2] , tetapi juga [2:10] , [2:10:2] , [2::2] dan bahkan [:] . Semantik dari operator adalah: [mulai: berhenti: langkah], namun Anda dapat menggunakannya dengan cara lain untuk membuat objek khusus.


Tetapi jika Anda memanggil __getitem__ dengan sintaks ini, apa yang akan didapat sebagai parameter indeks? Inilah mengapa objek irisan ada.


 In : class Inspector: ...: def __getitem__(self, index): ...: print(index) ...: In : Inspector()[1] 1 In : Inspector()[1:2] slice(1, 2, None) In : Inspector()[1:2:3] slice(1, 2, 3) In : Inspector()[:] slice(None, None, None) 

Anda bahkan dapat menggabungkan sintaks tupel dan irisan:


 In : Inspector()[:, 0, :] (slice(None, None, None), 0, slice(None, None, None)) 

slice tidak melakukan apa-apa, hanya menyimpan atribut start , stop dan step .


 In : s = slice(1, 2, 3) In : s.start Out: 1 In : s.stop Out: 2 In : s.step Out: 3 

Mengganggu Asyncio Coroutine


Setiap asyncio dapat dieksekusi dapat dibatalkan menggunakan metode cancel() . Dalam hal ini, CanceledError akan dikirim ke coroutine, sebagai akibatnya, ini dan semua coroutine yang terkait dengannya akan terputus sampai kesalahan ditangkap dan ditekan.


CancelledError adalah subkelas Exception , yang berarti dapat ditangkap secara tidak sengaja menggunakan kombinasi try ... except Exception , yang dirancang untuk menangkap "kesalahan". Untuk mendapatkan bug dengan aman pada coroutine, Anda harus melakukan ini:


 try: await action() except asyncio.CancelledError: raise except Exception: logging.exception('action failed') 

Perencanaan eksekusi


Untuk merencanakan eksekusi beberapa kode pada waktu tertentu, asyncio biasanya membuat tugas yang mengeksekusi await asyncio.sleep(x) :


 import asyncio async def do(n=0): print(n) await asyncio.sleep(1) loop.create_task(do(n + 1)) loop.create_task(do(n + 1)) loop = asyncio.get_event_loop() loop.create_task(do()) loop.run_forever() 

Tetapi membuat tugas baru bisa mahal, dan Anda tidak harus melakukan ini jika Anda tidak berencana untuk melakukan operasi asinkron (seperti fungsi do dalam contoh saya). Sebagai gantinya, Anda dapat menggunakan fungsi loop.call_later dan loop.call_at , yang memungkinkan Anda menjadwalkan panggilan panggil balik asinkron:


 import asyncio def do(n=0): print(n) loop = asyncio.get_event_loop() loop.call_later(1, do, n+1) loop.call_later(1, do, n+1) loop = asyncio.get_event_loop() do() loop.run_forever() 

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


All Articles