--------------------------------------------------
میخواهیم کمککنندهای (`helper`) بنویسیم که در موارد مشخص *Exception*ها را لاگ کند و از آن بگذرد.
--------------------------------------------------
*کریم در حال خواندن سوالات بود که از من به عنوان نویسندهی سوالات بابت توهینهایی که به کریم کردم در نداشتن توانایی یاد گرفتن برنامهنویسی دلخور شد. برای همین سوالی را طرح کرد که به من بفهماند که من هم توانایی یاد گرفتن برنامهنویسی را ندارم. از شما میخواهم به جای من سوال زیر را حل کنید.*
# جزئیات
سوال از این قرار است که باید در پایتون کمک کنندهای به نام `PyRanj` پیادهسازی کنید به صورتی که این کمککننده **سه قابلیت اصلی** زیر را داشته باشد:
## ۱. *Wrapper*
هر گاه از `pyranj` به عنوان *wrapper* یک تابع استفاده شود، در تابع مورد نظر نباید هیچ تغییری ایجاد شود و فقط در زمان صدا زدن تابع، در صورتی که *Exception* پرتاب شود، صرفا باید این اتفاق لاگ شود و برنامه ادامه پیدا کند. برای مثال در کد زیر باید خروجی پایینی لاگ شود:
```python
from pyranj import PyRanj as pyranj
@pyranj
def f():
raise Exception('Ranj')
f()
```
```
[EXCEPTION] :: Ranj
```
## ۲. *ContextManager*
هرگاه از `pyranj` به عنوان `context` استفاده شود، در عملیات مورد نظر نباید هیچ تغییری ایجاد شود و فقط اگر حین انجام عملیات، *Exception* پرتاب شود، صرفا باید این اتفاق لاگ شود و برنامه ادامه پیدا کند. برای مثال در کد زیر باید خروجی پایینی لاگ شود:
```python
from pyranj import PyRanj as pyranj
with pyranj:
raise Exception('Ranj')
```
```
[EXCEPTION] :: Ranj
```
## ۳. *Mixin*
هرگاه از `pyranj` در کلاسی ارثبری شود، **در صورتی که آن کلاس دارای متد run باشد**، تغییری که ایجاد میشود باید برابر با لاگشدن `Exception` با فرمت مشخص شده باشد. برای مثال در کد زیر باید خروجی پایینی لاگ شود:
```python
from pyranj import PyRanj as pyranj
class Runner(pyranj):
def run(self):
raise Exception('Ranj')
Runner().run()
```
```
[EXCEPTION] :: Ranj
```
----
**توجه:** برای لاگ کردن باید از متد `log` شی `logger` به شیوهی زیر عمل کنید و به آن یک رشته ورودی دهید (از [اینجا](https://quera.ir/qbox/download/sjEYu6PI2A/logger.py) میتوانید نمونه `logger` که در تستها استفاده میشود را دانلود کنید):
```python
from pyranj import PyRanj as pyranj
from logger import logger
logger.log('PyRanj Log')
```
علاوه بر قابلیتهای اصلی، کمککنندهی شما باید دارای قابلیتهای زیر نیز باشد.
### ۱. تغییر دادن `prefix` در متن لاگ
```python
@pyranj(prefix='[PREFIX]')
def f():
raise Exception('Ranj')
f()
```
```
[PREFIX] :: Ranj
```
**توجه کنید که این نوع از تغییر prefix در تمام ویژگیهای اصلی باید وجود داشته باشد.**
### ۲. لغو کردن `prefix` در متن لاگ
```python
from pyranj import PyRanj as pyranj
with pyranj(prefix='[PREFIX]')():
raise Exception('Ranj')
```
```
[EXCEPTION] :: Ranj
```
**توجه:** متنی که به ازای هر *Exception* لاگ میشود باید به فرمت زیر باشد، که در آن مقدار پیشفرض `prefix`، برابر است با `[EXCEPTION]`.
```python
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()
```
```
[EXCEPTION] :: x1
You :: x2
[EXCEPTION] :: x3
[EXCEPTION] :: xxxxx
Hey there is an error :: run ...
```
# نکات
+ نام برنامهی ارسالی شما باید `pyranj.py` باشد که در آن شی `PyRanj` وجود داشته باشد و ویژگیهای گفته شده را داشته باشد.
+ میتوانید کد تست نمونه را با استفاده از این [لینک](https://quera.ir/qbox/download/lSEP2gEP6T/pyranj_sampletest.zip) دانلود کنید.
+ تستهای اصلی این سوال از ۵ بخش تشکیل شده که هر بخش دارای سه نوع تست است؛ یعنی هنگام ارسال شما نام هر متد تست را ۵ بارمیبینید. این بخشها به ترتیب دستههای زیر هستند:
+ بخش `BasePyRanjTest`
+ بخش `TestPyRanjInstantiation`
+ بخش `TestPyRanjInstantiationWithPrefix`
+ بخش `TestPyRanjRecursiveInstantiation`
+ بخش `TestPyRanjRecursiveInstantiationWithPrefix`
# قسمت آموزشی
در این قسمت راهنماییهای سوال به ترتیب در روزهای شنبه، دوشنبه و چهارشنبه ساعت ۱۸ اضافه میشود. مشکلاتتان در راستای حل سوال را میتوانید از بخش ["سوال بپرسید"](https://quera.ir/contest/clarification/18303/) مطرح کنید.
<details class="blue">
<summary>راهنمایی ۱</summary>
توصیه میشود ابتدا در مورد مفاهیم
wrapper, contextManager, Mixin
از این لینکها بخوانید:
+ https://wiki.python.org/moin/FunctionWrappers
+ https://book.pythontips.com/en/latest/context_managers.html
+ https://www.ianlewis.org/en/mixins-and-python
توجه کنید که `helper` مورد نظر لزومی ندارد به صورت عادی تعریف شود و میتواند خودش حاصل فراخوانی یک تابع دیگر باشد.
برای این که `helper` مورد نظر به طور همزمان قابل ارثبری باشد و همچنین قابل فراخوانی، میتوان از تابع `__call__` در `metaclass` کمک گرفت. برای اطلاع ازین مورد میتوانید لینک زیر را بخوانید:
+ https://python-3-patterns-idioms-test.readthedocs.io/en/latest/Metaprogramming.html
</details>
<details class="blue">
<summary>راهنمایی ۲</summary>
ابتدا یک 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 آنها را هندل کرد
```python
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()
```
</details>
<details class="blue">
<summary>راهنمایی ۳</summary>
کد نهایی سوال به شکل زیر میشود:
```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()
```
</details>
ارسال پاسخ برای این سؤال
در حال حاضر شما دسترسی ندارید.