Tipps und Tricks von meinem Telegramm-Kanal @pythonetc, Februar 2019

Bild

Es ist eine neue Auswahl an Tipps und Tricks zu Python und Programmierung von meinem Telegramm-Kanal @pythonetc.

Frühere Veröffentlichungen .

Strukturen vergleichen


Manchmal möchten Sie komplexe Strukturen in Tests vergleichen und dabei einige Werte ignorieren. Normalerweise kann dies durch Vergleichen bestimmter Werte mit der Struktur erfolgen:

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

Sie können jedoch einen speziellen Wert erstellen, dessen Berichte jedem anderen Wert entsprechen:

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

__eq__ kann einfach durch Definieren der __eq__ -Methode erreicht werden:

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

sys.stdout ist ein Wrapper, mit dem Sie Zeichenfolgen anstelle von sys.stdout schreiben können. Die Zeichenfolge wird automatisch mit sys.stdout.encoding codiert:

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

sys.stdout.encoding ist schreibgeschützt und entspricht der Python-Standardcodierung, die durch Festlegen der Umgebungsvariablen PYTHONIOENCODING geändert werden kann:

 $ 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' 

Wenn Sie Bytes in stdout schreiben möchten, können Sie die automatische Codierung umgehen, indem Sie mit sys.stdout.buffer auf den sys.stdout.buffer Puffer 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 ist auch ein Wrapper, der für Sie sys.stdout.buffer . Sie kann umgangen werden, indem Sie mit sys.stdout.buffer.raw auf den Rohdatei-Handler sys.stdout.buffer.raw :

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

Ellipsenkonstante


Python hat eine sehr kurze Liste von eingebauten Konstanten. Eine davon ist Ellipsis die auch geschrieben werden kann als ... Diese Konstante hat für den Interpreter keine besondere Bedeutung, wird jedoch an Stellen verwendet, an denen eine solche Syntax angemessen erscheint.

numpy unterstützt Ellipsis als __getitem__ Argument, zB x[...] gibt alle Elemente von x .

PEP 484 definiert zusätzliche Bedeutung: Callable[..., type] ist eine Möglichkeit, einen Typ von Callables ohne Angabe von Argumenttypen zu definieren.

Schließlich können Sie mit ... angeben, dass die Funktion noch nicht implementiert ist. Dies ist ein vollständig gültiger Python-Code:

 def x(): ... 

In Python 2 kann Ellipsis jedoch nicht als ... geschrieben werden ... Die einzige Ausnahme ist a[...] , die a[Ellpsis] .

Alle folgenden Syntaxen gelten für Python 3, aber nur die erste Zeile gilt für Python 2:

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

Module werden erneut importiert


Bereits importierte Module werden nicht erneut geladen. import foo einfach nichts. Es hat sich jedoch als nützlich erwiesen, Module während der Arbeit in einer interaktiven Umgebung erneut zu importieren. Der richtige Weg, dies in Python 3.4+ zu tun, ist die Verwendung von 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 hat auch die autoreload Erweiterung, die Module bei Bedarf automatisch neu autoreload :

 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.


In einigen Sprachen können Sie die \G Zusicherung verwenden. Es wird an der Position abgeglichen, an der das vorherige Spiel beendet wurde. Dies ermöglicht das Schreiben endlicher Automaten, die Wort für Wort durch die Zeichenfolge gehen (wobei das Wort durch den regulären Ausdruck definiert wird).

In Python gibt es so etwas jedoch nicht. Die richtige Problemumgehung besteht darin, die Position manuell zu verfolgen und den Teilstring an Regex-Funktionen zu übergeben:

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

Im vorherigen Beispiel können wir Zeit sparen, indem wir vermeiden, die Zeichenfolge immer wieder zu schneiden, sondern das Modul re bitten, stattdessen von einer anderen Position aus zu suchen.

Das erfordert einige Änderungen. Erstens re.search re.search die Suche von einer benutzerdefinierten Position aus nicht, daher müssen wir den regulären Ausdruck manuell kompilieren. Zweitens bedeutet ^ den tatsächlichen Start der Zeichenfolge und nicht die Position, an der die Suche gestartet wurde. Daher müssen wir manuell überprüfen, ob die Übereinstimmung an derselben Position stattgefunden hat.

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

Ergebnis:

 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) 

Runde Funktion


Der heutige Beitrag wurde von Orsinium , dem Autor von @itgram_channel, verfasst.

Die Rundungsfunktion rundet eine Zahl mit einer bestimmten Genauigkeit in Dezimalstellen.

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

Sie können auch eine negative Genauigkeit einstellen:

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

round gibt den Wert des Typs der Eingabenummer zurück:

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

Für Ihre eigenen Klassen können Sie die runde Verarbeitung mit der Methode __round__ definieren:

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

Die Werte werden auf das nächste Vielfache von 10 ** (-precision) gerundet. Beispiel: Für precision=1 Wert auf ein Vielfaches von 0,1 gerundet: round(0.63, 1) gibt 0.6 . Wenn zwei Vielfache gleich nahe beieinander liegen, wird auf die gerade Wahl gerundet:

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

Manchmal kann das Runden von Schwimmern etwas überraschend sein:

 >>> round(2.85, 1) 2.9 

Dies liegt daran, dass die meisten Dezimalbrüche nicht genau als Gleitkomma dargestellt werden können (https://docs.python.org/3.7/tutorial/floatingpoint.html):

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

Wenn Sie die Hälfte decimal.Decimal möchten, können Sie 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/de444228/


All Articles