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

gambar

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

Publikasi sebelumnya .

Membandingkan struktur


Terkadang Anda ingin membandingkan struktur yang rumit dalam pengujian dengan mengabaikan beberapa nilai. Biasanya, itu dapat dilakukan dengan membandingkan nilai-nilai tertentu dengan struktur:

>>> d = dict(a=1, b=2, c=3) >>> assert d['a'] == 1 >>> assert d['c'] == 3 

Namun, Anda dapat membuat nilai khusus yang laporannya setara dengan nilai lainnya:

 >>> assert d == dict(a=1, b=ANY, c=3) 

Itu dapat dengan mudah dilakukan dengan mendefinisikan metode __eq__ :

 >>> class AnyClass: ... def __eq__(self, another): ... return True ... >>> ANY = AnyClass() 

sys.stdout adalah pembungkus yang memungkinkan Anda untuk menulis string, bukan byte mentah. String dikodekan secara otomatis menggunakan sys.stdout.encoding :

 >>> _ = sys.stdout.write('Straße\n') Straße >>> sys.stdout.encoding 'UTF-8' 

sys.stdout.encoding adalah read-only dan sama dengan encoding default Python, yang dapat diubah dengan mengatur variabel lingkungan PYTHONIOENCODING :

 $ PYTHONIOENCODING=cp1251 python3 Python 3.6.6 (default, Aug 13 2018, 18:24:23) [GCC 4.8.5 20150623 (Red Hat 4.8.5-28)] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import sys >>> sys.stdout.encoding 'cp1251' 

Jika Anda ingin menulis byte ke stdout Anda dapat memotong encoding otomatis dengan mengakses buffer yang dibungkus dengan sys.stdout.buffer :

 >>> sys.stdout <_io.TextIOWrapper name='<stdout>' mode='w' encoding='cp1251'> >>> sys.stdout.buffer <_io.BufferedWriter name='<stdout>'> >>> _ = sys.stdout.buffer.write(b'Stra\xc3\x9fe\n') Straße 

sys.stdout.buffer juga merupakan pembungkus yang melakukan buffering untuk Anda. Itu dapat dilewati dengan mengakses file handler mentah dengan sys.stdout.buffer.raw :

 >>> _ = sys.stdout.buffer.raw.write(b'Stra\xc3\x9fe') Straße 

Ellipsis konstan


Python memiliki daftar konstanta bawaan yang sangat pendek. Salah satunya adalah Ellipsis yang juga bisa ditulis ... Konstanta ini tidak memiliki arti khusus untuk penerjemah tetapi digunakan di tempat-tempat di mana sintaksis seperti itu terlihat tepat.

numpy mendukung Ellipsis sebagai argumen __getitem__ , misal x[...] mengembalikan semua elemen x .

PEP 484 mendefinisikan arti tambahan: Callable[..., type] adalah cara untuk mendefinisikan jenis callable tanpa tipe argumen yang ditentukan.

Akhirnya, Anda dapat menggunakan ... untuk menunjukkan bahwa fungsi tersebut belum diimplementasikan. Ini adalah kode Python yang benar-benar valid:

 def x(): ... 

Namun, dalam Python 2 Ellipsis tidak dapat ditulis sebagai ... Satu-satunya pengecualian adalah a[...] yang berarti a[Ellpsis] .

Semua sintaks berikut ini berlaku untuk Python 3, tetapi hanya baris pertama yang valid untuk Python 2:

 a[...] a[...:2:...] [..., ...] {...:...} a = ... ... is ... def a(x=...): ... 

Modul mengimport ulang


