.لینک‌های مفید برای شرکت در مسابقه:

می‌توانید سوال‌های خود را در رابطه با سوالات و دیگر بخش‌های پایتون از قسمت "سوال بپرسید" مطرح کنید.

توجه کنید که نسخه پایتون استفاده در سوالات ۳.۷ است. سری سوم راهنمایی‌ها به مرور به سوالات اضافه می‌شوند.

پایرنج



می‌خواهیم کمک‌کننده‌ای (helper) بنویسیم که در موارد مشخص Exceptionها را لاگ کند و از آن بگذرد.


کریم در حال خواندن سوالات بود که از من به عنوان نویسنده‌ی سوالات بابت توهین‌هایی که به کریم کردم در نداشتن توانایی یاد گرفتن برنامه‌نویسی دلخور شد. برای همین سوالی را طرح کرد که به من بفهماند که من هم توانایی یاد گرفتن برنامه‌نویسی را ندارم. از شما می‌خو‌اهم به جای من سوال زیر را حل کنید.

جزئیات🔗

سوال از این قرار است که باید در پایتون کمک کننده‌ای به نام PyRanj پیاده‌سازی کنید به صورتی که این کمک‌کننده سه قابلیت اصلی زیر را داشته باشد:

۱. Wrapper🔗

هر گاه از pyranj به عنوان wrapper یک تابع استفاده شود، در تابع مورد نظر نباید هیچ تغییری ایجاد شود و فقط در زمان صدا زدن تابع، در صورتی که Exception پرتاب شود، صرفا باید این اتفاق لاگ شود و برنامه ادامه پیدا کند. برای مثال در کد زیر باید خروجی پایینی لاگ شود:

from pyranj import PyRanj as pyranj

@pyranj
def f():
    raise Exception('Ranj')

f()
Python
[EXCEPTION] :: Ranj
Plain text

۲. ContextManager🔗

هرگاه از pyranj به عنوان context استفاده شود، در عملیات مورد نظر نباید هیچ تغییری ایجاد شود و فقط اگر حین انجام عملیات، Exception پرتاب شود، صرفا باید این اتفاق لاگ شود و برنامه ادامه پیدا کند. برای مثال در کد زیر باید خروجی پایینی لاگ شود:

from pyranj import PyRanj as pyranj

with pyranj:
    raise Exception('Ranj')
Python
[EXCEPTION] :: Ranj
Plain text

۳. Mixin🔗

هرگاه از ‍pyranj در کلاسی ارث‌بری شود، در صورتی که آن کلاس دارای متد run باشد، تغییری که ایجاد می‌شود باید برابر با لاگ‌شدن Exception با فرمت مشخص شده باشد. برای مثال در کد زیر باید خروجی پایینی لاگ شود:

from pyranj import PyRanj as pyranj

class Runner(pyranj):
    def run(self):
        raise Exception('Ranj')

 Runner().run()
Python
[EXCEPTION] :: Ranj
Plain text

توجه: برای لاگ کردن باید از متد log شی logger به شیوه‌ی زیر عمل کنید و به آن یک رشته ورودی دهید (از اینجا می‌توانید نمونه logger که در تست‌ها استفاده می‌شود را دانلود کنید):

from pyranj import PyRanj as pyranj

from logger import logger

logger.log('PyRanj Log')
Python

علاوه بر قابلیت‌های اصلی، کمک‌کننده‌ی شما باید دارای قابلیت‌های زیر نیز باشد.

۱. تغییر دادن prefix در متن لاگ🔗

@pyranj(prefix='[PREFIX]')
def f():
    raise Exception('Ranj')

f()
Python
[PREFIX] :: Ranj
Plain text

توجه کنید که این نوع از تغییر prefix در تمام ویژگی‌های اصلی باید وجود داشته باشد.

۲. لغو کردن prefix در متن لاگ🔗

from pyranj import PyRanj as pyranj

with pyranj(prefix='[PREFIX]')():
    raise Exception('Ranj')
Python
[EXCEPTION] :: Ranj
Plain text

توجه: متنی که به ازای هر Exception لاگ می‌شود باید به فرمت زیر باشد، که در آن مقدار پیشفرض prefix، برابر است با [EXCEPTION].

f"{prefix} :: {exception}" 
Python

برای فهمیدن بهتر سوال می‌توانید مثال زیر و خروجی آن را مشاهده کنید.

from pyranj import PyRanj as pyranj

@pyranj
def f1():
    raise Exception('x1')

f1()

pyrannnnnnnj = pyranj()()(prefix='Hey')()

@pyrannnnnnnj(prefix='You')
def f2():
    raise Exception('x2')

f2()

with pyranj(prefix='Yes')()()()()():
    raise Exception('x3')


class A(pyranj):
    def run(self, num):
        raise Exception('x' * num)

A().run(5)


class B(pyranj()()(prefix='Hey there is an error')):
    def run(self):
        raise Exception('run ...')

B().run()
Python
[EXCEPTION] :: x1
You :: x2
[EXCEPTION] :: x3
[EXCEPTION] :: xxxxx
Hey there is an error :: run ...
Plain text

