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