Modul yang sudah diimpor tidak akan dimuat lagi. import foo tidak melakukan apa-apa. Namun, terbukti berguna untuk mengimpor kembali modul saat bekerja di lingkungan yang interaktif. Cara yang tepat untuk melakukan ini dalam Python 3.4+ adalah dengan menggunakan importlib :

 In [1]: import importlib In [2]: with open('foo.py', 'w') as f: ...: f.write('a = 1') ...: In [3]: import foo In [4]: foo.a Out[4]: 1 In [5]: with open('foo.py', 'w') as f: ...: f.write('a = 2') ...: In [6]: foo.a Out[6]: 1 In [7]: import foo In [8]: foo.a Out[8]: 1 In [9]: importlib.reload(foo) Out[9]: <module 'foo' from '/home/v.pushtaev/foo.py'> In [10]: foo.a Out[10]: 2 

ipython juga memiliki ekstensi autoreload yang secara otomatis autoreload kembali modul jika perlu:

 In [1]: %load_ext autoreload In [2]: %autoreload 2 In [3]: with open('foo.py', 'w') as f: ...: f.write('print("LOADED"); a=1') ...: In [4]: import foo LOADED In [5]: foo.a Out[5]: 1 In [6]: with open('foo.py', 'w') as f: ...: f.write('print("LOADED"); a=2') ...: In [7]: import foo LOADED In [8]: foo.a Out[8]: 2 In [9]: with open('foo.py', 'w') as f: ...: f.write('print("LOADED"); a=3') ...: In [10]: foo.a LOADED Out[10]: 3 

\ G


Dalam beberapa bahasa, Anda dapat menggunakan pernyataan \G Ini cocok pada posisi di mana pertandingan sebelumnya berakhir. Itu memungkinkan penulisan automata terbatas yang berjalan melalui kata demi kata string (di mana kata didefinisikan oleh regex).

Namun, tidak ada hal seperti itu di Python. Solusi yang tepat adalah melacak posisi secara manual dan meneruskan fungsi substring ke regex:

 import re import json text = '<a><b>foo</b><c>bar</c></a><z>bar</z>' regex = '^(?:<([az]+)>|</([az]+)>|([az]+))' stack = [] tree = [] pos = 0 while len(text) > pos: error = f'Error at {text[pos:]}' found = re.search(regex, text[pos:]) assert found, error pos += len(found[0]) start, stop, data = found.groups() if start: tree.append(dict( tag=start, children=[], )) stack.append(tree) tree = tree[-1]['children'] elif stop: tree = stack.pop() assert tree[-1]['tag'] == stop, error if not tree[-1]['children']: tree[-1].pop('children') elif data: stack[-1][-1]['data'] = data print(json.dumps(tree, indent=4)) 

Pada contoh sebelumnya, kita dapat menghemat waktu dengan menghindari mengiris string berulang kali tetapi meminta modul ulang untuk mencari mulai dari posisi yang berbeda.