نکات🔗

  • نام برنامه‌ی ارسالی شما باید pyranj.py باشد که در آن شی PyRanj وجود داشته باشد و ویژگی‌های گفته شده را داشته باشد.
  • می‌توانید کد تست‌ نمونه را با استفاده از این لینک دانلود کنید.
  • تست‌های اصلی این سوال از ۵ بخش تشکیل شده که هر بخش دارای سه نوع تست است‌؛ یعنی هنگام ارسال شما نام هر متد تست را ۵ بارمی‌بینید. این‌ بخش‌ها به ترتیب دسته‌های زیر هستند:
    • بخش ‍‍BasePyRanjTest
    • بخش TestPyRanjInstantiation
    • بخش TestPyRanjInstantiationWithPrefix
    • بخش ‍‍TestPyRanjRecursiveInstantiation
    • بخش ‍‍TestPyRanjRecursiveInstantiationWithPrefix

قسمت آموزشی🔗

در این قسمت راهنمایی‌های سوال به ترتیب در روزهای شنبه، دوشنبه و چهارشنبه ساعت ۱۸ اضافه می‌شود. مشکلات‌تان در راستای حل سوال را می‌توانید از بخش "سوال بپرسید" مطرح کنید.

راهنمایی ۱

توصیه می‌شود ابتدا در مورد مفاهیم wrapper, contextManager, Mixin از این لینک‌ها بخوانید:

توجه کنید که helper مورد نظر لزومی ندارد به صورت عادی تعریف شود و می‌تواند خودش حاصل فراخوانی یک تابع دیگر باشد.

برای این که helper مورد نظر به طور همزمان قابل ارث‌بری باشد و هم‌چنین قابل فراخوانی، می‌توان از تابع __call__ در metaclass کمک گرفت. برای اطلاع ازین مورد می‌توانید لینک زیر را بخوانید:

راهنمایی ۲

ابتدا یک decorator عادی تعریف کنید که یک prefix ورودی می‌گیرد و یک تابع را تغییر می‌دهد.

سپس تابعی تعریف کنید به اسم get_pyranj که یک prefix هم ورودی می‌گیرد. اینگونه در هرجایی از class یا metaclass به آن prefix دسترسی دارید. درون تابع metaclass و class مربوطه را تعریف کنید و در خروجی آن class مورد نظر را برگردانید.

توابع enter و exit را در metaclass بیفزایید (در تابع exit می‌توانید بفهمید آیا در طول اجرا با این کانتکست Exception پرتاب شده است یا نه. حالا برای این‌که حالت ارث‌بری را به درستی هندل کنید، متود getattribute را در class باز نویسی کنید. و در صورتی که attribute مورد نظر اسمش برابر با run بود آن را دکوریت کنید.

اگر توجه داشته باشید موقع فراخوانی این helper سه حالت ممکن است پیش بیاید (ساخته شدن یک آبجکت جدید که از helper ارث بری کرده است، دکوریت کردن یک تابع، و تحویل گرفتن دکوریتور با یک prefix به عنوان ورودی) که می‌توان در متود call_ در metaclass آن‌ها را هندل کرد

def pyranj_message(e, prefix=None):
    ...


def pyranj_decorator(prefix=None):
    ...


def get_pyranj(prefix_=None):
    class InnerPyRanjMeta(type):
        def __call__(cls, *args, **kwargs):
            ...
            # We may call get_pyranj recursively

        def __enter__(self):
            pass

        def __exit__(self, exc_type, exc_val, exc_tb):
            ...

    class InnerPyRanj(metaclass=InnerPyRanjMeta):
        def __getattribute__(self, item):
            ...

    return InnerPyRanj


PyRanj = get_pyranj()
Python
راهنمایی ۳

کد نهایی سوال به شکل زیر می‌شود:

from logger import logger

PREFIX = 'prefix'
FUNC = 'func'
RUN = 'run'


def pyranj_message(e, prefix=None):
    if prefix is None:
        prefix = '[EXCEPTION]'
    return f'{prefix} :: {e}'


def pyranj_decorator(prefix=None):
    def decorator(f):
        def wrapper(*args, **kwargs):
            try:
                return f(*args, **kwargs)
            except Exception as e:
                logger.log(pyranj_message(e, prefix))

        return wrapper

    return decorator


def get_pyranj(prefix_=None):
    class InnerPyRanjMeta(type):
        def __call__(cls, *args, **kwargs):
            if cls != InnerPyRanj:
                return super(InnerPyRanjMeta, cls).__call__(*args, **kwargs)
            param = args[0] if args else None
            func = param if type(param) == type(pyranj_decorator) else None
            prefix = kwargs.get(PREFIX, None)
            if func:
                return pyranj_decorator(prefix_)(func)
            return get_pyranj(prefix)

        def __enter__(self):
            pass

        def __exit__(self, exc_type, exc_val, exc_tb):
            if exc_type:
                logger.log(pyranj_message(exc_val, prefix=prefix_))
            return True

    class InnerPyRanj(metaclass=InnerPyRanjMeta):
        def __getattribute__(self, item):
            attr = super().__getattribute__(item)
            if item == RUN:
                return pyranj_decorator(prefix_)(attr)
            return attr

    return InnerPyRanj


PyRanj = get_pyranj()
Python
ارسال پاسخ برای این سؤال
در حال حاضر شما دسترسی ندارید.