إنها مجموعة جديدة من النصائح والحيل حول Python والبرمجة من قناة Telegram-channelpythonetc.
المنشورات السابقة .
هياكل مقارنة
في بعض الأحيان تريد مقارنة الهياكل المعقدة في الاختبارات التي تتجاهل بعض القيم. عادة ، يمكن القيام بذلك عن طريق مقارنة قيم معينة مع الهيكل:
>>> d = dict(a=1, b=2, c=3) >>> assert d['a'] == 1 >>> assert d['c'] == 3
ومع ذلك ، يمكنك إنشاء قيمة خاصة تساوي التقارير أي قيمة أخرى:
>>> assert d == dict(a=1, b=ANY, c=3)
يمكن القيام بذلك بسهولة عن طريق تحديد طريقة
__eq__
:
>>> class AnyClass: ... def __eq__(self, another): ... return True ... >>> ANY = AnyClass()
sys.stdout
عبارة عن مجمّع يسمح لك بكتابة سلاسل بدلاً من البايتات الخام. يتم تشفير السلسلة تلقائيًا باستخدام
sys.stdout.encoding
:
>>> _ = sys.stdout.write('Straße\n') Straße >>> sys.stdout.encoding 'UTF-8'
sys.stdout.encoding
للقراءة فقط ويساوي ترميز Python الافتراضي ، والذي يمكن تغييره عن طريق تعيين متغير البيئة
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'
إذا كنت ترغب في كتابة وحدات البايت إلى
stdout
فيمكنك تجاوز الترميز التلقائي عن طريق الوصول إلى المخزن المؤقت المُلف مع
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
عبارة عن غلاف يقوم
sys.stdout.buffer
المؤقت لك. يمكن تجاوزه عن طريق الوصول إلى معالج الملفات الخام باستخدام
sys.stdout.buffer.raw
:
>>> _ = sys.stdout.buffer.raw.write(b'Stra\xc3\x9fe') Straße
القطع الناقص الثابت
بيثون لديها قائمة قصيرة جدا من الثوابت المضمنة. واحد منهم هو
Ellipsis
والذي يمكن كتابته أيضًا كـ
...
هذا الثابت ليس له معنى خاص للمترجم الفوري ولكنه يستخدم في الأماكن التي تبدو فيها صيغة الجملة هذه مناسبة.
دعم
numpy
Ellipsis
كوسيطة
__getitem__
، على سبيل المثال
x[...]
تُرجع جميع عناصر
x
.
يعرّف PEP 484 معنى إضافي:
Callable[..., type]
هي طريقة لتحديد نوع من callables مع عدم تحديد أنواع الوسائط.
أخيرًا ، يمكنك استخدام
...
للإشارة إلى أن الوظيفة لم يتم تنفيذها بعد. هذا رمز Python صالح تمامًا:
def x(): ...
ومع ذلك ، في Python 2
Ellipsis
لا يمكن كتابته كـ
...
الاستثناء الوحيد هو
a[...]
وهذا يعني
a[Ellpsis]
.
جميع بناء الجملة التالي صالحة لـ Python 3 ، لكن السطر الأول فقط صالح لـ Python 2:
a[...] a[...:2:...] [..., ...] {...:...} a = ... ... is ... def a(x=...): ...
وحدات إعادة الاستيراد
لن يتم تحميل الوحدات النمطية المستوردة بالفعل مرة أخرى.
import foo
لا يفعل شيئا. ومع ذلك ، فقد ثبت أنه من المفيد إعادة استيراد الوحدات النمطية أثناء العمل في بيئة تفاعلية. الطريقة الصحيحة للقيام بذلك في Python 3.4+ هي استخدام
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
أيضًا على ملحق
autoreload
التلقائي الذي
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
تتطابق في الموضع حيث انتهت المباراة السابقة. يسمح لك هذا بكتابة أوتوماتية محدودة تنتقل عبر سلسلة الكلمات كلمة (حيث يتم تعريف الكلمة بواسطة regex).
ومع ذلك ، لا يوجد شيء من هذا القبيل في بيثون. الحل الصحيح هو تتبع الموضع يدويًا وتمرير السلسلة الفرعية إلى وظائف regex:
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))
في المثال السابق ، يمكننا توفير بعض الوقت عن طريق تجنب تشريح السلسلة مرارًا وتكرارًا ولكن نطلب من وحدة إعادة البحث البحث بدءًا من موضع مختلف بدلاً من ذلك.
وهذا يتطلب بعض التغييرات. أولاً ، لا تدعم إعادة البحث البحث من موضع مخصص ، لذلك يتعين علينا ترجمة التعبير العادي يدويًا. ثانيًا ،
^
تعني البداية الحقيقية للسلسلة ، وليس الموضع الذي بدأ فيه البحث ، لذلك يتعين علينا التحقق يدويًا من أن المطابقة قد حدثت في نفس الموضع.
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))
النتيجة:
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)
وظيفة مستديرة
تم نشر منشور اليوم بواسطة orsinium ، مؤلفitgram_channel.round
الدالة
round
رقمًا إلى دقة معينة بالأرقام العشرية.
>>> round(1.2) 1 >>> round(1.8) 2 >>> round(1.228, 1) 1.2
كما يمكنك إعداد الدقة السلبية:
>>> round(413.77, -1) 410.0 >>> round(413.77, -2) 400.0
قيمة الإرجاع
round
لنوع رقم الإدخال:
>>> 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'>
بالنسبة للفئات الخاصة بك ، يمكنك تحديد المعالجة المستديرة باستخدام طريقة
__round__
:
>>> class Number(int): ... def __round__(self, p=-1000): ... return p ... >>> round(Number(2)) -1000 >>> round(Number(2), -2) -2
يتم تقريب القيم إلى أقرب مضاعف من
10 ** (-precision)
. على سبيل المثال ،
precision=1
سيتم تقريب القيمة إلى مضاعفات 0.1: ترجع
round(0.63, 1)
0.6
. إذا كان المضاعفان متقاربان ، فسيتم التقريب باتجاه الخيار المتساوي:
>>> round(0.5) 0 >>> round(1.5) 2
في بعض الأحيان قد يكون تقريب العوامات مفاجئًا:
>>> round(2.85, 1) 2.9
وذلك لأن معظم الكسور العشرية لا يمكن تمثيلها تمامًا كتعويم (https://docs.python.org/3.7/tutorial/floatingpoint.html):
>>> format(2.85, '.64f') '2.8500000000000000888178419700125232338905334472656250000000000000'
إذا كنت تريد
decimal.Decimal
إلى النصف ، فيمكنك استخدام
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')