Pada awalnya adalah
artikel ini . Kemudian sebuah
komentar muncul
padanya . Sebagai hasilnya, saya mempelajari bahan, menggali debag dan dapat mengoptimalkan kode dari bagian pertama cerita ini. Saya mengusulkan untuk berjalan bersama saya di sepanjang poin utama.
Pertama, saya ingin mengucapkan terima kasih kepada
Mogost . Berkat komentarnya, saya mendefinisikan ulang pendekatan ke Python. Saya sebelumnya pernah mendengar bahwa ada banyak orang yang tidak ekonomis di antara para pythonis (ketika berhadapan dengan ingatan), tetapi sekarang ternyata saya entah bagaimana bergabung dengan pesta ini.
Jadi mari kita mulai. Mari berspekulasi, dan apa hambatannya secara umum.
Gigih jika:
if isinstance(self.custom_handlers, property): if self.custom_handlers and e.__class__ in self.custom_handlers: if e.__class__ not in self.exclude:
dan ini bukan batasnya. Oleh karena itu, saya menghapus sebagian dari ifs, mentransfer sesuatu ke __init__, mis. ke tempat itu akan dipanggil sekali. Secara khusus, memeriksa properti dalam kode harus dipanggil sekali, karena dekorator diterapkan pada metode dan ditugaskan untuk itu. Dan properti kelas, masing-masing, akan tetap tidak berubah. Karena itu, tidak perlu memeriksa properti secara konstan.
Titik terpisah adalah jika dalam. Profiler menunjukkan bahwa setiap in memiliki panggilan yang berbeda, jadi saya memutuskan untuk menggabungkan semua penangan menjadi satu dict. Ini diizinkan untuk menghindari jika pada umumnya, alih-alih hanya menggunakan:
self.handlers.get(e.__class__, Exception)(e)
jadi di self.handlers kita memiliki dict, yang secara default berisi fungsi yang memunculkan pengecualian
lainnya .
Tentu saja, bungkusnya perlu mendapat perhatian khusus. Ini adalah fungsi yang sama yang disebut setiap kali dekorator dipanggil. Yaitu di sini lebih baik untuk menghindari cek yang tidak perlu dan segala macam beban semaksimal mungkin, jika memungkinkan, meletakkannya di __init__ atau di __call__. Inilah pembungkus sebelumnya:
def wrapper(self, *args, **kwargs): if self.custom_handlers: if isinstance(self.custom_handlers, property): self.custom_handlers = self.custom_handlers.__get__(self, self.__class__) if asyncio.iscoroutinefunction(self.func): return self._coroutine_exception_handler(*args, **kwargs) else: return self._sync_exception_handler(*args, **kwargs)
jumlah cek melewati atap. Ini semua akan dipanggil pada setiap panggilan ke dekorator. Karenanya bungkus menjadi seperti ini:
def __call__(self, func): self.func = func if iscoroutinefunction(self.func): def wrapper(*args, **kwargs): return self._coroutine_exception_handler(*args, **kwargs) else: def wrapper(*args, **kwargs): return self._sync_exception_handler(*args, **kwargs) return wrapper
ingat, __call__ akan dipanggil sekali. Di dalam __call__, tergantung pada derajat asinkroninya fungsi, kami mengembalikan fungsi itu sendiri atau coroutine. Dan saya juga ingin mencatat bahwa fungsi asyncio.iscoroutine membuat panggilan tambahan, jadi saya beralih ke inspect.iscoroutinefunction. Sebenarnya, bangku (cProfile) untuk asyncio dan periksa:
ncalls tottime percall cumtime percall filename:lineno(function) 1 0.000 0.000 0.000 0.000 <string>:1(<module>) 1 0.000 0.000 0.000 0.000 coroutines.py:160(iscoroutinefunction) 1 0.000 0.000 0.000 0.000 inspect.py:158(isfunction) 1 0.000 0.000 0.000 0.000 inspect.py:179(iscoroutinefunction) 1 0.000 0.000 0.000 0.000 {built-in method builtins.exec} 1 0.000 0.000 0.000 0.000 {built-in method builtins.isinstance} 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
ncalls tottime percall cumtime percall filename:lineno(function) 1 0.000 0.000 0.000 0.000 <string>:1(<module>) 1 0.000 0.000 0.000 0.000 inspect.py:158(isfunction) 1 0.000 0.000 0.000 0.000 inspect.py:179(iscoroutinefunction) 1 0.000 0.000 0.000 0.000 {built-in method builtins.exec} 1 0.000 0.000 0.000 0.000 {built-in method builtins.isinstance} 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
Kode lengkap:
from inspect import iscoroutinefunction from asyncio import QueueEmpty, QueueFull from concurrent.futures import TimeoutError class ProcessException(object): __slots__ = ('func', 'handlers') def __init__(self, custom_handlers=None): self.func = None if isinstance(custom_handlers, property): custom_handlers = custom_handlers.__get__(self, self.__class__) def raise_exception(e: Exception): raise e exclude = { QueueEmpty: lambda e: None, QueueFull: lambda e: None, TimeoutError: lambda e: None } self.handlers = { **exclude, **(custom_handlers or {}), Exception: raise_exception } def __call__(self, func): self.func = func if iscoroutinefunction(self.func): def wrapper(*args, **kwargs): return self._coroutine_exception_handler(*args, **kwargs) else: def wrapper(*args, **kwargs): return self._sync_exception_handler(*args, **kwargs) return wrapper async def _coroutine_exception_handler(self, *args, **kwargs): try: return await self.func(*args, **kwargs) except Exception as e: return self.handlers.get(e.__class__, Exception)(e) def _sync_exception_handler(self, *args, **kwargs): try: return self.func(*args, **kwargs) except Exception as e: return self.handlers.get(e.__class__, Exception)(e)
Dan mungkin contohnya tidak lengkap tanpa batas waktu. Oleh karena itu, gunakan contoh dari komentar di atas:
class MathWithTry(object): def divide(self, a, b): try: return a // b except ZeroDivisionError: return ' , '
dan sebuah contoh dari teks
artikel sebelumnya (
PERHATIAN! kami meneruskan
e ke contoh dari teks di lambda. Dalam artikel sebelumnya ini tidak dan hanya ditambahkan dalam inovasi):
class Math(object): @property def exception_handlers(self): return { ZeroDivisionError: lambda e: ' , ' } @ProcessException(exception_handlers) def divide(self, a, b): return a // b
di sini adalah hasil untuk Anda:
timeit.timeit('math_with_try.divide(1, 0)', number=100000, setup='from __main__ import math_with_try') 0.05079065300014918 timeit.timeit('math_with_decorator.divide(1, 0)', number=100000, setup='from __main__ import math_with_decorator') 0.16211646200099494
Sebagai kesimpulan, saya ingin mengatakan bahwa pengoptimalan, menurut pendapat saya, adalah proses yang agak rumit, dan di sini penting untuk tidak terbawa suasana dan tidak mengoptimalkan sesuatu yang merugikan keterbacaan. Kalau tidak, mendebit saat dioptimalkan akan sangat sulit.
Terima kasih atas komentar anda Saya menantikan komentar pada artikel ini juga :)
PS berkat komentar para pengguna Habr itu mungkin untuk mempercepat lebih, itulah yang terjadi:
from inspect import iscoroutinefunction from asyncio import QueueEmpty, QueueFull from concurrent.futures import TimeoutError class ProcessException(object): __slots__ = ('handlers',) def __init__(self, custom_handlers=None): if isinstance(custom_handlers, property): custom_handlers = custom_handlers.__get__(self, self.__class__) raise_exception = ProcessException.raise_exception exclude = { QueueEmpty: lambda e: None, QueueFull: lambda e: None, TimeoutError: lambda e: None } self.handlers = { **exclude, **(custom_handlers or {}), Exception: raise_exception } def __call__(self, func): handlers = self.handlers if iscoroutinefunction(func): async def wrapper(*args, **kwargs): try: return await func(*args, **kwargs) except Exception as e: return handlers.get(e.__class__, handlers[Exception])(e) else: def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except Exception as e: return handlers.get(e.__class__, handlers[Exception])(e) return wrapper @staticmethod def raise_exception(e: Exception): raise e
timeit.timeit('divide(1, 0)', number=100000, setup='from __main__ import divide') 0.13714907199755544
Dipercepat rata-rata 0,03. Terima kasih kepada
Kostiantyn dan
Yngvie .
PS Diperbarui! Saya telah lebih mengoptimalkan kode berdasarkan komentar dari komentar
onegreyonewhite dan
resetme . Diganti self.funct dengan func dan menjadikan self.handlers menjadi variabel. Eksekusi semakin dipercepat, terutama terlihat jika ada lebih banyak pengulangan per nol. Saya mengutip timeit:
timeit.timeit('t.divide_with_decorator(1, 0)', number=1000000, setup='from __main__ import t') 1.1116105649998644
Sebelum optimasi ini, eksekusi dengan nilai angka yang sama mengambil 1,24 rata-rata.
PS Saya telah lebih mengoptimalkan dengan memperkenalkan fungsi kenaikan_exception dari __init__ di @staticmethod dan saya mengaksesnya melalui variabel untuk menghapus akses melalui titik. Sebenarnya, waktu eksekusi rata-rata adalah:
timeit.timeit('t.divide_with_decorator(1, 0)', number=1000000, setup='from __main__ import t') 1.0691639049982768
ini untuk metodenya. Dan fungsi disebut lebih cepat (rata-rata):
timeit.timeit('div(1, 0)', number=1000000, setup='from __main__ import div') 1.0463485610016505