@ Pythonetc Compilation Februar 2019


Dies ist die neunte Auswahl an Python-Tipps und -Programmierungen aus meinem @ pythonetc-Feed.

Vorherige Auswahl .

Strukturvergleich


Manchmal ist es beim Testen erforderlich, komplexe Strukturen zu vergleichen und einige Werte zu ignorieren. Dies kann normalerweise durch Vergleichen bestimmter Werte aus einer solchen 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, der jedem anderen Wert entspricht:

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

Dies ist mit der magischen Methode __eq__ einfach zu __eq__ :

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

stdout


sys.stdout ist ein Wrapper, mit dem Sie Zeichenfolgenwerte und keine Bytes schreiben können. Diese Zeichenfolgenwerte werden automatisch mit sys.stdout.encoding codiert:

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

schreibgeschützt und gleich der Standardcodierung, die mit der Umgebungsvariablen PYTHONIOENCODING konfiguriert werden 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' 

Wenn Sie Bytes in stdout schreiben möchten, können Sie die automatische Codierung überspringen, indem Sie die sys.stdout.buffer im Wrapper platzierten Puffers verwenden:

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

auch eine Hülle. Sie kann umgangen werden, indem Sie den Dateideskriptor mit sys.stdout.buffer.raw :

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

Konstante Auslassungspunkte


Python hat nur sehr wenige integrierte Konstanten. Eine davon, Ellipsis , kann auch geschrieben werden als ... Für den Interpreter hat diese Konstante keinen bestimmten Wert, wird jedoch verwendet, wenn eine solche Syntax angemessen ist.

numpy unterstützt Ellipsis als Argument für __getitem__ . Beispielsweise gibt x[...] alle Elemente von x .

PEP 484 definiert einen weiteren Wert für diese Konstante: Mit Callable[..., type] können Sie die Typen des aufgerufenen Callable[..., type] bestimmen, ohne die Argumenttypen anzugeben.

Schließlich können Sie mit ... angeben, dass eine Funktion noch nicht implementiert wurde. Dies ist völlig korrekter Python-Code:

 def x(): ... 

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

Diese Syntax ist für Python 3 korrekt, aber nur die erste Zeile ist für Python 2 korrekt:

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

Module erneut importieren


Bereits importierte Module werden nicht erneut geladen. Der Befehl import foo einfach nichts. Es ist jedoch nützlich, um Module erneut zu importieren, wenn Sie in einer interaktiven Umgebung arbeiten. In Python 3.4+ muss hierfür importlib verwendet werden:

 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 

Es ipython auch eine autoreload Erweiterung für ipython , die Module bei Bedarf automatisch neu ipython :

 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 den Ausdruck \G verwenden. Es sucht nach einer Übereinstimmung von der Position, an der die vorherige Suche beendet wurde. Dies ermöglicht es uns, endliche Zustandsmaschinen zu schreiben, die Zeichenfolgenwerte Wort für Wort verarbeiten (das Wort wird durch einen regulären Ausdruck bestimmt).

In Python gibt es nichts Vergleichbares zu diesem Ausdruck, und Sie können ähnliche Funktionen implementieren, indem Sie die Position manuell verfolgen und einen Teil der Zeichenfolge an Funktionen für reguläre Ausdrücke ü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 obigen Beispiel können Sie Zeit bei der Verarbeitung sparen, ohne die Zeile immer re unterbrechen, und das re Modul bitten, von einer anderen Position aus zu suchen.

Dazu müssen Sie einige Änderungen am Code vornehmen. Erstens unterstützt re.search nicht das Bestimmen der Position des Beginns der Suche, sodass Sie den regulären Ausdruck manuell kompilieren müssen. Zweitens bezeichnet ^ den Anfang des Zeichenfolgenwerts und nicht die Position des Suchbeginns. Sie müssen also manuell überprüfen, ob die Übereinstimmung an derselben Position gefunden wurde.

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

Ergebnisse:

 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) 

Zahlen runden


Dieser Artikel wurde von orsinium , Autor des Telegrammkanals @itgram_channel, geschrieben.

Die Rundungsfunktion rundet eine Zahl auf die angegebene Anzahl von Dezimalstellen.

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

Sie können auch eine negative Rundungsgenauigkeit einstellen:

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

Gibt einen Wert des gleichen Typs wie die 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 round 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 

Hier werden die Werte auf das nächste Vielfache von 10 ** (-precision) gerundet. Mit einer precision=1 Wert beispielsweise auf ein Vielfaches von 0,1 gerundet: round(0.63, 1) gibt 0.6 . Wenn zwei Mehrfachzahlen gleich nahe beieinander liegen, wird auf eine gerade Zahl gerundet:

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

Manchmal kann das Runden einer Gleitkommazahl zu einem unerwarteten Ergebnis führen:

 >>> round(2.85, 1) 2.9 

Tatsache ist, dass die meisten Dezimalbrüche mit einer Gleitkommazahl ( https://docs.python.org/3.7/tutorial/floatingpoint.html ) nicht genau ausgedrückt werden können:

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

Wenn Sie Hälften decimal.Decimal möchten, verwenden 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/de444226/


All Articles