Python (BSU FAMCS Fall’19)
Task 1. (1 point). Implement a decorator handle_error to handle exceptions in a function regarding
decorator parameters. Parameters are the next:
∙ re_raise – a flag to control whether an exception will be re-raised from a function, True by
default. All exceptions that aren’t inherited from exc_type should be re-raised unconditionally;
∙ log_traceback – exception traceback will be logged if the flag is set to True (by default). All
exceptions that aren’t inherited from exc_type should not be processed;
∙ exc_type – exception base type or a non-empty tuple of exception base types that are handled
by the decorator (Exception by default);
∙ tries – number of times function has to be invoked again with the same parameters if it raises
an exception (default value 1 means no repeats). Check the value of tries provided as infinite
tries are not permitted (e.g. None or negative integer values);
∙ delay – a delay between tries in seconds (may be float, by default it’s 0);
∙ backoff – a value that a delay is multiplied by from attempt to attempt (by default 1, see an
example below).
Note the usage of a module global logger object is an ordinary practice. The logger is an instance
of logging.Logger basically.
Save the decorator in error_handling.py file.
Example 1
# suppress exception , log traceback
@handle_error ( re_raise = False )
def some_function (): x = 1 / 0 # ZeroDivisionError
some_function ()
print ( 1 ) # line will be executed as exception is suppressed
Example 2
# re - raise exception and doesn ’t log traceback as exc_type doesn ’t match
@handle_error ( re_raise =False , exc_type = KeyError )
def some_function (): x = 1 / 0 # ZeroDivisionError
some_function ()
print ( 1 ) # line won ’t be executed as exception is re - raised
Example 3
Let suppose that random.random() function consequently produces 0.2, 0.5, 0.3 values. Thus the
decorator invokes an original some_function, handles it, waits for 0.5 seconds, tries again, waits for
1 more second, tries again and finally re-raises an exception.
import random
@handle_error ( re_raise =True , tries =3 , delay =0 .5 , backoff =2 )
def some_function ():
if random . random () < 0 . 75: x = 1 / 0 # ZeroDivisionError
some_function ()
Task 2. (0.5 points). Implement a context manager handle_error_context that is idiomatically
similar to handle_error decorator from the task above and intended to handle exceptions depending
on the next parameters:
∙ re_raise – a flag to control whether an exception will be re-raised from a function, True by
default. All exceptions that aren’t inherited from exc_type should be re-raised unconditionally;
∙ log_traceback – exception traceback will be logged if the flag is set to True (by default). All
exceptions that aren’t inherited from exc_type should not be processed;
∙ exc_type – exception base type or a non-empty tuple of exception base types that are handled
by the decorator (Exception by default);
The code of handle_error decorator is supposed to be re-used to avoid code duplication implementing
your context manager. Also try to avoid class based context manager implementation.
Save your solution in error_handling.py file.
Example
# log traceback , re - raise exception
with handle_error_context ( log_traceback =True , exc_type = ValueError ):
raise ValueError ()
Task 3. (1 point). Implement a metaclass BoundedMeta that limits the number of class instances
created.
Provide a parameter max_instance_count to set maximal number of instances value (1 by default).
Raise an exception TypeError trying to create a new instance over the limit. Number of instances will
be supposed to be unlimited if max_instance_count value equals None.
In an example below class C has BoundedMeta metaclass so no more than 2 instances may be
created.
Save your metaclass implementation in functional.py file.
BoundedMeta class boilerplate
class C ( metaclass = BoundedMeta , max_instance_count =2 ):
pass
c1 = C ()
c2 = C ()
try :
c3 = C ()
except TypeError :
print (’ everything works fine !’)
else :
print (’something goes wrong !’)
Task 4. (1 point). Implement a class BoundedBase that has an abstract class method providing a
value of maximal number of class instances that are permitted to create.
Name the method get_max_instance_count.
As in the previous task raise an exception TypeError trying to create a new instance over the limit.
Number of instances will be supposed to be unlimited if get_max_instance_count returns None.
In an example below only one instance of class D inherited from BoundedBase class is allowed to be
created.
Save your class implementation in functional.py file.
BoundedBase class boilerplate
class D ( BoundedBase ):
@classmethod
def get_max_instance_count ( cls ):
return 1 2
d1 = D ()
try :
d2 = D ()
except TypeError :
print (’ everything works fine !’)
else :
print (’something goes wrong !’)
Task 5. (0.5 points). Implement a function that returns the number of times it has been called.
Global variables are not permitted. In other words all code related to the task may contain only a
function definition leading with a keyword def.
Name your solution function smart_function and save it into functional.py file.
Example
for real_call_count in range (1 , 5 ):
assert f () == real_call_count
task1
import logging
from time import sleep
def handle_error(re_raise = True, log_traceback = True, exc_type= Exception, tries = 1, delay = 0, backoff = 1):
def log_decorator(f):
def wrapper(*args, **kw):
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
if log_traceback == True:
logger.info("Start log")
exc_info =""
for i in range(tries):
exc_info = ""
try:
return f(*args, **kw)
except exc_type as e:
if log_traceback == True:
logger.error(repr(e))
exc_info = repr(e)
sleep(delay*backoff)
if re_raise == True and exc_info !="":
print(exc_info)
return wrapper
return log_decorator
import random
@handle_error ( re_raise =True , tries =3 , delay =0.5 , backoff =2 )
def some_function ():
if random.random()<0.75: x = 1 / 0 # ZeroDivisionError
some_function ()
这个 decorator 有些复杂,只实现第一个例子。
def handle_error(re_raise = True, log_traceback = True):
def log_decorator(f):
def wrapper(*args, **kw):
try:
return f(*args, **kw)
except Exception as e:
print(repr(e))
return wrapper
return log_decorator
@handle_error(re_raise = False)
def some_function():
x = 1 / 0
some_function ()
print ( 1 ) # line will be executed as exception is suppressed
task4 的代码
class BoundedBase:
number = 0
def __init__(self):
BoundedBase.number = BoundedBase.number + 1
if BoundedBase.number > 1:
raise TypeError
class D ( BoundedBase ):
@classmethod
def get_max_instance_count ( cls ):
return 1
d1 = D()
try :
d2 = D()
except TypeError :
print ('everything works fine !')
else:
print ('something goes wrong !')
task3 的解答, 这个 metaClass 资料太少了。
class BoundedMeta(type):
number = 0
max_instance_count = 1
@classmethod
def __prepare__(metacls, name, bases, **kargs):
return super().__prepare__(name, bases, **kargs)
def __new__(metacls, name, bases, namespace, **kargs):
return super().__new__(metacls, name, bases, namespace)
def __init__(cls, name, bases, namespace, max_instance_count = 1, **kargs):
BoundedMeta.max_instance_count = max_instance_count
super().__init__(name, bases, namespace)
def __call__(self, *args, **kwargs):
BoundedMeta.number = BoundedMeta.number + 1
if BoundedMeta.max_instance_count is not None:
if BoundedMeta.number > BoundedMeta.max_instance_count:
raise TypeError
class C(metaclass = BoundedMeta , max_instance_count = 2):
pass
c1 = C ()
c2 = C ()
try :
c3 = C ()
except TypeError :
print ('everything works fine !')
else:
print ('something goes wrong !')
第5个
def smart_function(func):
num = [0]
def call_func():
func()
num[0] += 1
print("执行次数",num[0])
return call_func
# 待测试方法
@smart_function
def test():
pass
test()
test()
test()
执行次数 1
执行次数 2
执行次数 3
上面的 task3 的解答 , 符合要求吗?