Ini adalah koleksi ketiga tips dan pemrograman Python dari
umpan @pythonetc saya.
Pilihan sebelumnya:
Metode pabrik
Jika Anda membuat objek baru di dalam
__init__
, maka akan lebih bijaksana untuk meneruskannya sebagai argumen, dan menggunakan metode pabrik untuk membuat objek. Ini akan memisahkan logika bisnis dari implementasi teknis membuat objek.
Dalam contoh ini,
__init__
menerima
host
dan
port
sebagai argumen untuk membuat koneksi database:
class Query: def __init__(self, host, port): self._connection = Connection(host, port)
Opsi refactoring:
class Query: def __init__(self, connection): self._connection = connection @classmethod def create(cls, host, port): return cls(Connection(host, port))
Pendekatan ini setidaknya memiliki keuntungan sebagai berikut:
- Penempatan itu mudah. Dalam tes, Anda dapat melakukan
Query(FakeConnection())
. - Kelas dapat memiliki metode pabrik sebanyak yang Anda inginkan. Anda dapat membuat koneksi tidak hanya menggunakan
host
dan port
, tetapi juga mengkloning koneksi lain, membaca file konfigurasi, menggunakan koneksi default, dll. - Metode pabrik serupa dapat diubah menjadi fungsi asinkron, yang sama sekali tidak mungkin dilakukan dengan
__init__
.
Super atau selanjutnya
Fungsi
super()
memungkinkan Anda untuk referensi kelas dasar. Ini bisa sangat berguna dalam kasus-kasus di mana kelas turunan ingin menambahkan sesuatu ke implementasi metode, daripada menimpanya sepenuhnya.
class BaseTestCase(TestCase): def setUp(self): self._db = create_db() class UserTestCase(BaseTestCase): def setUp(self): super().setUp() self._user = create_user()
Nama super tidak berarti apa-apa "super". Dalam konteks ini, ini berarti "lebih tinggi dalam hierarki" (misalnya, seperti pada kata "pengawas"). Pada saat yang sama,
super()
tidak selalu merujuk ke kelas dasar, ia dapat dengan mudah mengembalikan kelas anak. Jadi akan lebih tepat untuk menggunakan nama
next()
, karena kelas berikutnya dikembalikan sesuai dengan MRO.
class Top: def foo(self): return 'top' class Left(Top): def foo(self): return super().foo() class Right(Top): def foo(self): return 'right' class Bottom(Left, Right): pass
Ingat bahwa
super()
dapat menghasilkan hasil yang berbeda tergantung dari mana metode itu berasal.
>>> Bottom().foo() 'right' >>> Left().foo() 'top'
Ruang nama khusus untuk membuat kelas
Kelas dibuat dalam dua langkah besar. Pertama, tubuh kelas dieksekusi, seperti tubuh fungsi. Pada langkah kedua, namespace yang dihasilkan (yang dikembalikan oleh
locals()
) digunakan oleh metaclass (defaultnya adalah
type
) untuk membuat objek kelas.
class Meta(type): def __new__(meta, name, bases, ns): print(ns) return super().__new__( meta, name, bases, ns ) class Foo(metaclass=Meta): B = 2
Kode ini menampilkan
{'__module__': '__main__', '__qualname__':'Foo', 'B': 3}
.
Jelas, jika Anda memperkenalkan sesuatu seperti
B = 2; B = 3
B = 2; B = 3
, maka metaclass hanya akan melihat
B = 3
, karena hanya nilai ini dalam
ns
. Keterbatasan ini berasal dari kenyataan bahwa metaclass mulai bekerja hanya setelah eksekusi tubuh.
Namun, Anda dapat melakukan intervensi dalam prosedur eksekusi dengan menyelipkan namespace Anda sendiri. Kamus sederhana digunakan secara default, tetapi Anda dapat memberikan objek Anda sendiri, mirip dengan kamus, jika Anda menggunakan metode
__prepare__
dari metaclass.
class CustomNamespace(dict): def __setitem__(self, key, value): print(f'{key} -> {value}') return super().__setitem__(key, value) class Meta(type): def __new__(meta, name, bases, ns): return super().__new__( meta, name, bases, ns ) @classmethod def __prepare__(metacls, cls, bases): return CustomNamespace() class Foo(metaclass=Meta): B = 2 B = 3
Hasil eksekusi kode:
__module__ -> __main__ __qualname__ -> Foo B -> 2 B -> 3
Dengan demikian
enum.Enum
dilindungi dari
duplikasi .
matplotlib
matplotlib
adalah pustaka Python yang kompleks dan fleksibel untuk membuat grafik. Banyak produk mendukungnya, termasuk Jupyter dan Pycharm. Berikut adalah contoh rendering fraktal sederhana menggunakan
matplotlib
:
https://repl.it/@VadimPushtaev/myplotlib (lihat gambar judul publikasi ini).
Dukungan Zona Waktu
Python menyediakan pustaka
datetime
kuat untuk bekerja dengan tanggal dan waktu. Sangat mengherankan bahwa objek
datetime
memiliki antarmuka khusus untuk mendukung zona waktu (yaitu, atribut
tzinfo
), tetapi modul ini memiliki dukungan terbatas untuk antarmuka yang disebutkan, sehingga bagian dari pekerjaan ditugaskan ke modul lain.
Yang paling populer di antara mereka adalah
pytz
. Tetapi kenyataannya adalah bahwa
pytz
tidak sepenuhnya sesuai dengan antarmuka
tzinfo
. Ini dinyatakan di awal dokumentasi
pytz
: "Perpustakaan ini berbeda dari API Python yang didokumentasikan untuk implementasi tzinfo."
Anda tidak dapat menggunakan objek
pytz
tzinfo
sebagai
tzinfo
. Jika Anda mencoba melakukan ini, maka Anda berisiko mendapatkan hasil yang benar-benar gila:
In : paris = pytz.timezone('Europe/Paris') In : str(datetime(2017, 1, 1, tzinfo=paris)) Out: '2017-01-01 00:00:00+00:09'
Perhatikan offset +00: 09. Pytz harus digunakan seperti ini:
In : str(paris.localize(datetime(2017, 1, 1))) Out: '2017-01-01 00:00:00+01:00'
Selain itu, setelah operasi aritmatika, Anda perlu menerapkan
normalize
ke objek
datetime
Anda untuk menghindari penggantian offset (misalnya, di perbatasan periode DST).
In : new_time = time + timedelta(days=2) In : str(new_time) Out: '2018-03-27 00:00:00+01:00' In : str(paris.normalize(new_time)) Out: '2018-03-27 01:00:00+02:00'
Jika Anda memiliki Python 3.6, dokumentasi merekomendasikan menggunakan
dateutil.tz
daripada
pytz
. Pustaka ini sepenuhnya kompatibel dengan
tzinfo
, ini dapat diteruskan sebagai atribut dan Anda tidak perlu menggunakan
normalize
. Benar, ini bekerja lebih lambat.
Jika Anda ingin tahu mengapa
pytz
tidak mendukung API
datetime
, atau ingin melihat lebih banyak contoh, baca artikel
ini .
StopIteration Ajaib
Setiap kali
next(x)
dipanggil, ia mengembalikan nilai baru dari iterator
x
sampai sebuah pengecualian dilemparkan. Jika ternyata
StopIteration
, maka iterator habis dan tidak bisa lagi memberikan nilai. Jika generator diulangi, maka di ujung bodi akan secara otomatis membuang
StopIteration
:
>>> def one_two(): ... yield 1 ... yield 2 ... >>> i = one_two() >>> next(i) 1 >>> next(i) 2 >>> next(i) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
StopIteration
dapat secara otomatis ditangani oleh alat yang memanggil
next
:
>>> list(one_two()) [1, 2]
Tetapi masalahnya adalah bahwa StopIteration yang jelas tidak diharapkan yang muncul di tubuh generator akan diam-diam diambil sebagai tanda akhir generator, dan bukan sebagai kesalahan, seperti pengecualian lainnya:
def one_two(): yield 1 yield 2 def one_two_repeat(n): for _ in range(n): i = one_two() yield next(i) yield next(i) yield next(i) print(list(one_two_repeat(3)))
Di sini,
yield
terakhir adalah kesalahan: pengecualian
StopIteration
dilemparkan menghentikan iterasi
list(...)
. Kami mendapatkan hasilnya
[1, 2]
. Namun, dalam Python 3.7 perilaku ini telah berubah. Alien
StopIteration
diganti dengan
RuntimeError
:
Traceback (most recent call last): File "test.py", line 10, in one_two_repeat yield next(i) StopIteration The above exception was the direct cause of the following exception: Traceback (most recent call last): File "test.py", line 12, in <module> print(list(one_two_repeat(3))) RuntimeError: generator raised StopIteration
Anda dapat menggunakan
__future__ import generator_stop
untuk mengaktifkan perilaku yang sama sejak Python 3.5.