نصائح وحيل من my Telegram-channelpythonetc ، فبراير 2019

صورة

إنها مجموعة جديدة من النصائح والحيل حول 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') 

Source: https://habr.com/ru/post/ar444228/


All Articles