Itu membutuhkan beberapa perubahan. Pertama, re.search tidak mendukung pencarian dari posisi khusus, jadi kami harus mengkompilasi ekspresi reguler secara manual. Kedua, ^ berarti awal sebenarnya untuk string, bukan posisi di mana pencarian dimulai, jadi kami harus memeriksa secara manual bahwa kecocokan terjadi pada posisi yang sama.

 import re import json text = '<a><b>foo</b><c>bar</c></a><z>bar</z>' * 10 def print_tree(tree): print(json.dumps(tree, indent=4)) def xml_to_tree_slow(text): regex = '^(?:<([az]+)>|</([az]+)>|([az]+))' stack = [] tree = [] pos = 0 while len(text) > pos: error = f'Error at {text[pos:]}' found = re.search(regex, text[pos:]) assert found, error pos += len(found[0]) start, stop, data = found.groups() if start: tree.append(dict( tag=start, children=[], )) stack.append(tree) tree = tree[-1]['children'] elif stop: tree = stack.pop() assert tree[-1]['tag'] == stop, error if not tree[-1]['children']: tree[-1].pop('children') elif data: stack[-1][-1]['data'] = data def xml_to_tree_slow(text): regex = '^(?:<([az]+)>|</([az]+)>|([az]+))' stack = [] tree = [] pos = 0 while len(text) > pos: error = f'Error at {text[pos:]}' found = re.search(regex, text[pos:]) assert found, error pos += len(found[0]) start, stop, data = found.groups() if start: tree.append(dict( tag=start, children=[], )) stack.append(tree) tree = tree[-1]['children'] elif stop: tree = stack.pop() assert tree[-1]['tag'] == stop, error if not tree[-1]['children']: tree[-1].pop('children') elif data: stack[-1][-1]['data'] = data return tree _regex = re.compile('(?:<([az]+)>|</([az]+)>|([az]+))') def _error_message(text, pos): return text[pos:] def xml_to_tree_fast(text): stack = [] tree = [] pos = 0 while len(text) > pos: error = f'Error at {text[pos:]}' found = _regex.search(text, pos=pos) begin, end = found.span(0) assert begin == pos, _error_message(text, pos) assert found, _error_message(text, pos) pos += len(found[0]) start, stop, data = found.groups() if start: tree.append(dict( tag=start, children=[], )) stack.append(tree) tree = tree[-1]['children'] elif stop: tree = stack.pop() assert tree[-1]['tag'] == stop, _error_message(text, pos) if not tree[-1]['children']: tree[-1].pop('children') elif data: stack[-1][-1]['data'] = data return tree print_tree(xml_to_tree_fast(text)) 

Hasil:

 In [1]: from example import * In [2]: %timeit xml_to_tree_slow(text) 356 µs ± 16.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) In [3]: %timeit xml_to_tree_fast(text) 294 µs ± 6.15 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) 

Fungsi bulat


Posting hari ini ditulis oleh orsinium , penulis @itgram_channel.

Fungsi putaran round angka ke presisi tertentu dalam digit desimal.

 >>> round(1.2) 1 >>> round(1.8) 2 >>> round(1.228, 1) 1.2 

Anda juga dapat mengatur presisi negatif:

 >>> round(413.77, -1) 410.0 >>> round(413.77, -2) 400.0 

nilai pengembalian round tipe nomor input:

 >>> type(round(2, 1)) <class 'int'> >>> type(round(2.0, 1)) <class 'float'> >>> type(round(Decimal(2), 1)) <class 'decimal.Decimal'> >>> type(round(Fraction(2), 1)) <class 'fractions.Fraction'> 

Untuk kelas Anda sendiri, Anda dapat mendefinisikan pemrosesan putaran dengan metode __round__ :

 >>> class Number(int): ... def __round__(self, p=-1000): ... return p ... >>> round(Number(2)) -1000 >>> round(Number(2), -2) -2 

Nilai dibulatkan ke kelipatan terdekat dari 10 ** (-precision) . Misalnya, untuk precision=1 nilai akan dibulatkan menjadi kelipatan 0,1: round(0.63, 1) mengembalikan 0.6 . Jika dua kelipatan sama-sama dekat, pembulatan dilakukan menuju pilihan genap:

 >>> round(0.5) 0 >>> round(1.5) 2 

Terkadang pembulatan mengapung bisa sedikit mengejutkan:

 >>> round(2.85, 1) 2.9 

Ini karena sebagian besar pecahan desimal tidak dapat direpresentasikan dengan tepat sebagai pelampung (https://docs.python.org/3.7/tutorial/floatingpoint.html):

 >>> format(2.85, '.64f') '2.8500000000000000888178419700125232338905334472656250000000000000' 

Jika Anda ingin membulatkan setengah Anda dapat menggunakan decimal.Decimal :

 >>> from decimal import Decimal, ROUND_HALF_UP >>> Decimal(1.5).quantize(0, ROUND_HALF_UP) Decimal('2') >>> Decimal(2.85).quantize(Decimal('1.0'), ROUND_HALF_UP) Decimal('2.9') >>> Decimal(2.84).quantize(Decimal('1.0'), ROUND_HALF_UP) Decimal('2.8') 

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


All Articles