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