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