کریم در حال خواندن سوالات بود که از من به عنوان نویسندهی سوالات بابت توهینهایی که به کریم کردم در نداشتن توانایی یاد گرفتن برنامهنویسی دلخور شد. برای همین سوالی را طرح کرد که به من بفهماند که من هم توانایی یاد گرفتن برنامهنویسی را ندارم. از شما میخواهم به جای من سوال زیر را حل کنید.
## جزئیات
میخواهیم کمککنندهای (`helper`) بنویسیم که در موارد مشخص *Exception*ها را لاگ کند و از آن بگذرد.
سوال از این قرار است که باید در پایتون کمک کنندهای به نام `PyRanj` پیادهسازی کنید به صورتی که این کمککننده **سه قابلیت اصلی** زیر را داشته باشد:
```python pyranj.py
class PyRanj():
pass
```
### ۱. *Wrapper*
هر گاه از `pyranj` به عنوان *wrapper* یک تابع استفاده شود، در تابع مورد نظر نباید هیچ تغییری ایجاد شود و فقط در زمان صدا زدن تابع، در صورتی که *Exception* پرتاب شود، صرفا باید این اتفاق لاگ شود و برنامه ادامه پیدا کند. برای مثال در کد زیر باید خروجی پایینی لاگ شود:
```python source.py
from pyranj import PyRanj as pyranj
@pyranj
def f():
raise Exception('Ranj')
f()
```
```
[EXCEPTION] :: Ranj
```
### ۲. *ContextManager*
هرگاه از `pyranj` به عنوان `context` استفاده شود، در عملیات مورد نظر نباید هیچ تغییری ایجاد شود و فقط اگر حین انجام عملیات، *Exception* پرتاب شود، صرفا باید این اتفاق لاگ شود و برنامه ادامه پیدا کند. برای مثال در کد زیر باید خروجی پایینی لاگ شود:
```python source.py
from pyranj import PyRanj as pyranj
with pyranj:
raise Exception('Ranj')
```
```
[EXCEPTION] :: Ranj
```
### ۳. *Mixin*
هرگاه از `pyranj` در کلاسی ارثبری شود، **در صورتی که آن کلاس دارای متد run باشد**، تغییری که ایجاد میشود باید برابر با لاگشدن `Exception` با فرمت مشخص شده باشد. برای مثال در کد زیر باید خروجی پایینی لاگ شود:
```python source.py
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 source.py
from pyranj import PyRanj as pyranj
from logger import logger
logger.log('PyRanj Log')
```
علاوه بر قابلیتهای اصلی، کمککنندهی شما باید دارای قابلیتهای زیر نیز باشد.
#### **۱. تغییر دادن `prefix` در متن لاگ**
```python source.py
@pyranj(prefix='[PREFIX]')
def f():
raise Exception('Ranj')
f()
```
```
[PREFIX] :: Ranj
```
+ توجه کنید که این نوع از تغییر prefix در تمام ویژگیهای اصلی باید وجود داشته باشد.
#### **۲. لغو کردن `prefix` در متن لاگ**
```python source.py
from pyranj import PyRanj as pyranj
with pyranj(prefix='[PREFIX]')():
raise Exception('Ranj')
```
```
[EXCEPTION] :: Ranj
```
**توجه:** متنی که به ازای هر *Exception* لاگ میشود باید به فرمت زیر باشد، که در آن مقدار پیشفرض `prefix`، برابر است با `[EXCEPTION]`.
```python source.py
f"{prefix} :: {exception}"
```
برای فهمیدن بهتر سوال میتوانید مثال زیر و خروجی آن را مشاهده کنید.
## نمونه
```python source.py
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 ...
```
## نکات
+ میتوانید فایل اولیه این سوال را از [این لینک](/problemset/assignments/4367/download_problem_initial_project/60139/) دریافت کنید.
+ تستهای اصلی این سوال از ۵ بخش تشکیل شده که هر بخش دارای سه نوع تست است؛ یعنی هنگام ارسال شما نام هر متد تست را ۵ بار میبینید. این بخشها به ترتیب دستههای زیر هستند:
+ بخش `BasePyRanjTest`
+ بخش `TestPyRanjInstantiation`
+ بخش `TestPyRanjInstantiationWithPrefix`
+ بخش `TestPyRanjRecursiveInstantiation`
+ بخش `TestPyRanjRecursiveInstantiationWithPrefix`
## نحوه ارسال
شما باید کلاس `PyRanj` موجود در فایل `pyranj.py` را تکمیل و سپس این فایل را ارسال کنید.