...
 
Commits (9)
## CALC
`eval` `input` `namespaces`
### Условие
В этом задании вам нужно написать интерактивный калькулятор, используя функцию `eval`.
При запуске программа должна печатать приглашение к вводу `>>> ` в stdout, читать пользовательский ввод из stdin, выполнять полученное выражение, выводить результат в stdout, и так далее в цикле. Выполнение завершается при получении EOF (см. задачу `input_`).
Выражения должны выполняться в закрытом пространстве имён, в который могут быть предзагружены некоторые объекты, но у калькулятора не должно быть доступа к встроенным функциям из `__builtins__`.
Предполагаем что ввод всегда валидный, обрабатывать ошибки не нужно.
### Пример
```bash
$ python calc.py
>>> 1 + 2
3
>>> [i**2 for i in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> math.cos(math.pi)
-1.0
>>> print(123)
NameError: name 'print' is not defined
>>>
```
### Что почитать
Почему eval -- зло: https://habr.com/ru/post/221937
import math
from typing import Any, Dict, Optional
PROMPT = '>>> '
def run_calc(context: Optional[Dict[str, Any]] = None) -> None:
"""Run interactive calculator session in specified namespace"""
if __name__ == '__main__':
context = {'math': math}
run_calc(context)
import io
import math
import sys
import pytest
from .calc import run_calc
def test_basic(capsys, monkeypatch): # type: ignore
monkeypatch.setattr(sys, 'stdin', io.StringIO(
'3 + 5\n'
'24 / 2\n'
'math.cos(math.pi)\n'
))
run_calc({'math': math})
captured = capsys.readouterr()
assert captured.out == (
'>>> 8\n'
'>>> 12.0\n'
'>>> -1.0\n'
'>>> \n'
)
def test_context(capsys, monkeypatch): # type: ignore
context = {'foo': lambda x: x * 2}
monkeypatch.setattr(sys, 'stdin', io.StringIO(
'foo(2)\n'
'foo("abc")\n'
'foo([1, 2, 3])\n'
))
run_calc(context)
captured = capsys.readouterr()
assert captured.out == (
'>>> 4\n'
'>>> abcabc\n'
'>>> [1, 2, 3, 1, 2, 3]\n'
'>>> \n'
)
def test_context_error(monkeypatch): # type: ignore
monkeypatch.setattr(sys, 'stdin', io.StringIO(
'math.sqrt(144)\n'
'foo(144)\n'
'print(123)\n'
))
with pytest.raises(NameError, match='name \'math\' is not defined'):
run_calc()
with pytest.raises(NameError, match='name \'foo\' is not defined'):
run_calc()
with pytest.raises(NameError, match='name \'print\' is not defined'):
run_calc()
## LRU cache
`decorators` `functools` `type casts` `OrderedDict`
### Условие
Бывает полезно оптимизировать вызовы "тяжёлых" функций с помощью кеширования.
Кеширование – это сохранение результатов выполнения функций для предотвращения повторных вычислений.
Перед вызовом функции проверяется есть ли уже вычисленный результат. Если есть – функция не вызывается,
а возвращается сохранённое значение.
Реализуйте декоратор для Least Recently Used (LRU) Cache. Пользователь указывает размер кеша
`N`, и в кеше сохраняются значения для `N` наборов входных параметров функции, причем вытесняется из кеша сначала то,
что использовалось давней всего.
Для решения задачи мы рекомендуем использовать `OrderedDict`.
Декоратор назовите `@cache`, он должен принимать один параметр – размер кеша. Поддержите как
можно более широкий класс функций (функции многих аргументов, функции с именоваными параметрами,
принимающие сложные типы и т.д.).
Декоратор не должен затирать документацию функции.
Естественно, вам нельзя пользоваться дефолтным `functools.lru_cache`, а также **не понадобится** `setrecursionlimit`
для прохождения тестов.
### Пример
Следующий код
```python
def foo(value):
print('calculate foo for {}'.format(value))
return value
foo(1)
foo(2)
foo(1)
foo(2)
foo(3)
foo(1)
```
производит вот такой вывод:
```
calculate foo for 1
calculate foo for 2
calculate foo for 3
```
### Typehints
Правильно описать типы внутри декоратора в терминах mypy поможет [этот
пример](https://mypy.readthedocs.io/en/latest/generics.html#declaring-decorators) из документации.
from typing import Callable, Any, TypeVar
Function = TypeVar('Function', bound=Callable[..., Any])
def cache(max_size: int) -> Callable[[Function], Function]:
"""
Returns decorator, which stores result of function
for `max_size` most recent function arguments.
:param max_size: max amount of unique arguments to store values for
:return: decorator, which wraps any function passed
"""
from typing import Any, Tuple, Union, TypeVar
from .lru_cache import cache
from _pytest.capture import CaptureFixture # typing
@cache(20)
def binomial(n: int, k: int) -> int:
if k > n:
return 0
if k == 0:
return 1
return binomial(n - 1, k) + binomial(n - 1, k - 1)
@cache(2048)
def ackermann(m: int, n: int) -> int:
print(f'Calculating for {m} and {n}...')
if m == 0:
return n + 1
if m > 0 and n == 0:
return ackermann(m - 1, 1)
if m > 0 and n > 0:
return ackermann(m - 1, ackermann(m, n - 1))
assert False, 'unreachable'
@cache(1)
def join(args: Union[Tuple[Any, ...], Any]) -> Tuple[Any, ...]:
result: Tuple[Any, ...] = tuple()
for arg in args:
if isinstance(arg, tuple):
result += join(arg)
else:
result += (arg,)
return result
def test_cache_not_changes_func() -> None:
T = TypeVar('T')
@cache(1)
def func(a: T) -> T:
"""test doc"""
return a
assert func.__name__ == 'func'
assert func.__doc__ == 'test doc'
assert func.__module__ == __name__
def test_binomial() -> None:
result = sum(binomial(30, i) for i in range(31))
assert result == 2 ** 30
def test_ackermann(capsys: CaptureFixture) -> None:
result = ackermann(3, 7)
assert result == 1021
assert capsys.readouterr().out.count('\n') == 2558
def test_join_lists() -> None:
result = join(((1, 2, 3), 1, (1, 2, 3, 4, ((1, 2), 2, 3))))
assert result == (1, 2, 3, 1, 1, 2, 3, 4, 1, 2, 2, 3)
# PROFILER
`decorators` `functools` `datetime` `interview`
### Условие
Напишите декоратор `@profiler`, который при вызове функции будет замерять время ее исполнения
(в секундах, можно дробное) и количество рекусивных вызовов произошедших при **последнем** вызове функции.
Для работы со временем в питоне есть замечательный модуль `datetime`.
Декоратор не должен затирать основные атрибуты функции: `__name__`, `__doc__`, `__module__`.
Пользоваться глобальными переменными запрещено, сохранять результаты замеров нужно в **атрибутах** функции.
Атрибуты назовите `last_time_taken` и `calls`.
NB. Вообще, хранить какие-то свои данные в атрибутах функции - антипаттерн, и в продакшен коде так делать не стоит.
Однако на собеседовании (не обязательно в Яндекс) вполне могут дать задачу написать что-то подобное,
поэтому стоит быть готовым :)
Кроме того, в данной задаче вам **не нужно** не указывать типы, т.к. они будут только мешаться при попытке
достать в тестах нужные аттрибуты, которых нет у произвольной функции (`Callable`).
### Пример
```python
>>> @profiler
... def f():
... pass
...
>>> f()
>>> f()
>>> f.calls
1
>>> f.last_time_taken
3.7e-05
```
def profiler(func): # type: ignore
"""
Returns profiling decorator, which counts calls of function
and measure last function execution time.
Results are stored as function attributes: `calls`, `last_time_taken`
:param func: function to decorate
:return: decorator, which wraps any function passed
"""
from collections import namedtuple
from datetime import datetime
from .profiler import profiler
@profiler
def ackermann(m: int, n: int) -> int:
if m == 0:
return n + 1
if m > 0 and n == 0:
return ackermann(m - 1, 1)
if m > 0 and n > 0:
return ackermann(m - 1, ackermann(m, n - 1))
assert False, "unreachable"
@profiler
def strange_function(a, b, c=1, d=2): # type: ignore
return a.foo + b.bar + c + d
def test_example() -> None:
start = datetime.now()
result = ackermann(3, 2)
delta = datetime.now() - start
assert ackermann.calls == 541
assert ackermann.last_time_taken <= delta.total_seconds()
assert result == 29
def test_profiler_one_call() -> None:
start = datetime.now()
result = ackermann(3, 2)
delta = datetime.now() - start
assert ackermann.calls == 541
assert ackermann.last_time_taken <= delta.total_seconds()
assert result == 29
def test_profiler_many_call() -> None:
_ = ackermann(0, 1)
_ = ackermann(3, 2)
assert ackermann.calls == 541
def test_profiler_strange_akkerman() -> None:
nt = namedtuple('foo', ['foo', 'bar'])
expected_result = 10
result = strange_function(nt(1, 2), nt(1, 2), 3, 4)
assert result == expected_result
def test_profiler_not_changes_func() -> None:
@profiler
def f() -> None:
"""test"""
pass
assert f.__name__ == 'f'
assert f.__doc__ == 'test'
assert f.__module__ == __name__
Subproject commit d8537ddface8f6c658048a1f4eeeee3f5b9e1d16
Subproject commit 5b41f00d22daf272a223daefd506b7b988f38e48