Commit 337ecbad authored by Vadim Mazaev's avatar Vadim Mazaev

add lecture6 tasks

parent abe2a8c7
## ИГРА БЕЗ ИМЕНИ
### Условие
Мы хотим сыграть с вами в игру.
Игра представляет из себя класс на питоне у которого есть некоторое (большое) количество методов,
которые надо позвать в правильном порядке.
Каждый из этих методов принимает единственный параметр –
объект типа `Player`, которому она скажет какой метод надо звать следующим.
Тонкость состоит в том, что игра сама решает как именно обращаться к игроку,
и каждый раз при обращении говорит ему не только метод, к которому надо обратиться к ней,
но и метод, который она позовет у игрока в следующий раз, чтобы сказать ему новые имена.
Более того, игра не любит жуликов, и игроку разрешено иметь только один
правильный метод для приема новых имен, если в классе `Player` будут оставаться старые методы -
ей это не понравится.
Игра заканчивается по желанию игры. Она выбросит правильное исключение,
и мы его обработаем, зачтя вам задачу.
Игра начинается, когда у обьекта класса `Game` вызывают метод `start`,
которому надо передать на вход объект класса `Player` с методом `ready`.
```python
class Game:
def start(self, player):
player.ready('next_step', 'next_player_method')
...
```
Вызвав в ответ этот метод (`ready`), игра передаст в него новые названия методов,
которые заменят `start` и `ready` в следующей итерации.
### Замечание
* Не мудрите, решение занимает 25-30 строк.
* Игра происходит блоками, а не рекурсивно:
- `player.play(game)` -> `game.start(player)` -> `player.ready('next_step', 'next_player_method')`
- `player.play(game)` -> `game.next_step(player)` -> `player.next_player_method('another_game_step', 'yet_another_player_method')`
- `player.play(game)` -> `game.another_game_step(player)` -> ...
import typing as tp
class Player:
def ready(self, next_game_method: str, next_my_method: str) -> None:
"""
:param next_game_method: next game class method to call
:param next_my_method: next method game will call
"""
def play(self, game: tp.Any) -> None:
"""
:param game: some class with method start
"""
from importlib import reload
import pytest
import typing as tp
from . import game_has_no_name
@pytest.fixture()
def player_class() -> tp.Generator[tp.Type[game_has_no_name.Player], None, None]:
reload(game_has_no_name)
yield game_has_no_name.Player
class LooseException(Exception):
pass
class WinException(Exception):
def __init__(self, value: float) -> None:
self.prise = value
super().__init__()
class Game:
def start(self, player: game_has_no_name.Player) -> None:
player.ready('next_step', 'fake_player_method')
def next_step(self, player: game_has_no_name.Player) -> None:
if hasattr(player, 'ready'):
raise LooseException()
raise WinException(2)
def test_player(player_class: tp.Type[game_has_no_name.Player]) -> None:
game = Game()
player = player_class()
with pytest.raises(WinException):
for i in range(1000000):
player.play(game)
## ИГРА КРЕВЕТОК С РЫБАМИ
### Предисловие
Океан представляется двумерным массивом ячеек.
Каждая явейка может быть:
* пустой
* со скалой
* с рыбой
* с креветкой
Ячейки являются соседними, если имеют хотя бы по одной общей точке с данной ячейкой.
Все ячейки за границами игрового поля считаются пустыми.
Правила обновления ячеек:
* ячейки со скалами не меняются во времени
* если какой-то рыбе слишком тесно (от 4 и более соседей-рыб), то рыба погибает
* если рыбе слишком одиноко (0 или 1 соседей-рыб), то рыба погибает
* если у рыбы 2 или 3 соседа-рыбы, то она просто продолжает жить
* соседи-скалы и соседи-креветки никак не влияют на жизнь рыб
* креветки существуют по аналогичным правилам (учитывая только соседей креветок)
* если какая-то ячейка океана была пуста и имела ровно 3-х соседей рыб, то в следующий момент времени в ней рождается рыба.
Иначе если у ячейки было ровно три соседа-креветки, в ней рождается креветка
* изменение всех ячеек океана происходит одновременно, учитывая только состояния ячеек в предыдущий момент времени
В каждый квант времени ячейки последовательно обрабатываются.
### Условие
Вам нужно в файле ```life_game.py``` реализовать класс ```LifeGame```.
* Инициализируется начальным состоянием океана - прямоугольным списком списков (формируя тем самым матрицу), каждый элемент которого это число.
0 - если ячейка пустая, 1 - со скалой, 2 - с рыбой, 3 - с креветкой
* Содержит метод ```get_next_generation```, который обновляет состояние океана и возвращает его содержимое
* ```get_next_generation``` должен быть единственный публичным методом в классе
* Вам нужно подумать, как поделить все на небольшие логические методы, которые,
в отличие от ```get_next_generation``` пометить "приватными", то есть через underscore.
Например, вы хотите создать метод, который извлекает соседей для клетки
```python
class LifeGame(object):
...
def _get_neighbours(self, i: int, j: int):
pass
```
Это не настоящее сокрытие реализации. Это способ оповестить пользователя о том, что у него нет никаких гарантий на этот метод.
## Пример
```python
>>> life_game = LifeGame([[1, 1], [1, 1]])
>>> life_game.get_next_generation()
[[1, 1], [1, 1]]
```
class LifeGame(object):
"""
Class for Game life
"""
import pytest
from types import FunctionType
import typing as tp
from .life_game import LifeGame
class Case:
def __init__(self, board: tp.List[tp.List[int]], expected: tp.List[tp.List[int]], generation_number: int) -> None:
self.board = board
self.expected = expected
self.generation_number = generation_number
TESTS = [
Case(
board=[
[0, 2, 0],
[0, 2, 0],
[0, 2, 0]
],
expected=[
[0, 0, 0],
[2, 2, 2],
[0, 0, 0]
],
generation_number=1
),
Case(
board=[
[0, 0, 0, 0],
[0, 3, 3, 0],
[0, 3, 3, 0],
[0, 1, 0, 1]
],
expected=[
[0, 0, 0, 0],
[0, 3, 3, 0],
[0, 3, 3, 0],
[0, 1, 0, 1]
],
generation_number=7
),
Case(
board=[
[0, 0, 0, 0],
[0, 1, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 0]
],
expected=[
[0, 0, 0, 0],
[0, 1, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 0]
],
generation_number=2
),
Case(
board=[
[0, 0, 0, 0],
[0, 3, 0, 0],
[0, 3, 0, 0],
[0, 0, 3, 0],
[0, 0, 0, 0]
],
expected=[
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 3, 3, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]
],
generation_number=1
),
Case(
board=[
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 2, 0, 0, 0, 0],
[0, 0, 0, 2, 2, 2, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0]
],
expected=[
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 2, 2, 2, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 2, 0, 0, 0, 0, 0, 2, 0],
[0, 2, 0, 0, 0, 0, 0, 2, 0],
[0, 2, 0, 0, 0, 0, 0, 2, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 2, 2, 2, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0]
],
generation_number=9
),
Case(
board=[
[0, 1, 0, 0, 0, 1, 0, 0, 0, 1],
[0, 0, 0, 1, 0, 0, 0, 1, 0, 0],
[0, 1, 0, 0, 0, 1, 0, 0, 0, 1],
[0, 0, 0, 1, 3, 0, 3, 1, 0, 0],
[0, 1, 0, 0, 3, 1, 3, 0, 0, 1],
[0, 0, 0, 1, 3, 0, 3, 1, 0, 0],
[0, 1, 0, 0, 3, 1, 3, 0, 0, 1],
[0, 0, 0, 1, 0, 0, 0, 1, 0, 0],
[0, 1, 0, 0, 0, 1, 0, 0, 0, 1],
[0, 0, 0, 1, 0, 0, 0, 1, 0, 0]
],
expected=[
[0, 1, 0, 0, 0, 1, 0, 0, 0, 1],
[0, 0, 0, 1, 0, 0, 0, 1, 0, 0],
[0, 1, 0, 0, 0, 1, 0, 0, 0, 1],
[0, 0, 0, 1, 0, 0, 0, 1, 0, 0],
[0, 1, 0, 3, 3, 1, 3, 3, 0, 1],
[0, 0, 0, 1, 3, 0, 3, 1, 0, 0],
[0, 1, 0, 0, 0, 1, 0, 0, 0, 1],
[0, 0, 0, 1, 0, 0, 0, 1, 0, 0],
[0, 1, 0, 0, 0, 1, 0, 0, 0, 1],
[0, 0, 0, 1, 0, 0, 0, 1, 0, 0]
],
generation_number=9
),
Case(
board=[
[0, 1, 2, 3, 0],
[2, 3, 0, 1, 2],
[0, 1, 2, 3, 0],
[2, 3, 0, 1, 2],
[0, 1, 2, 3, 0]
],
expected=[
[0, 1, 0, 0, 0],
[0, 0, 0, 1, 0],
[0, 1, 0, 0, 0],
[0, 0, 0, 1, 0],
[0, 1, 0, 0, 0]
],
generation_number=2
),
Case(
board=[
[3, 3, 3, 3, 3],
[0, 0, 0, 0, 0],
[3, 3, 3, 3, 3],
[0, 0, 0, 0, 0],
[3, 3, 3, 3, 3]
],
expected=[
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
],
generation_number=10
),
Case(
board=[
[0]
],
expected=[
[0]
],
generation_number=100000
),
Case(
board=[
[1]
],
expected=[
[1]
],
generation_number=1
),
Case(
board=[
[2]
],
expected=[
[0]
],
generation_number=1
),
Case(
board=[
[3]
],
expected=[
[0]
],
generation_number=1
),
Case(
board=[
[2],
[2]
],
expected=[
[0],
[0]
],
generation_number=1
),
Case(
board=[
[3],
[3]
],
expected=[
[0],
[0]
],
generation_number=1
),
Case(
board=[
[2, 2],
[2, 2]
],
expected=[
[2, 2],
[2, 2]
],
generation_number=1
),
Case(
board=[
[3, 3],
[3, 3]
],
expected=[
[3, 3],
[3, 3]
],
generation_number=1
),
Case(
board=[
[3, 3, 0],
[3, 0, 2],
[0, 2, 2]
],
expected=[
[3, 3, 0],
[3, 2, 2],
[0, 2, 2]
],
generation_number=1
),
]
@pytest.mark.parametrize("test_case", TESTS)
def test_life_game(test_case: Case) -> None:
game = LifeGame(test_case.board)
generation = None
for _ in range(test_case.generation_number):
generation = game.get_next_generation()
assert generation == test_case.expected
def test_methods() -> None:
methods_names = [x for x, y in LifeGame.__dict__.items() if type(y) == FunctionType]
private_methods = {x for x in methods_names if x.startswith('_')}
public_methods = {x for x in methods_names if not x.startswith('_')}
assert public_methods == {'get_next_generation'}
assert len(private_methods - {'__init__'}) > 0
## ЛИСТ ТВИСТ
`UserList` `inheritance`
### Условие
Вам нужно реализовать класс с интерфейсом списка, в котором добавить аттрибуты:
* **reversed** (с коротким псевдонимом **R**):
* При обращении возвращается список с элементами в обратном порядке.
* **first** (с коротким псевдонимом **F**):
* При обращении возвращается первый элемент списка.
* Должна присутствовать возможность изменять этот атрибут. Вместе с ним должен меняться и сам список
* При попытке доступиться или установить **first** в пустом списке поведение не определено
* **last** (с коротким псевдонимом **L**):
* При обращении возвращается последний элемент списка.
* Должна присутствовать возможность изменять этот атрибут. Вместе с ним должен меняться и сам список.
* При попытке доступиться или установить **last** в пустом списке поведение не определено
* **size** (с коротким псевдонимом **S**):
* При обращении возвращается размер списка.
* Должна присутствовать возможность изменять этот атрибут:
при увеличении размера в конец должны добавляться значения None,
а при уменьшении последние элементы должны удаляться.
## Пример
```python
>>> list_twist = ListTwist([1, 2, 3])
>>> print(list_twist.reversed)
[3, 2, 1]
>>> print(list_twist.first)
1
>>> list_twist.F = 0
>>> print(list_twist)
[0, 2, 3]
>>> print(list_twist.last)
3
>>> list_twist.last = 4
[0, 2, 4]
>>> list_twist.size = 2
>>> print(list_twist)
[0, 2]
>>> list_twist.size = 4
>>> print(list_twist)
[0, 2, None, None]
```
### Замечания
* Все перечисленные атрибуты не являются методами (см. пример)
* Нужно вычислять на лету и модифицировать состояние
* Важно не сломать базовую функциональность списка
from collections import UserList
import typing as tp
# https://github.com/python/mypy/issues/5264#issuecomment-399407428
if tp.TYPE_CHECKING:
BaseList = UserList[tp.Optional[tp.Any]]
else:
BaseList = UserList
class ListTwist(BaseList):
"""
List-like class with additional attributes:
* reversed, R - return reversed list
* first, F - insert or retrieve first element;
Undefined for empty list
* last, L - insert or retrieve last element;
Undefined for empty list
* size, S - set or retrieve size of list;
If size less than list length - truncate to size;
If size greater than list length - pad with Nones
"""
This diff is collapsed.
Subproject commit dc3b73ec6c90cc184c54042315bf4e16a485d296
Subproject commit 80bbf5b6b0f7281c12acd1cda52433ef877389f0
## TYPY PROTOCOL
`typing` `Union` `Tuple` `Optional` `Callable`
### Условие
Нужно аннотировать все функции. Для этого вам нужно почитать тесты,
и подобрать самые подходящие общие типы, которые будут им удовлетворять.
Нельзя использовать ```Any```.
### Про задачу
* Здесь вам нужно вспомнить [базовые типы для аннотаций](https://mypy.readthedocs.io/en/latest/kinds_of_types.html)
* В тестах проверяется:
* Что вы везде навесили аннотации
* Что в случае плохих переданных значений mypy падает, а в случае хороших = успешно проходит
Это первая задача в цикле из трех задач про типы.
Воспринимайте тесты, как условия, которым должен удовлетворять интерфейс ваших функций и классов.
\ No newline at end of file
# mypy: ignore-errors
import typing as tp
import mypy.api
import inspect
import tempfile
from .typy import func1, func2, func3, func4, func5
def check_annotations(func: tp.Callable[..., tp.Any]) -> None:
for p, value in inspect.signature(func).parameters.items():
assert value.annotation != inspect.Signature.empty, f"Parameter {p} does not have annotation"
assert value.annotation != tp.Any, f"Parameter {p} has prohibited Any annotation"
assert inspect.signature(func).return_annotation != inspect.Signature.empty, "Return does not have annotation"
assert inspect.signature(func).return_annotation != tp.Any, "Return has prohibited Any annotation"
def check_func(func: tp.Callable[..., tp.Any], test_case: tp.Callable[..., tp.Any], is_success: bool) -> None:
with tempfile.NamedTemporaryFile(mode="w") as fp:
fp.write("import typing as tp")
fp.write("\n")
fp.write("import numbers")
fp.write("\n")
fp.write("import abc")
fp.write("\n\n")
fp.write(inspect.getsource(func))
fp.write("\n")
fp.write(inspect.getsource(test_case))
fp.write("\n")
fp.flush()
normal_report, error_report, exit_status = mypy.api.run([fp.name, '--config-file', ''])
print(f"Report:\n{normal_report}\n{error_report}")
result_success_status = exit_status == 0
assert result_success_status is is_success, \
f"Mypy check should be {is_success}, but result {result_success_status}"
def func1_case_1() -> None:
func1("dd")
def func1_case_2() -> None:
func1(1j)
def func1_case_3() -> None:
func1(1)
def func1_case_4() -> None:
func1(1.0)
def func1_case_5() -> None:
class R(float):
pass
func1(R(1))
def test_func1() -> None:
check_annotations(func1)
check_func(func1, func1_case_1, False)
check_func(func1, func1_case_2, False)
check_func(func1, func1_case_3, True)
check_func(func1, func1_case_4, True)
check_func(func1, func1_case_5, True)
def func2_case_1() -> None:
func2(None) # success
def func2_case_2() -> None:
func2(True) # success
def func2_case_3() -> None:
func2(1.0) # fail
def func2_case_4() -> None:
func2("hello") # success
def func2_case_5() -> None:
func2(["hello"]) # fail
def func2_case_6() -> None:
func2({1, 2, 3}) # fail
def func2_case_7() -> None:
func2([1, 2, 3]) # success
def func2_case_8() -> None:
func2([1.0, 2.0, 3.0]) # fail
def func2_case_9() -> None:
func2({1: "hello"}) # fail
def func2_case_10() -> None:
T = tp.TypeVar("T", int, str)
class A(tp.Sequence[T]):
def __init__(self, value: T):
self._a: T = value
@tp.overload # noqa: F811
def __getitem__(self, i: int) -> T: # noqa: F811
pass
@tp.overload # noqa: F811
def __getitem__(self, s: slice) -> tp.Sequence[T]: # noqa: F811
pass
def __getitem__(self, i): # noqa: F811
return self._a
def __len__(self) -> int:
pass
func2(A[str]("hello")) # fail
def func2_case_11() -> None:
T = tp.TypeVar("T", bool, str)
class A(tp.Sequence[T]):
def __init__(self, value: T):
self._a: T = value
@tp.overload # noqa: F811
def __getitem__(self, i: int) -> T: # noqa: F811
pass
@tp.overload # noqa: F811
def __getitem__(self, s: slice) -> tp.Sequence[T]: # noqa: F811
pass
def __getitem__(self, i): # noqa: F811
return self._a
def __len__(self) -> int:
pass
func2(A[bool](True)) # success
def test_func2() -> None:
check_annotations(func2)
check_func(func2, func2_case_1, True)
check_func(func2, func2_case_2, True)
check_func(func2, func2_case_3, False)
check_func(func2, func2_case_4, True)
check_func(func2, func2_case_5, False)
check_func(func2, func2_case_6, False)
check_func</