@Pythonetc Agustus 2018



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 # prints 'right' print(Bottom().foo()) 

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.

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


All Articles