@Pythonetc kompilasi februari 2019


Ini adalah koleksi kesembilan tips Python dan pemrograman dari feed @pythonetc saya.

Pilihan sebelumnya .

Perbandingan Struktural


Kadang-kadang ketika menguji perlu untuk membandingkan struktur yang kompleks, mengabaikan beberapa nilai. Ini biasanya dapat dilakukan dengan membandingkan nilai-nilai spesifik dari struktur seperti itu:

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

Namun, Anda dapat membuat nilai khusus yang sama dengan yang lain:

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

Ini mudah dilakukan dengan menggunakan metode ajaib __eq__ :

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

stdout


sys.stdout adalah pembungkus yang memungkinkan Anda untuk menulis nilai string, bukan byte. Nilai-nilai string ini secara otomatis dikodekan menggunakan sys.stdout.encoding :

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

read-only dan sama dengan encoding default, yang dapat dikonfigurasi menggunakan PYTHONIOENCODING 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 melewati penyandian otomatis dengan menggunakan sys.stdout.buffer buffer yang ditempatkan di wrapper:

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

juga bungkusnya. Itu dapat dilewati dengan menghubungi deskriptor file dengan sys.stdout.buffer.raw :

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

Elipsis konstan


Python memiliki sedikit konstanta bawaan. Salah satunya, Ellipsis , juga bisa ditulis ... Untuk penerjemah, konstanta ini tidak memiliki nilai spesifik, tetapi digunakan jika sintaksinya sesuai.

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

PEP 484 menetapkan nilai lain untuk konstanta ini: Callable[..., type] memungkinkan Anda untuk menentukan tipe-tipe yang dipanggil tanpa menentukan tipe argumen.

Akhirnya, Anda dapat menggunakan ... untuk menunjukkan bahwa suatu fungsi belum diimplementasikan. Ini adalah kode Python yang sepenuhnya benar:

 def x(): ... 

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

Sintaks ini benar untuk Python 3, tetapi hanya baris pertama yang benar untuk Python 2:

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

Impor kembali modul


Modul yang sudah diimpor tidak akan memuat lagi. Perintah import foo tidak melakukan apa-apa. Namun, ini berguna untuk menerapkan kembali modul ketika bekerja di lingkungan yang interaktif. Dalam Python 3.4+, importlib harus digunakan untuk ini:

 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 

Ada juga ekstensi autoreload untuk ipython , yang secara otomatis akan memasukkan 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 ekspresi \G Ia mencari kecocokan dari posisi di mana pencarian sebelumnya berakhir. Ini memungkinkan kita untuk menulis mesin keadaan terbatas yang memproses nilai string kata demi kata (kata ditentukan oleh ekspresi reguler).

Dalam Python, tidak ada yang seperti ekspresi ini, dan Anda dapat menerapkan fungsi serupa dengan secara manual melacak posisi dan meneruskan bagian dari string ke fungsi ekspresi reguler:

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

Dalam contoh di atas, Anda dapat menghemat waktu pemrosesan tanpa memutus garis berulang-ulang, dan minta modul re untuk mulai mencari dari posisi lain.

Untuk melakukan ini, Anda perlu membuat beberapa perubahan pada kode. Pertama, re.search tidak mendukung penentuan posisi awal pencarian, jadi Anda harus mengkompilasi ekspresi reguler secara manual. Kedua, ^ menunjukkan awal dari nilai string, bukan posisi awal pencarian, jadi Anda perlu memeriksa secara manual bahwa kecocokan ditemukan di 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) 

Angka pembulatan


Item ini ditulis oleh orsinium , penulis saluran Telegram @itgram_channel.

Fungsi putaran round angka ke jumlah tempat desimal yang ditentukan.

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

Anda juga dapat mengatur akurasi pembulatan negatif:

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

mengembalikan nilai dari jenis yang sama dengan 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 round menggunakan metode __round__ :

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

Di sini nilai dibulatkan ke kelipatan terdekat dari 10 ** (-precision) . Misalnya, dengan precision=1 nilai akan dibulatkan ke kelipatan 0,1: round(0.63, 1) mengembalikan 0.6 . Jika dua bilangan ganda sama-sama dekat, maka pembulatan dilakukan ke bilangan genap:

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

Terkadang membulatkan angka floating point dapat memberikan hasil yang tidak terduga:

 >>> round(2.85, 1) 2.9 

Faktanya adalah bahwa sebagian besar pecahan desimal tidak dapat diekspresikan secara akurat menggunakan angka floating point ( https://docs.python.org/3.7/tutorial/floatingpoint.html ):

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

Jika Anda ingin decimal.Decimal dua, gunakan 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/id444226/


All Articles