├── .gitignore ├── .readthedocs.yaml ├── README.md └── docs ├── requirements.txt └── source ├── LICENSE.rst ├── _static ├── basicconfig.png ├── logger.png ├── logger_no_filter.png └── loggers.png ├── _templates └── footer.html ├── book ├── 01_pytest_basics │ ├── basic_examples.rst │ ├── basics.rst │ ├── conftest.rst │ ├── extra.rst │ ├── fixture.rst │ ├── fixture_builtin.rst │ ├── fixture_custom.rst │ ├── fixture_features.rst │ ├── fixture_parametrization.rst │ ├── further_reading.rst │ ├── index.rst │ ├── parametrized_tests.rst │ ├── pytest_pyneng.rst │ ├── running_tests.rst │ ├── test_hints.rst │ └── test_network.rst ├── 02_type_annotations │ ├── errors_and_solutions.rst │ ├── examples.rst │ ├── further_reading.rst │ ├── index.rst │ ├── mypy.rst │ ├── pydantic.rst │ └── syntax.rst ├── 03_code_formatters │ ├── black.rst │ ├── further_reading.rst │ └── index.rst ├── 04_click │ ├── arguments.rst │ ├── basics.rst │ ├── complex.rst │ ├── further_reading.rst │ ├── index.rst │ ├── options.rst │ ├── parameters.rst │ ├── setuptools.rst │ └── utilities.rst ├── 05_logging │ ├── basics.rst │ ├── components.rst │ ├── filter.rst │ ├── further_reading.rst │ ├── hierarchy.rst │ ├── index.rst │ ├── nullhandler.rst │ └── rich_handler.rst ├── 06_functions │ ├── first_class_functions.rst │ ├── further_reading.rst │ ├── index.rst │ ├── namespace.rst │ ├── terms.rst │ └── useful_builtin_functions.rst ├── 07_closure │ ├── closure.rst │ ├── further_reading.rst │ └── index.rst ├── 08_decorators │ ├── basics.rst │ ├── builtin.rst │ ├── class_as_decorator.rst │ ├── class_decorator.rst │ ├── examples.rst │ ├── further_reading.rst │ ├── index.rst │ ├── modules_examples.rst │ ├── stack.rst │ ├── with_args.rst │ ├── with_args_examples.rst │ └── wrapt.rst ├── 09_oop_basics │ ├── class_example.rst │ ├── class_namespace.rst │ ├── class_variables.rst │ ├── create_class.rst │ ├── create_methods.rst │ ├── index.rst │ ├── init_method.rst │ ├── parameter_self.rst │ └── terminology.rst ├── 10_oop_special_methods │ ├── add_method.rst │ ├── context_manager.rst │ ├── index.rst │ ├── iterable_iterator.rst │ ├── protocols.rst │ ├── sequence_protocol.rst │ ├── str_repr.rst │ └── underscore_names.rst ├── 11_oop_method_decorators │ ├── base_ssh.py │ ├── classmethod.rst │ ├── further_reading.rst │ ├── index.rst │ ├── property.rst │ ├── property_variations.rst │ ├── staticmethod.rst │ └── templates │ │ ├── index │ │ ├── sh_cdp_n_det.template │ │ ├── sh_clock.template │ │ ├── sh_ip_int_br.template │ │ └── sh_ip_route_ospf.template ├── 12_oop_inheritance │ ├── abc.rst │ ├── abc_example.py │ ├── abc_standard_library.rst │ ├── base_ssh.py │ ├── builtin.rst │ ├── exceptions.rst │ ├── further_reading.rst │ ├── index.rst │ ├── inheritance.rst │ ├── mixin.rst │ ├── mixin_example.py │ ├── multiple_inheritance.rst │ ├── netmiko_codebase_examples.py │ └── terms.rst ├── 13_data_classes │ ├── dataclasses.rst │ ├── further_reading.rst │ ├── index.rst │ └── ipaddress_dataclass.py ├── 14_generators │ ├── further_reading.rst │ ├── generator.rst │ ├── generator_example.rst │ ├── genexpr.rst │ ├── index.rst │ └── syntax.rst ├── 15_itertools │ ├── chain.rst │ ├── compress.rst │ ├── count.rst │ ├── cycle.rst │ ├── dropwhile_takewhile.rst │ ├── further_reading.rst │ ├── groupby.rst │ ├── index.rst │ ├── islice.rst │ ├── itertools.rst │ ├── mit_collapse.rst │ ├── mit_grouping.rst │ ├── mit_look.rst │ ├── mit_summarizing.rst │ ├── mit_windowed.rst │ ├── more_itertools.rst │ ├── repeat.rst │ ├── tee.rst │ └── zip_longest.rst ├── 16_asyncio_basics │ ├── asyncio_vs_threads.rst │ ├── basics.rst │ ├── coroutine_mechanics.rst │ ├── draft_notes.rst │ ├── further_reading.rst │ ├── index.rst │ ├── old_coroutine.rst │ ├── run_awaitables.rst │ ├── terms.rst │ └── useful_functions.rst ├── 17_async_libraries │ ├── api_ssh_read_stream.rst │ ├── api_ssh_write_stream.rst │ ├── async_timeout.rst │ ├── asyncssh.rst │ ├── further_reading.rst │ ├── index.rst │ ├── netdev.rst │ └── scrapli.rst ├── 18_using_asyncio │ ├── async_class.rst │ ├── event_loop.rst │ ├── index.rst │ ├── task_vs_future.rst │ └── to_thread.rst ├── Part_I.rst ├── Part_II.rst ├── Part_III.rst ├── Part_IV.rst ├── Part_V.rst ├── Part_VI.rst └── additional_info │ ├── collections │ ├── chainmap.rst │ ├── counter.rst │ ├── defaultdict.rst │ ├── deque.rst │ ├── index.rst │ ├── namedtuple.rst │ ├── ordereddict.rst │ ├── time_complexity.rst │ └── userlist_userdict_userstr.rst │ ├── index.rst │ ├── memory_test.rst │ ├── oop_extra │ ├── descriptor.rst │ ├── index.rst │ ├── metaclass.rst │ └── slots.rst │ └── pdb │ ├── basics.rst │ ├── further_reading.rst │ └── index.rst ├── conf.py ├── download.rst ├── exercises ├── exercises_intro.rst └── old_exercises │ ├── 01_exercises.rst │ ├── 02_exercises.rst │ ├── 03_exercises.rst │ ├── 04_exercises.rst │ ├── 05_exercises.rst │ ├── 06_exercises.rst │ ├── 07_exercises.rst │ ├── 08_exercises.rst │ ├── 09_exercises.rst │ ├── 10_exercises.rst │ ├── 11_exercises.rst │ ├── 12_exercises.rst │ ├── 13_exercises.rst │ ├── 14_exercises.rst │ ├── 15_exercises.rst │ ├── 16_exercises.rst │ └── 17_exercises.rst ├── index.rst └── resources.rst /.gitignore: -------------------------------------------------------------------------------- 1 | # Node rules: 2 | ## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 3 | .grunt 4 | 5 | ## Dependency directory 6 | ## Commenting this out is preferred by some people, see 7 | ## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git 8 | node_modules 9 | 10 | # Book build output 11 | docs/build/ 12 | 13 | # eBook build output 14 | *.epub 15 | *.mobi 16 | pyneng.pdf 17 | 18 | # OS generated files # 19 | # ###################### 20 | .DS_Store 21 | .DS_Store? 22 | ._* 23 | .Spotlight-V100 24 | .Trashes 25 | ehthumbs.db 26 | Thumbs.db 27 | 28 | # Vim undo files. Python# 29 | # ####################### 30 | *.un~ 31 | *.pyc 32 | *.swp 33 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the version of Python and other tools you might need 9 | build: 10 | os: ubuntu-20.04 11 | tools: 12 | python: "3.9" 13 | # You can also specify other tool versions: 14 | # nodejs: "16" 15 | # rust: "1.55" 16 | # golang: "1.17" 17 | 18 | # Build documentation in the docs/ directory with Sphinx 19 | sphinx: 20 | configuration: docs/source/conf.py 21 | 22 | # If using Sphinx, optionally build your docs in additional formats such as PDF 23 | formats: 24 | - pdf 25 | - epub 26 | 27 | # Optionally declare the Python requirements required to build your docs 28 | python: 29 | install: 30 | - requirements: docs/requirements.txt 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Advanced Python for network engineers 2 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | furo==2022.6.21 2 | sphinx-inline-tabs 3 | sphinx_copybutton==0.5.0 4 | -------------------------------------------------------------------------------- /docs/source/_static/basicconfig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natenka/advpyneng-book/6a66da6307a01add9c1f81d0bb97ea18f40b6754/docs/source/_static/basicconfig.png -------------------------------------------------------------------------------- /docs/source/_static/logger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natenka/advpyneng-book/6a66da6307a01add9c1f81d0bb97ea18f40b6754/docs/source/_static/logger.png -------------------------------------------------------------------------------- /docs/source/_static/logger_no_filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natenka/advpyneng-book/6a66da6307a01add9c1f81d0bb97ea18f40b6754/docs/source/_static/logger_no_filter.png -------------------------------------------------------------------------------- /docs/source/_static/loggers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natenka/advpyneng-book/6a66da6307a01add9c1f81d0bb97ea18f40b6754/docs/source/_static/loggers.png -------------------------------------------------------------------------------- /docs/source/_templates/footer.html: -------------------------------------------------------------------------------- 1 | 59 | 60 | -------------------------------------------------------------------------------- /docs/source/book/01_pytest_basics/conftest.rst: -------------------------------------------------------------------------------- 1 | conftest 2 | -------- 3 | 4 | conftest.py это файл в котором хранятся fixture для разных тестов. 5 | Этих файлов может быть много, например, может быть такая структура: 6 | 7 | :: 8 | 9 | ├── conftest.py 10 | ├── pytest.ini 11 | └── tests 12 | ├── conftest.py 13 | ├── helper_test_functions.py 14 | ├── network 15 | │   ├── conftest.py 16 | │   └── test_11_network_fixture_params.py 17 | └── unit 18 | ├── conftest.py 19 | ├── test_01_check_ip.py 20 | ├── test_02_send_command.py 21 | ├── test_03_check_password.py 22 | ├── test_04_get_interfaces.py 23 | ├── test_05_class_topology.py 24 | └── test_06_class_ipv4network.py 25 | 26 | 27 | Conftest.py также добавляет каталог в котором он находится в sys.path. 28 | Поэтому часто можно встретить пустые файлы conftest.py. 29 | Например в этом случае conftest.py в текущем каталоге может быть пустым, 30 | но он нужен чтобы тесты из каталога tests могли импортировать функции из файлов 31 | в текущем каталоге: 32 | 33 | :: 34 | 35 | $ tree 36 | . 37 | ├── check_ip_functions.py 38 | ├── check_password_function_input.py 39 | ├── check_password_function.py 40 | ├── class_ipv4_network.py 41 | ├── common_functions.py 42 | ├── conftest.py 43 | └── tests 44 | ├── conftest.py 45 | ├── test_check_ip_function.py 46 | ├── test_check_password_input.py 47 | ├── test_check_password_parametrize.py 48 | ├── test_check_password.py 49 | └── test_ipv4_network.py 50 | 51 | -------------------------------------------------------------------------------- /docs/source/book/01_pytest_basics/extra.rst: -------------------------------------------------------------------------------- 1 | Дополнительные возможности 2 | -------------------------- 3 | 4 | pytest.raises 5 | ~~~~~~~~~~~~~ 6 | 7 | .. code:: python 8 | 9 | import ipaddress 10 | import pytest 11 | 12 | 13 | def check_ip(ip): 14 | if type(ip) != str: 15 | raise TypeError("Function only works with strings") 16 | try: 17 | ipaddress.ip_address(ip) 18 | return True 19 | except ValueError: 20 | return False 21 | 22 | 23 | def test_check_ip_raises_1(): 24 | with pytest.raises(TypeError): 25 | check_ip(100) 26 | 27 | 28 | def test_check_ip_raises_3(): 29 | with pytest.raises(TypeError) as error: 30 | check_ip(100) 31 | assert "strings" in str(error.value) 32 | 33 | 34 | def test_check_ip_raises_4(): 35 | with pytest.raises(TypeError, match="st.+ngs"): 36 | check_ip(100) 37 | -------------------------------------------------------------------------------- /docs/source/book/01_pytest_basics/fixture.rst: -------------------------------------------------------------------------------- 1 | Fixture 2 | ------- 3 | 4 | Fixtures это функции, которые выполняют что-то до теста и, при необходимости, после. 5 | 6 | Два самых распространенных применения fixture: 7 | 8 | * для передачи каких-то данных для теста 9 | * setup and teardown 10 | 11 | Fixture scope - контролирует как часто запускается fixture: 12 | 13 | * function (значение по умолчанию) - fixture запускается до и после каждого теста, который использует это fixture 14 | * class 15 | * module - fixture запускается один раз до и после тестов в модуле, который использует это fixture 16 | * package 17 | * session - fixture запускается один раз в начале сессии и в конце 18 | 19 | Запуск "после" актуален только для fixture c yield. 20 | 21 | 22 | Полезные команды для работы с fixture: 23 | 24 | * ``pytest --fixtures`` - показывает все доступные fixture (встроенные, 25 | из плагинов и найденные в тестах и conftest.py). Добавление ``-v`` показывает 26 | в каких файлах находятся fixture и на какой строке определена функция 27 | * ``pytest --setup-show`` - показывает какие fixture запускаются и когда 28 | 29 | 30 | .. toctree:: 31 | :maxdepth: 1 32 | :hidden: 33 | 34 | fixture_custom 35 | fixture_builtin 36 | conftest 37 | fixture_features 38 | fixture_parametrization 39 | -------------------------------------------------------------------------------- /docs/source/book/01_pytest_basics/fixture_builtin.rst: -------------------------------------------------------------------------------- 1 | Встроенные fixture 2 | ------------------ 3 | 4 | * `capsys `__ 5 | * `monkeypatch `__ 6 | * `tmp_path `__ 7 | * `и другие `__ 8 | 9 | capsys 10 | ~~~~~~ 11 | 12 | .. code:: python 13 | 14 | from netmiko import ConnectHandler 15 | from paramiko.ssh_exception import AuthenticationException 16 | 17 | 18 | def send_show_command(device, command): 19 | try: 20 | with ConnectHandler(**device) as ssh: 21 | ssh.enable() 22 | result = ssh.send_command(command) 23 | return result 24 | except AuthenticationException as error: 25 | print(error) 26 | 27 | 28 | def test_function_return_value(capsys, first_router_wrong_pass): 29 | return_value = send_show_command(first_router_wrong_pass, "sh ip int br") 30 | correct_stdout = "Authentication fail" 31 | out, err = capsys.readouterr() 32 | assert out != "", "Сообщение об ошибке не выведено на stdout" 33 | assert correct_stdout in out, "Выведено неправильное сообщение об ошибке" 34 | 35 | 36 | 37 | monkeypatch 38 | ~~~~~~~~~~~ 39 | 40 | .. code:: python 41 | 42 | def check_passwd(username, password, min_length=8): 43 | if len(password) < min_length: 44 | print('Пароль слишком короткий') 45 | return False 46 | elif username in password: 47 | print('Пароль содержит имя пользователя') 48 | return False 49 | else: 50 | print(f'Пароль для пользователя {username} прошел все проверки') 51 | return True 52 | 53 | 54 | def test_password_min_length(monkeypatch): 55 | monkeypatch.setattr('builtins.input', lambda x=None: 'user') 56 | monkeypatch.setattr('getpass.getpass', lambda x=None: '12345') 57 | assert check_passwd(min_length=3) == True 58 | 59 | 60 | @pytest.mark.parametrize( 61 | "username,password,result", 62 | [ 63 | ('user', '12345', True), 64 | ('user', '12345user', False) 65 | ], 66 | ) 67 | def test_check_passwd_function(monkeypatch, username, password, result): 68 | monkeypatch.setattr('builtins.input', lambda x=None: username) 69 | monkeypatch.setattr('getpass.getpass', lambda x=None: password) 70 | assert check_passwd(min_length=3) == result 71 | -------------------------------------------------------------------------------- /docs/source/book/01_pytest_basics/fixture_custom.rst: -------------------------------------------------------------------------------- 1 | Создание fixture 2 | ---------------- 3 | 4 | .. code:: python 5 | 6 | import pytest 7 | 8 | 9 | @pytest.fixture 10 | def topology_with_dupl_links(): 11 | topology = {('R1', 'Eth0/0'): ('SW1', 'Eth0/1'), 12 | ('R2', 'Eth0/0'): ('SW1', 'Eth0/2'), 13 | ('R2', 'Eth0/1'): ('SW2', 'Eth0/11'), 14 | ('R3', 'Eth0/0'): ('SW1', 'Eth0/3'), 15 | ('R3', 'Eth0/1'): ('R4', 'Eth0/0'), 16 | ('R3', 'Eth0/2'): ('R5', 'Eth0/0'), 17 | ('SW1', 'Eth0/1'): ('R1', 'Eth0/0'), 18 | ('SW1', 'Eth0/2'): ('R2', 'Eth0/0'), 19 | ('SW1', 'Eth0/3'): ('R3', 'Eth0/0')} 20 | return topology 21 | 22 | 23 | @pytest.fixture 24 | def normalized_topology_example(): 25 | normalized_topology = {('R1', 'Eth0/0'): ('SW1', 'Eth0/1'), 26 | ('R2', 'Eth0/0'): ('SW1', 'Eth0/2'), 27 | ('R2', 'Eth0/1'): ('SW2', 'Eth0/11'), 28 | ('R3', 'Eth0/0'): ('SW1', 'Eth0/3'), 29 | ('R3', 'Eth0/1'): ('R4', 'Eth0/0'), 30 | ('R3', 'Eth0/2'): ('R5', 'Eth0/0')} 31 | return normalized_topology 32 | 33 | 34 | 35 | .. code:: python 36 | 37 | import yaml 38 | import pytest 39 | from netmiko import ConnectHandler 40 | 41 | 42 | @pytest.fixture(scope='module') 43 | def first_router_from_devices_yaml(): 44 | with open('devices.yaml') as f: 45 | devices = yaml.safe_load(f) 46 | r1 = devices[0] 47 | return r1 48 | 49 | 50 | @pytest.fixture(scope='module') 51 | def first_router_wrong_pass(first_router_from_devices_yaml): 52 | r1 = first_router_from_devices_yaml.copy() 53 | r1['password'] = 'wrong' 54 | return r1 55 | 56 | 57 | @pytest.fixture(scope='module') 58 | def first_router_wrong_ip(first_router_from_devices_yaml): 59 | r1 = first_router_from_devices_yaml.copy() 60 | r1['ip'] = 'unreachable' 61 | return r1 62 | 63 | Fixture r1_test_connection создает подключение netmiko до тестов в одном файле 64 | и закрывает его после: 65 | 66 | .. code:: python 67 | 68 | @pytest.fixture(scope='module') 69 | def r1_test_connection(first_router_from_devices_yaml): 70 | with ConnectHandler(**first_router_from_devices_yaml) as r1: 71 | r1.enable() 72 | yield r1 73 | 74 | 75 | -------------------------------------------------------------------------------- /docs/source/book/01_pytest_basics/fixture_features.rst: -------------------------------------------------------------------------------- 1 | Возможности fixture 2 | ------------------- 3 | 4 | Fixture может смотреть в тест/модуль, который "вызвал" fixture. 5 | 6 | conftest.py 7 | 8 | .. code:: python 9 | 10 | @pytest.fixture(scope="module") 11 | def smtp_connection(request): 12 | server = getattr(request.module, "smtpserver", "smtp.gmail.com") 13 | 14 | smtp_connection = smtplib.SMTP(server, 587, timeout=5) 15 | yield smtp_connection 16 | print("finalizing {} ({})".format(smtp_connection, server)) 17 | smtp_connection.close() 18 | 19 | 20 | test_smtp.py 21 | 22 | .. code:: python 23 | 24 | smtpserver = "mail.python.org" # will be read by smtp fixture 25 | 26 | 27 | def test_showhelo(smtp_connection): 28 | assert 0, smtp_connection.helo() 29 | 30 | 31 | factory as fixture 32 | 33 | .. code:: python 34 | 35 | @pytest.fixture 36 | def make_customer_record(): 37 | def _make_customer_record(name): 38 | return {"name": name, "orders": []} 39 | 40 | return _make_customer_record 41 | 42 | 43 | def test_customer_records(make_customer_record): 44 | customer_1 = make_customer_record("Lisa") 45 | customer_2 = make_customer_record("Mike") 46 | customer_3 = make_customer_record("Meredith") 47 | 48 | -------------------------------------------------------------------------------- /docs/source/book/01_pytest_basics/fixture_parametrization.rst: -------------------------------------------------------------------------------- 1 | Параметризация fixture 2 | ---------------------- 3 | 4 | .. code:: python 5 | 6 | with open("devices.yaml") as f: 7 | devices_params = yaml.safe_load(f) 8 | ip_list = [d["host"] for d in devices_params] 9 | 10 | 11 | @pytest.fixture(params=devices_params, scope="session", ids=ip_list) 12 | def ssh_connection(request): 13 | with ConnectHandler(**request.param) as ssh: 14 | ssh.enable() 15 | yield ssh 16 | 17 | 18 | def test_ospf_enabled(ssh_connection): 19 | output = ssh_connection.send_command("sh ip ospf") 20 | assert "routing process" in output.lower() 21 | 22 | 23 | def test_loopback(ssh_connection): 24 | output = ssh_connection.send_command("sh ip int br | i up +up") 25 | assert "Loopback0" in output 26 | 27 | 28 | Параметризация fixture и теста: 29 | 30 | .. code:: python 31 | 32 | with open("devices.yaml") as f: 33 | devices_params = yaml.safe_load(f) 34 | ip_list = [d["host"] for d in devices_params] 35 | 36 | 37 | @pytest.fixture(params=devices_params, scope="session", ids=ip_list) 38 | def ssh_connection(request): 39 | ssh = ConnectHandler(**request.param) 40 | ssh.enable() 41 | yield ssh 42 | ssh.disconnect() 43 | 44 | 45 | @pytest.mark.parametrize( 46 | "ip", 47 | ["192.168.100.100", "192.168.100.2", "192.168.100.3"], 48 | ids=["ISP1", "ISP2", "FW"], 49 | ) 50 | def test_ping(ssh_connection, ip): 51 | output = ssh_connection.send_command(f"ping {ip}") 52 | assert "success rate is 100 percent" in output.lower() 53 | 54 | -------------------------------------------------------------------------------- /docs/source/book/01_pytest_basics/further_reading.rst: -------------------------------------------------------------------------------- 1 | Дополнительные материалы 2 | ------------------------ 3 | 4 | Документация: 5 | ~~~~~~~~~~~~~ 6 | 7 | - `pytest `__ 8 | - `Good Integration Practices `__ 9 | 10 | Статьи: 11 | ~~~~~~~ 12 | 13 | - `Getting Started With Testing in Python `__ 14 | - `Effective Python Testing With Pytest `__ 15 | - `tox `__ 16 | - `pytest and tox `__ 17 | 18 | Альтернативы pytest 19 | ~~~~~~~~~~~~~~~~~~~ 20 | 21 | * `unittest `__ 22 | * `doctest `__ 23 | * `nose `__ 24 | 25 | Полезные ссылки по сравнению фреймворков: 26 | 27 | * `Test & code podcast: 2: Pytest vs Unittest vs Nose `__ 28 | * `The Cleaning Hand of Pytest `__ 29 | 30 | Тестирование сети 31 | ~~~~~~~~~~~~~~~~~ 32 | 33 | * `Automating "Network Ready for Use" Testing `__ - полезное выступление об использовании pytest для тестирования сети 34 | 35 | Примеры тестов в модулях 36 | ~~~~~~~~~~~~~~~~~~~~~~~~ 37 | 38 | Scrapli: 39 | 40 | * `Тесты scrapli `__. 41 | * Пример `относительно простых тестов scrapli `__ 42 | * `Тесты Response `__ 43 | * `Тесты со skipif `_ 44 | 45 | Netmiko: 46 | 47 | * `Тесты netmiko `__. 48 | * Пример `относительно простых тестов netmiko `__ 49 | 50 | -------------------------------------------------------------------------------- /docs/source/book/01_pytest_basics/index.rst: -------------------------------------------------------------------------------- 1 | .. raw:: latex 2 | 3 | \newpage 4 | 5 | 1. Основы pytest 6 | ================= 7 | 8 | Pytest - фреймворк для тестирования кода 9 | 10 | Тестирование кода позволяет проверить: 11 | 12 | * работает ли код так как нужно 13 | * как ведет себя код в нестандартных ситуациях 14 | * пользовательский интерфейс 15 | * ... 16 | 17 | Уровни тестирования 18 | 19 | * unit - тестирование отдельных функций/классов 20 | * intergration - тестирование взаимодействия разных частей софта друг с другом 21 | * system - тестируется вся система, для web, например, это может быть 22 | тестирование от логина пользователя до выхода 23 | 24 | Альтернативы pytest: 25 | 26 | * `unittest `__ 27 | * `doctest `__ 28 | * `nose `__ 29 | 30 | 31 | Пример тестов с unittest: 32 | 33 | .. code:: python 34 | 35 | import unittest 36 | 37 | class TestStringMethods(unittest.TestCase): 38 | 39 | def test_upper(self): 40 | self.assertEqual('foo'.upper(), 'FOO') 41 | 42 | def test_isupper(self): 43 | self.assertTrue('FOO'.isupper()) 44 | self.assertFalse('Foo'.isupper()) 45 | 46 | def test_split(self): 47 | s = 'hello world' 48 | self.assertEqual(s.split(), ['hello', 'world']) 49 | # check that s.split fails when the separator is not a string 50 | with self.assertRaises(TypeError): 51 | s.split(2) 52 | 53 | 54 | if __name__ == '__main__': 55 | unittest.main() 56 | 57 | 58 | Аналогичный тест с pytest: 59 | 60 | .. code:: python 61 | 62 | def test_upper(): 63 | assert 'foo'.upper() == 'FOO' 64 | 65 | 66 | def test_isupper(): 67 | assert 'FOO'.isupper() == True 68 | assert 'Foo'.isupper() == False 69 | 70 | 71 | def test_split(): 72 | s = 'hello world' 73 | assert s.split() == ['hello', 'world'] 74 | # check that s.split fails when the separator is not a string 75 | with pytest.raises(TypeError): 76 | s.split(2) 77 | 78 | 79 | 80 | .. toctree:: 81 | :maxdepth: 1 82 | :hidden: 83 | 84 | basics 85 | basic_examples 86 | running_tests 87 | parametrized_tests 88 | fixture 89 | extra 90 | test_network 91 | test_hints 92 | further_reading 93 | ../../exercises/01_exercises.rst 94 | -------------------------------------------------------------------------------- /docs/source/book/01_pytest_basics/test_network.rst: -------------------------------------------------------------------------------- 1 | Использование pytest для тестирования сети 2 | ------------------------------------------ 3 | 4 | conftest.py 5 | 6 | .. code:: python 7 | 8 | import pytest 9 | from netmiko import Netmiko 10 | import yaml 11 | 12 | 13 | with open("devices.yaml") as f: 14 | DEVICES = yaml.safe_load(f) 15 | DEVICES_IP = [dev["host"] for dev in DEVICES] 16 | 17 | 18 | def get_host(device): 19 | return device["host"] 20 | 21 | 22 | @pytest.fixture(params=DEVICES, ids=get_host, scope="session") 23 | def ssh_connection(request): 24 | with Netmiko(**request.param) as ssh: 25 | ssh.enable() 26 | yield ssh 27 | 28 | Тесты: 29 | 30 | .. code:: python 31 | 32 | import pytest 33 | 34 | 35 | @pytest.mark.parametrize( 36 | "command,check_output", 37 | [ 38 | ("sh ip ospf", "routing process"), 39 | ("sh ip int br", "up"), 40 | ], 41 | ) 42 | def test_ospf(ssh_connection, command, check_output): 43 | output = ssh_connection.send_command(command) 44 | assert check_output in output.lower() 45 | 46 | 47 | @pytest.mark.parametrize( 48 | "ip_address", ["192.168.100.1", "192.168.100.100"], ids=["ISP1", "ISP2"] 49 | ) 50 | def test_ping(ssh_connection, ip_address): 51 | output = ssh_connection.send_command(f"ping {ip_address}") 52 | assert "success rate is 100" in output.lower() 53 | 54 | 55 | def test_loopback(ssh_connection): 56 | loopback = "Loopback0" 57 | output = ssh_connection.send_command("sh ip int br") 58 | assert loopback in output 59 | 60 | 61 | def test_intf(ssh_connection): 62 | output = ssh_connection.send_command("sh ip int br | i up +up") 63 | assert output.count("up") >= 4 64 | 65 | -------------------------------------------------------------------------------- /docs/source/book/02_type_annotations/examples.rst: -------------------------------------------------------------------------------- 1 | Примеры использования аннотации типов 2 | ------------------------------------- 3 | 4 | ignore-missing-imports 5 | ~~~~~~~~~~~~~~~~~~~~~~ 6 | 7 | :: 8 | 9 | mypy --ignore-missing-imports example_04_class_basessh.py 10 | 11 | Отложенное вычисление аннотаций типов 12 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 13 | 14 | .. note:: 15 | 16 | Работает в Python 3.7+ с импортом __future__ 17 | 18 | Использование имени класса в аннотации внутри этого же класса: 19 | 20 | .. code:: python 21 | 22 | from __future__ import annotations 23 | import ipaddress 24 | 25 | 26 | class IPAddress: 27 | def __init__(self, ip: str) -> None: 28 | self.ip = ip 29 | 30 | def __add__(self, other: int) -> IPAddress: 31 | ip_int = int(ipaddress.ip_address(self.ip)) 32 | sum_ip_str = str(ipaddress.ip_address(ip_int + other)) 33 | return IPAddress(sum_ip_str) 34 | 35 | Опциональный аргумент 36 | ~~~~~~~~~~~~~~~~~~~~~ 37 | 38 | .. code:: python 39 | 40 | from typing import Union, List, Optional 41 | 42 | 43 | def check_passwd(username: str, password: str, min_length: int = 8, 44 | check_username: bool = True, 45 | forbidden_symbols: Union[List, None] = None) -> bool: 46 | #forbidden_symbols: Optional[List] = None) -> bool: 47 | if len(password) < min_length: 48 | print('Пароль слишком короткий') 49 | return False 50 | elif check_username and username in password: 51 | print('Пароль содержит имя пользователя') 52 | return False 53 | else: 54 | print(f'Пароль для пользователя {username} прошел все проверки') 55 | return True 56 | -------------------------------------------------------------------------------- /docs/source/book/02_type_annotations/further_reading.rst: -------------------------------------------------------------------------------- 1 | Дополнительные материалы 2 | ------------------------ 3 | 4 | * `Шпаргалка по аннотации типов `__ 5 | * `Модуль typing `__ 6 | * `Type hinting in PyCharm `__ 7 | * `PEP 563 -- Postponed Evaluation of Annotations `__ 8 | * `Annotations Best Practices `__ 9 | 10 | Статьи: 11 | 12 | * `the state of type hints in Python `__ 13 | * `Введение в аннотации типов Python `__ 14 | * `Python Type Checking (Guide) `__ 15 | 16 | Видео: 17 | 18 | * `Bernat Gabor - Type hinting (and mypy) - PyCon 2019 `__ 19 | * `Carl Meyer - Type-checked Python in the real world - PyCon 2018 `__ 20 | * `Michael Sullivan - Getting to Three Million Lines of Type-Annotated Python - PyCon 2019 `__ 21 | 22 | Связанные проекты: 23 | 24 | * `pydantic `__ 25 | * `typeshed `__ 26 | * `MonkeyType `__ 27 | -------------------------------------------------------------------------------- /docs/source/book/02_type_annotations/index.rst: -------------------------------------------------------------------------------- 1 | .. raw:: latex 2 | 3 | \newpage 4 | 5 | 2. Основы аннотации типов 6 | ========================== 7 | 8 | Аннотация типов - это дополнительное описание в классах, функциях, переменных, 9 | которое указывает какой тип данных должен быть в этом месте. 10 | 11 | 12 | При этом указанные типы не проверяются и не форсируются самим Python. То есть, при выполнении кода, 13 | несоответствие реального типа данных тому, что написано в аннотациях, не вызывает ошибок или 14 | предупреждений. Для проверки типов данных используются отдельные модули, например, mypy. 15 | Mypy выполняет статический анализ кода - проверяет соответствие типов данных без выполнения кода. 16 | 17 | .. note:: 18 | 19 | Аннотация типов добавлялась постепенно в Python 3.x. Начиная с версии Python 3.0 была доступна 20 | аннотация функций, а в Python 3.6 была добавлена аннотация для переменных. 21 | 22 | 23 | Преимущества: 24 | 25 | * при создании объектов сразу описаны типы данных 26 | * можно проверять правильность указанных типов с помощью отдельных модулей 27 | * IDE могут делать подсказки, указывать на ошибки на основании аннотации типов 28 | 29 | Нюансы: 30 | 31 | * как и с тестами, надо потратить время на написание аннотаций (хотя есть софт, который может в этом помочь) 32 | * на данный момент, надо делать довольно большое количество импортов 33 | * желательно использовать Python 3.6+ чтобы были доступны все возможности, в идеале, последнюю версию Python. 34 | 35 | .. toctree:: 36 | :maxdepth: 1 37 | :hidden: 38 | 39 | syntax 40 | mypy 41 | examples 42 | errors_and_solutions 43 | further_reading 44 | ../../exercises/02_exercises.rst 45 | -------------------------------------------------------------------------------- /docs/source/book/02_type_annotations/mypy.rst: -------------------------------------------------------------------------------- 1 | Основы mypy 2 | ----------- 3 | 4 | Так как сам Python никак не проверяет указанные типы данных, надо использовать какой-то дополнительный 5 | модуль для проверки. Один из таких модулей - mypy. 6 | 7 | Mypy выполняет статический анализ кода - проверяет соответствие типов данных без выполнения кода. 8 | 9 | .. note:: 10 | 11 | Mypy не единственный проект такого типа. Другие модули: pyre, pytype. 12 | 13 | 14 | Пример запуска скрипта с помощью mypy: 15 | 16 | :: 17 | 18 | $ mypy example_01_function_check_ip.py 19 | example_01_function_check_ip.py:13: error: Argument 1 to "check_ip" has incompatible type "int"; expected "str" 20 | Found 1 error in 1 file (checked 1 source file) 21 | 22 | 23 | Писать аннотацию для переменных нужно далеко не всегда. Как правило, того типа который 24 | "угадал" mypy достаточно. Например, в этом случае mypy понимает, что ip это строка: 25 | 26 | .. code:: python 27 | 28 | ip = '10.1.1.1' 29 | 30 | И не будет выводить никаких ошибок: 31 | 32 | :: 33 | 34 | $ mypy example_03_variable.py 35 | 36 | Success: no issues found in 1 source file 37 | 38 | Однако, если переменная может быть и строкой и числом: 39 | 40 | .. code:: python 41 | 42 | ip = '10.1.1.1' 43 | ip = 3 44 | 45 | mypy посчитает это ошибкой: 46 | 47 | :: 48 | 49 | example_03_variable.py:2: error: Incompatible types in assignment (expression has type "int", variable has type "str") 50 | Found 1 error in 1 file (checked 1 source file) 51 | 52 | В таком случае надо явно указать, что переменная может быть числом или строкой: 53 | 54 | .. code:: python 55 | 56 | from typing import Union 57 | 58 | ip: Union[int, str] = '10.1.1.1' 59 | ip = 3 60 | 61 | 62 | 63 | 64 | strict 65 | ~~~~~~ 66 | 67 | 68 | .. code:: python 69 | 70 | def func1(a: str, b: str) -> str: 71 | return a + b 72 | 73 | def func2(c, d): 74 | result = func1(4, 6) 75 | return c + d 76 | 77 | По умолчанию, mypy игнорирует функции без аннотации типов: 78 | 79 | :: 80 | 81 | $ mypy testme.py 82 | Success: no issues found in 1 source file 83 | 84 | С параметром strict mypy проверяет эти функции и их работу с другими объектами: 85 | 86 | :: 87 | 88 | $ mypy testme.py --strict 89 | testme.py:4: error: Function is missing a type annotation 90 | testme.py:5: error: Argument 1 to "func1" has incompatible type "int"; expected "str" 91 | testme.py:5: error: Argument 2 to "func1" has incompatible type "int"; expected "str" 92 | Found 3 errors in 1 file (checked 1 source file) 93 | 94 | 95 | reveal 96 | ~~~~~~ 97 | 98 | reveal_type 99 | 100 | 101 | reveal_locals: 102 | 103 | .. code:: python 104 | 105 | def check_passwd(username: str, password: str, 106 | min_length: int = 8, check_username: bool = True) -> bool: 107 | reveal_locals() 108 | if len(password) < min_length: 109 | print('Пароль слишком короткий') 110 | return False 111 | elif check_username and username in password: 112 | print('Пароль содержит имя пользователя') 113 | return False 114 | else: 115 | print(f'Пароль для пользователя {username} прошел все проверки') 116 | return True 117 | 118 | :: 119 | 120 | example_02_function_check_passwd.py:4: note: Revealed local types are: 121 | example_02_function_check_passwd.py:4: note: check_username: builtins.bool 122 | example_02_function_check_passwd.py:4: note: min_length: builtins.int 123 | example_02_function_check_passwd.py:4: note: password: builtins.str 124 | example_02_function_check_passwd.py:4: note: username: builtins.str 125 | 126 | -------------------------------------------------------------------------------- /docs/source/book/02_type_annotations/pydantic.rst: -------------------------------------------------------------------------------- 1 | pydantic 2 | -------- 3 | 4 | pydantic использует аннотацию типов для проверки данных. 5 | 6 | Пример создания dataclass: 7 | 8 | .. code:: python 9 | 10 | In [9]: from dataclasses import dataclass 11 | 12 | In [10]: @dataclass 13 | ...: class Book: 14 | ...: title: str 15 | ...: price: int 16 | ...: 17 | 18 | В этом случае, аннотация переменных используется для создания атрибутов, но при этом 19 | тип данных не проверяется и все эти варианты отработают: 20 | 21 | .. code:: python 22 | 23 | In [11]: book = Book('Good Omens', price=35) 24 | 25 | In [12]: book = Book('Good Omens', price='35') 26 | 27 | In [13]: book = Book('Good Omens', price='a') 28 | 29 | При использовании декоратора dataclass из модуля pydantic, типы проверяются: 30 | 31 | .. code:: python 32 | 33 | In [14]: from pydantic.dataclasses import dataclass 34 | 35 | In [15]: @dataclass 36 | ...: class Book: 37 | ...: title: str 38 | ...: price: int 39 | ...: 40 | 41 | In [16]: book = Book('Good Omens', price=35) 42 | 43 | In [17]: book = Book('Good Omens', price='35') 44 | 45 | In [18]: book = Book('Good Omens', price='a') 46 | --------------------------------------------------------------------------- 47 | ValidationError Traceback (most recent call last) 48 | in 49 | ----> 1 book = Book('Good Omens', price='a') 50 | 51 | in __init__(self, title, price) 52 | 53 | ~/venv/pyneng-py3-7/lib/python3.7/site-packages/pydantic/dataclasses.cpython-37m-i386-linux-gnu.so in pydantic.dataclasses._process_class._pydantic_post_init() 54 | 55 | ValidationError: 1 validation error for Book 56 | price 57 | value is not a valid integer (type=type_error.integer) 58 | 59 | In [19]: book = Book('Good Omens', price='35') 60 | 61 | In [20]: book.price 62 | Out[20]: 35 63 | 64 | 65 | `Примеры использования pydantic `__ 66 | -------------------------------------------------------------------------------- /docs/source/book/03_code_formatters/black.rst: -------------------------------------------------------------------------------- 1 | Автоматическое форматирование кода с Black 2 | ------------------------------------------ 3 | 4 | Black - модуль для автоматического форматирования кода Python. 5 | 6 | Установка 7 | 8 | ``` 9 | pip install black 10 | ``` 11 | 12 | Использование: 13 | 14 | ``` 15 | black somefile_or_dir 16 | ``` 17 | 18 | Правила 19 | ~~~~~~~~~ 20 | 21 | .. code:: python 22 | 23 | # in: 24 | 25 | j = [1, 26 | 2, 27 | 3 28 | ] 29 | 30 | # out: 31 | 32 | j = [1, 2, 3] 33 | 34 | 35 | 36 | .. code:: python 37 | 38 | # in: 39 | 40 | ImportantClass.important_method(exc, limit, lookup_lines, capture_locals, extra_argument) 41 | 42 | # out: 43 | 44 | ImportantClass.important_method( 45 | exc, limit, lookup_lines, capture_locals, extra_argument 46 | ) 47 | 48 | 49 | Волшебная запятая 50 | ~~~~~~~~~~~~~~~~~ 51 | 52 | Исключение кода из форматирования 53 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 54 | -------------------------------------------------------------------------------- /docs/source/book/03_code_formatters/further_reading.rst: -------------------------------------------------------------------------------- 1 | Дополнительные материалы 2 | ------------------------ 3 | 4 | Документация: 5 | 6 | * `Black `__ 7 | 8 | 9 | Линтеры и другие утилиты для анализа кода 10 | 11 | * [pycodestyle](https://github.com/PyCQA/pycodestyle) 12 | * [pylint](https://github.com/PyCQA/pylint) 13 | * [isort - сортировка строк import](https://github.com/PyCQA/isort) 14 | * [Pyflakes](https://github.com/PyCQA/pyflakes) 15 | * [Flake8](https://github.com/PyCQA/flake8) - объединяет pycodestyle, pyflakes, mccabe 16 | 17 | Модули для автоматического форматирования кода: 18 | 19 | * [black](https://github.com/psf/black) 20 | * [blue](https://github.com/grantjenks/blue) 21 | * [yapf](https://github.com/google/yapf) 22 | * [autopep8](https://github.com/hhatto/autopep8) 23 | 24 | -------------------------------------------------------------------------------- /docs/source/book/03_code_formatters/index.rst: -------------------------------------------------------------------------------- 1 | .. raw:: latex 2 | 3 | \newpage 4 | 5 | 3. Code formatters 6 | =================== 7 | 8 | 9 | .. toctree:: 10 | :maxdepth: 1 11 | :hidden: 12 | 13 | black 14 | further_reading 15 | -------------------------------------------------------------------------------- /docs/source/book/04_click/arguments.rst: -------------------------------------------------------------------------------- 1 | Аргументы 2 | --------- 3 | 4 | Аргументы создаются с помощью декоратора ``@click.argument``: 5 | 6 | .. code:: python 7 | 8 | @click.argument(name, type=None, required=True, default=None, nargs=None) 9 | 10 | 11 | В самом простом случае, достаточно указать только имя аргумента: 12 | 13 | .. code:: python 14 | 15 | @click.command() 16 | @click.argument("name") 17 | def cli(name): 18 | """Print NAME""" 19 | print(name) 20 | 21 | Так будет выглядеть help скрипта: 22 | 23 | :: 24 | 25 | $ python basics_01.py --help 26 | Usage: basics_01.py [OPTIONS] NAME 27 | 28 | Print NAME 29 | 30 | Options: 31 | --help Show this message and exit. 32 | 33 | 34 | И так вызов: 35 | 36 | :: 37 | 38 | $ python basics_01.py R2D2 39 | R2D2 40 | 41 | Переменное количество аргументов 42 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 43 | 44 | Параметр nargs позволяет контролировать какое количество аргументов можно передать. 45 | По умолчанию, значение 1. Значение -1 - это специальное значение обозначающее, что аргументов может быть 46 | сколько угодно. 47 | 48 | Пример использования nargs 49 | 50 | .. code:: python 51 | 52 | import subprocess 53 | import click 54 | 55 | 56 | def ping_ip(ip_address): 57 | reply = subprocess.run( 58 | f"ping -c 2 -n {ip_address}", 59 | shell=True, 60 | stdout=subprocess.PIPE, 61 | stderr=subprocess.PIPE, 62 | encoding="utf-8", 63 | ) 64 | if reply.returncode == 0: 65 | return True 66 | else: 67 | return False 68 | 69 | 70 | @click.command() 71 | @click.argument("ip_addresses", nargs=-1, required=True) 72 | def cli(ip_addresses): 73 | """ 74 | Ping IP_ADDRESSES 75 | """ 76 | for ip in ip_addresses: 77 | if ping_ip(ip): 78 | print(f"IP-адрес {ip:15} пингуется") 79 | else: 80 | print(f"IP-адрес {ip:15} не пингуется") 81 | 82 | 83 | if __name__ == "__main__": 84 | cli() 85 | 86 | -------------------------------------------------------------------------------- /docs/source/book/04_click/further_reading.rst: -------------------------------------------------------------------------------- 1 | Дополнительные материалы 2 | ------------------------ 3 | 4 | Документация: 5 | 6 | * `Click `__ 7 | * `Примеры click `__ 8 | 9 | 10 | Полезные статьи: 11 | 12 | * `Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click `__ 13 | 14 | Видео: 15 | 16 | * `Building Command Line Applications with Click `__ 17 | * `Sebastian Vetter - Click: A Pleasure To Write, A Pleasure To Use - PyCon 2016 `__ 18 | 19 | Другие модули для создания cli: 20 | 21 | * `argparse `__, `Argparse Tutorial `__ 22 | * `typer `__ 23 | * `docopt `__ 24 | * `Python Fire `__ 25 | 26 | 27 | setuptools: 28 | 29 | * `Click Setuptools Integration `__ 30 | * `How to package and deploy CLI applications with Python PyPA setuptools build `__ 31 | -------------------------------------------------------------------------------- /docs/source/book/04_click/index.rst: -------------------------------------------------------------------------------- 1 | .. raw:: latex 2 | 3 | \newpage 4 | 5 | 4. Модуль click 6 | ================ 7 | 8 | Click это модуль, который позволяет создавать интерфейс командной строки. 9 | Примеры того, что позволяет делать модуль: 10 | 11 | * создавать аргументы и опции, с которыми может вызываться скрипт 12 | * указывать типы аргументов, значения по умолчанию 13 | * отображать сообщения с подсказками по использованию скрипта 14 | 15 | Click не единственный модуль для обработки аргументов командной строки. 16 | 17 | .. toctree:: 18 | :maxdepth: 1 19 | :hidden: 20 | 21 | basics 22 | setuptools 23 | parameters 24 | arguments 25 | options 26 | utilities 27 | complex 28 | further_reading 29 | ../../exercises/04_exercises.rst 30 | -------------------------------------------------------------------------------- /docs/source/book/04_click/options.rst: -------------------------------------------------------------------------------- 1 | Опции 2 | ----- 3 | 4 | Класс click.Option: 5 | 6 | .. code:: python 7 | 8 | class click.Option( 9 | param_decls=None, 10 | show_default=False, 11 | prompt=False, 12 | confirmation_prompt=False, 13 | hide_input=False, 14 | is_flag=None, 15 | flag_value=None, 16 | multiple=False, 17 | count=False, 18 | allow_from_autoenv=True, 19 | type=None, 20 | help=None, 21 | hidden=False, 22 | show_choices=True, 23 | show_envvar=False, 24 | **attrs 25 | ) 26 | 27 | 28 | 29 | Имена опций: 30 | 31 | * ``@click.option("-f", "--foo-bar")``, имя параметра функции будет ``"foo_bar"`` 32 | * ``@click.option("-x")``, имя ``"x"`` 33 | * ``@click.option("-f", "--filename", "dest")``, имя ``"dest"`` 34 | * ``@click.option("--CamelCase")``, имя ``"camelcase"`` 35 | * ``@click.option("-f", "-fb")``, имя ``"f"`` 36 | * ``@click.option("--f", "--foo-bar")``, имя ``"f"`` 37 | * ``@click.option("---f")``, имя ``"_f"`` 38 | 39 | 40 | Запрос значения у пользователя 41 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 42 | 43 | Запрос выполняется только если в опции не указано значение: 44 | 45 | .. code:: python 46 | 47 | @click.command() 48 | @click.option("--username", "-u", prompt=True) 49 | @click.option("--password", "-p", prompt=True, hide_input=True) 50 | @click.option("--secret", "-s", prompt=True, hide_input=True) 51 | def cli(username, password, secret): 52 | pass 53 | 54 | Параметр hide_input позволяет скрывать вводимое значение. 55 | 56 | Переменные окружения 57 | ~~~~~~~~~~~~~~~~~~~~ 58 | 59 | .. code:: python 60 | 61 | @click.command() 62 | @click.option("--username", "-u", envvar="NET_USER") 63 | @click.option("--password", "-p", envvar="NET_PASSWORD") 64 | @click.option("--secret", "-s", envvar="NET_SECRET") 65 | def cli(username, password, secret): 66 | pass 67 | 68 | Переменные окружения и prompt - сначала проверяется переменная окружения, потом, если ее нет, 69 | запрос у пользователя: 70 | 71 | .. code:: python 72 | 73 | @click.command() 74 | @click.option("--username", "-u", envvar="NET_USER", prompt=True) 75 | @click.option("--password", "-p", envvar="NET_PASSWORD", prompt=True, hide_input=True) 76 | @click.option("--secret", "-s", envvar="NET_SECRET", prompt=True, hide_input=True) 77 | def cli(username, password, secret): 78 | pass 79 | 80 | Флаг 81 | ~~~~ 82 | 83 | .. code:: python 84 | 85 | @click.option("--show-all", "-a", is_flag=True, help="show db content") 86 | 87 | 88 | Подтверждение ввода 89 | ~~~~~~~~~~~~~~~~~~~ 90 | 91 | confirmation_prompt может пригодится при запросе пароля или других критичных данных. 92 | В этом случае пароль запрашивается повторно автоматически и два введенных значения сравниваются: 93 | 94 | .. code:: python 95 | 96 | @click.command() 97 | @click.option("--username", "-u", prompt=True) 98 | @click.option("--password", "-p", prompt=True, hide_input=True, confirmation_prompt=True) 99 | def cli(username, password): 100 | print(username, password) 101 | 102 | 103 | .. note:: 104 | 105 | Так как это распространенная задача, ввод пароля таким образом можно заменить 106 | декоратором click.password_option. 107 | 108 | -------------------------------------------------------------------------------- /docs/source/book/05_logging/filter.rst: -------------------------------------------------------------------------------- 1 | Фильтры 2 | -------- 3 | 4 | Фильтры можно применять к logger или к handler 5 | 6 | .. note:: 7 | 8 | `LogRecord attributes `__ 9 | 10 | .. code:: python 11 | 12 | class LevelFilter(logging.Filter): 13 | def __init__(self, level): 14 | self.level = level 15 | 16 | def filter(self, record): 17 | return record.levelno == self.level 18 | 19 | 20 | log = logging.getLogger(__name__) 21 | log.setLevel(logging.DEBUG) 22 | log.addFilter(LevelFilter(logging.DEBUG)) 23 | 24 | logfile = logging.FileHandler("logfile3.log") 25 | logfile.setLevel(logging.DEBUG) 26 | formatter = logging.Formatter("{asctime} - {name} - {levelname} - {message}", style="{") 27 | logfile.setFormatter(formatter) 28 | 29 | log.addHandler(logfile) 30 | 31 | 32 | Handler Filter 33 | ~~~~~~~~~~~~~~ 34 | 35 | .. code:: python 36 | 37 | class LevelFilter(logging.Filter): 38 | def __init__(self, level): 39 | self.level = level 40 | 41 | def filter(self, record): 42 | return record.levelno == self.level 43 | 44 | 45 | log = logging.getLogger(__name__) 46 | log.setLevel(logging.DEBUG) 47 | 48 | logfile = logging.FileHandler("logfile3.log") 49 | logfile.setLevel(logging.DEBUG) 50 | logfile.addFilter(LevelFilter(logging.DEBUG)) 51 | logfile.addFilter(MessageFilter("test")) 52 | 53 | formatter = logging.Formatter("{asctime} - {name} - {levelname} - {message}", style="{") 54 | logfile.setFormatter(formatter) 55 | 56 | log.addHandler(logfile) 57 | 58 | 59 | Использование фильтра для добавления информации в запись 60 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 61 | 62 | .. note:: 63 | 64 | `Using Filters to impart contextual information `__ 65 | 66 | 67 | .. code:: python 68 | 69 | class AddIPFilter(logging.Filter): 70 | def filter(self, record): 71 | match = re.search(r"\d+\.\d+\.\d+\.\d+", record.msg) 72 | if match: 73 | record.ip = match.group() 74 | else: 75 | record.ip = None 76 | return True 77 | 78 | -------------------------------------------------------------------------------- /docs/source/book/05_logging/further_reading.rst: -------------------------------------------------------------------------------- 1 | Дополнительные материалы 2 | ------------------------ 3 | 4 | Документация: 5 | ~~~~~~~~~~~~~ 6 | 7 | - `Logging tutorial `__ 8 | - `Logging 9 | Cookbook `__ 10 | - Модуль `python-json-logger `__ 11 | 12 | Альтернативы модулю logging 13 | 14 | * `loguru `__ 15 | 16 | 17 | Статьи: 18 | ~~~~~~~ 19 | 20 | Основы: 21 | 22 | - `How To Use Logging in Python 23 | 3 `__ 24 | - `Python Logging 25 | Basics `__ 26 | 27 | Более подробные: 28 | 29 | - `A guide to logging in 30 | Python `__ 31 | - `Good logging practice in 32 | Python `__ 33 | - `Python Logging 34 | Tutorial `__ 35 | - `Understanding Python's logging 36 | module `__ 37 | - `Пример YAML файла с конфигурацией 38 | logging `__ 39 | 40 | -------------------------------------------------------------------------------- /docs/source/book/05_logging/hierarchy.rst: -------------------------------------------------------------------------------- 1 | Иерархия логеров 2 | ---------------- 3 | 4 | .. image:: https://raw.githubusercontent.com/natenka/advpyneng-book/master/docs/source/_static/loggers.png 5 | :align: center 6 | 7 | В модуле logging есть иерархия логеров. Самый главный в иерархии root. 8 | Остальные под ним, часто, на одном уровне. В одном модуле может быть много логеров с разной иерархией, 9 | например, в paramiko есть логер paramiko.transport, он по иерархии ниже paramiko. 10 | 11 | Такой конфиг настраивает logger root: 12 | 13 | .. code:: python 14 | 15 | logging.basicConfig( 16 | level=logging.INFO 17 | ) 18 | 19 | Так как он самый главный, его настройка приводит к тому, что все остальные логеры тоже начинают отображать информацию. 20 | При такой настройке настраивается конкретный logger и никакие другие логи уже не сыпятся. 21 | 22 | .. code:: python 23 | 24 | logger = logging.getLogger(__name__) 25 | logger.setLevel(logging.DEBUG) 26 | 27 | console = logging.StreamHandler() 28 | console.setLevel(logging.DEBUG) 29 | formatter = logging.Formatter( 30 | "%(threadName)s %(name)s %(levelname)s %(asctime)s: %(message)s", datefmt="%H:%M:%S" 31 | ) 32 | console.setFormatter(formatter) 33 | logger.addHandler(console) 34 | 35 | 36 | Хотя тут тоже можно использовать root, для этого надо первую строку написать так 37 | 38 | .. code:: python 39 | 40 | logger = logging.getLogger() 41 | 42 | С базовым конфигом надо отключить или хотя бы приглушить на какой-то уровень существующие логеры других модулей: 43 | 44 | .. code:: python 45 | 46 | logging.getLogger("paramiko").setLevel(logging.WARNING) 47 | 48 | logging.basicConfig( 49 | level=logging.INFO 50 | ) 51 | 52 | Со своим логером (не root) - надо наоборот добавить строк чтобы включить logger netmiko/paramiko. 53 | Для этого надо добавлять такие строки (учитывая что предыдущие все есть с handler, formatter и тп 54 | 55 | .. code:: python 56 | 57 | netmiko_log = logging.getLogger("netmiko") 58 | netmiko_log.setLevel(logging.DEBUG) 59 | netmiko_log.addHandler(console) 60 | 61 | -------------------------------------------------------------------------------- /docs/source/book/05_logging/index.rst: -------------------------------------------------------------------------------- 1 | .. raw:: latex 2 | 3 | \newpage 4 | 5 | 5. Модуль logging 6 | ================== 7 | 8 | +--------------+---------------------------------------------------------------------+ 9 | | Уровень | Когда используется | 10 | +==============+=====================================================================+ 11 | | ``DEBUG`` | Подробная информация для диагностики проблемы. | 12 | +--------------+---------------------------------------------------------------------+ 13 | | ``INFO`` | Подтверждение, что все работает как должно. | 14 | +--------------+---------------------------------------------------------------------+ 15 | | ``WARNING`` | Случилось что-то неожиданное, но программа все еще работает. | 16 | | | Также может использоваться для индикации о будущих проблемах. | 17 | +--------------+---------------------------------------------------------------------+ 18 | | ``ERROR`` | Возникла ошибка и из-за нее не получилось выполнить часть задач. | 19 | +--------------+---------------------------------------------------------------------+ 20 | | ``CRITICAL`` | Серьезная ошибка из-за которой программа не может подолжить работу | 21 | +--------------+---------------------------------------------------------------------+ 22 | 23 | 24 | .. toctree:: 25 | :maxdepth: 1 26 | :hidden: 27 | 28 | basics 29 | components 30 | hierarchy 31 | rich_handler 32 | filter 33 | nullhandler 34 | further_reading 35 | ../../exercises/05_exercises.rst 36 | 37 | 38 | -------------------------------------------------------------------------------- /docs/source/book/05_logging/nullhandler.rst: -------------------------------------------------------------------------------- 1 | NullHandler 2 | ----------- 3 | 4 | .. code:: python 5 | 6 | import logging 7 | 8 | 9 | log = logging.getLogger(__name__) 10 | log.addHandler(logging.NullHandler()) 11 | 12 | 13 | def send_show(device_dict, command): 14 | ip = device_dict["host"] 15 | log.info(f"===> Connection: {ip}") 16 | 17 | try: 18 | with ConnectHandler(**device_dict) as ssh: 19 | ssh.enable() 20 | result = ssh.send_command(command) 21 | log.debug(f"<=== Received: {ip}") 22 | log.debug(f"Получен вывод команды {command}\n\n{result}") 23 | return result 24 | except SSHException as error: 25 | log.error(f"Ошибка {error} на {ip}") 26 | -------------------------------------------------------------------------------- /docs/source/book/05_logging/rich_handler.rst: -------------------------------------------------------------------------------- 1 | Rich Handler 2 | ------------ 3 | 4 | 5 | Настройка с logging.basicConfig: 6 | 7 | .. code:: python 8 | 9 | import logging 10 | from rich.logging import RichHandler 11 | 12 | FORMAT = "%(message)s" 13 | logging.basicConfig( 14 | level="NOTSET", format=FORMAT, datefmt="[%X]", handlers=[RichHandler()] 15 | ) 16 | 17 | log = logging.getLogger("rich") 18 | log.info("Hello, World!") 19 | 20 | 21 | Настройка с Handler 22 | 23 | .. code:: python 24 | 25 | from concurrent.futures import ThreadPoolExecutor 26 | from pprint import pprint 27 | from itertools import repeat 28 | import logging 29 | 30 | import yaml 31 | from scrapli import Scrapli 32 | from scrapli.exceptions import ScrapliException 33 | from rich.logging import RichHandler 34 | 35 | 36 | logging.getLogger("scrapli").setLevel(logging.WARNING) 37 | log = logging.getLogger(__name__) 38 | log.setLevel(logging.DEBUG) 39 | 40 | ### stderr 41 | console = RichHandler(level=logging.DEBUG) 42 | formatter = logging.Formatter( 43 | "{name} - {message}", datefmt="%X", style="{" 44 | ) 45 | console.setFormatter(formatter) 46 | log.addHandler(console) 47 | 48 | ### File 49 | logfile = logging.FileHandler("logfile3.log") 50 | logfile.setLevel(logging.DEBUG) 51 | formatter = logging.Formatter("{asctime} - {name} - {levelname} - {message}", style="{") 52 | logfile.setFormatter(formatter) 53 | 54 | log.addHandler(logfile) 55 | -------------------------------------------------------------------------------- /docs/source/book/06_functions/first_class_functions.rst: -------------------------------------------------------------------------------- 1 | Функции - объекты первого класса 2 | --------------------------------- 3 | 4 | В Python все функции являются объектами первого класса. Это означает, что Python поддерживает: 5 | 6 | * передачу функций в качестве аргументов другим функциям 7 | * возвращение функции как результата других функций 8 | * присваивание функций переменным 9 | * сохранение функций в структурах данных 10 | 11 | 12 | Например, первый пункт "передача функций в качестве аргументов другим функциям" встречается 13 | при использовании встроенной функции map. Тут map применяет функцию str к каждому элементу списка: 14 | 15 | .. code:: python 16 | 17 | In [1]: list(map(str, [1, 2, 3])) 18 | Out[1]: ['1', '2', '3'] 19 | 20 | Функция delay ожидает как аргумент задержку в секундах, другую функцию и ее аргументы: 21 | 22 | .. code:: python 23 | 24 | import time 25 | 26 | def delay(seconds, func, *args, **kwargs): 27 | print(f'Delay {seconds} seconds...') 28 | time.sleep(seconds) 29 | return func(*args, **kwargs) 30 | 31 | 32 | 33 | Теперь функции delay можно передавать любую другую функцию как аргумент и 34 | она выполнится после указанной паузы: 35 | 36 | .. code:: python 37 | 38 | def summ(a, b): 39 | return a + b 40 | 41 | 42 | In [5]: delay(5, summ, 1, 4) 43 | Delay 5 seconds... 44 | Out[5]: 5 45 | 46 | Сохранение функций в структурах данных: 47 | 48 | .. code:: python 49 | 50 | In [8]: functions = [delay, summ] 51 | 52 | In [9]: functions 53 | Out[9]: 54 | [, 55 | ] 56 | 57 | Присваивание функций переменным: 58 | 59 | .. code:: python 60 | 61 | In [10]: delay_execution = delay 62 | 63 | In [11]: delay_execution 64 | Out[11]: 65 | 66 | In [12]: delay_execution(5, summ, 1, 4) 67 | Delay 5 seconds... 68 | Out[12]: 5 69 | 70 | -------------------------------------------------------------------------------- /docs/source/book/06_functions/further_reading.rst: -------------------------------------------------------------------------------- 1 | Дополнительные материалы 2 | ------------------------ 3 | 4 | * `Introspection of Function Parameters `__ 5 | * `inspect `__ 6 | * `rich inspect `__ 7 | * `Introspection in Python `__ 8 | -------------------------------------------------------------------------------- /docs/source/book/06_functions/index.rst: -------------------------------------------------------------------------------- 1 | .. raw:: latex 2 | 3 | \newpage 4 | 5 | 6. Функции 6 | ========== 7 | 8 | 9 | .. toctree:: 10 | :maxdepth: 1 11 | :hidden: 12 | 13 | terms 14 | namespace 15 | first_class_functions 16 | useful_builtin_functions 17 | further_reading 18 | -------------------------------------------------------------------------------- /docs/source/book/06_functions/namespace.rst: -------------------------------------------------------------------------------- 1 | .. meta:: 2 | :http-equiv=Content-Type: text/html; charset=utf-8 3 | 4 | Пространства имен. Области видимости 5 | ------------------------------------ 6 | 7 | Область видимости определяет где переменная доступна. Область видимость переменной 8 | зависит от того, где переменная создана. 9 | 10 | Чаще всего, речь будет о двух областях видимости: 11 | 12 | * глобальной - переменные, которые определены вне функции 13 | * локальной - переменные, которые определены внутри функции 14 | 15 | При использовании имен переменных в программе, Python каждый раз ищет, 16 | создает или изменяет эти имена в соответствующем пространстве имен. 17 | Пространство имен, которое доступно в каждый момент, зависит от области, 18 | в которой находится код. 19 | 20 | Поиск переменных 21 | ~~~~~~~~~~~~~~~~~ 22 | 23 | При поиске переменных, Python использует правило LEGB. Например, если 24 | внутри функции выполняется обращение к имени переменной, Python ищет переменную 25 | в таком порядке по областям видимости (до первого совпадения): 26 | 27 | L (local) - в локальной (внутри функции) 28 | E (enclosing) - в локальной области объемлющих функций (это те функции, внутри которых находится наша функция) 29 | G (global) - в глобальной (в скрипте) 30 | B (built-in) - во встроенной (зарезервированные значения Python) 31 | 32 | 33 | .. image:: https://raw.githubusercontent.com/natenka/pyneng-book/master/images/09_function_legb_enclosing.png 34 | :align: center 35 | :class: only-light 36 | 37 | .. only:: html 38 | 39 | .. image:: https://raw.githubusercontent.com/natenka/pyneng-book/master/images/09_function_legb_enclosing_dark.png 40 | :align: center 41 | :class: only-dark 42 | 43 | Локальные и глобальные переменные 44 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 45 | 46 | Локальные переменные: 47 | 48 | * переменные, которые определены внутри функции 49 | * эти переменные становятся недоступными после выхода из функции 50 | 51 | Глобальные переменные: 52 | 53 | * переменные, которые определены вне функции 54 | * эти переменные 'глобальны' только в пределах модуля, чтобы они были доступны 55 | в другом модуле, их надо импортировать 56 | 57 | .. image:: https://raw.githubusercontent.com/natenka/pyneng-book/master/images/09_function_local_global.png 58 | :align: center 59 | :class: only-light 60 | 61 | .. only:: html 62 | 63 | .. image:: https://raw.githubusercontent.com/natenka/pyneng-book/master/images/09_function_local_global_dark.png 64 | :align: center 65 | :class: only-dark 66 | 67 | Пример локальной intf_config: 68 | 69 | .. code:: python 70 | 71 | In [1]: def configure_intf(intf_name, ip, mask): 72 | ...: intf_config = f'interface {intf_name}\nip address {ip} {mask}' 73 | ...: return intf_config 74 | ...: 75 | 76 | In [2]: intf_config 77 | --------------------------------------------------------------------------- 78 | NameError Traceback (most recent call last) 79 | in 80 | ----> 1 intf_config 81 | 82 | NameError: name 'intf_config' is not defined 83 | 84 | 85 | Обратите внимание, что переменная intf_config недоступна за пределами функции. 86 | Для того чтобы получить результат функции, надо вызвать функцию и присвоить результат в переменную: 87 | 88 | .. code:: python 89 | 90 | In [3]: result = configure_intf('F0/0', '10.1.1.1', '255.255.255.0') 91 | 92 | In [4]: result 93 | Out[4]: 'interface F0/0\nip address 10.1.1.1 255.255.255.0' 94 | -------------------------------------------------------------------------------- /docs/source/book/07_closure/further_reading.rst: -------------------------------------------------------------------------------- 1 | Дополнительные материалы 2 | ------------------------ 3 | 4 | * `<>`__ 5 | * `<>`__ 6 | -------------------------------------------------------------------------------- /docs/source/book/07_closure/index.rst: -------------------------------------------------------------------------------- 1 | .. raw:: latex 2 | 3 | \newpage 4 | 5 | 7. Closure 6 | ========== 7 | 8 | 9 | .. toctree:: 10 | :maxdepth: 1 11 | :hidden: 12 | 13 | closure 14 | further_reading 15 | ../../exercises/07_exercises.rst 16 | -------------------------------------------------------------------------------- /docs/source/book/08_decorators/class_as_decorator.rst: -------------------------------------------------------------------------------- 1 | Класс как декоратор 2 | ------------------- 3 | 4 | Декоратор без аргументов 5 | ~~~~~~~~~~~~~~~~~~~~~~~~ 6 | 7 | .. code:: python 8 | 9 | class verbose: 10 | def __init__(self, func): 11 | print(f"Декорация функции {func.__name__}") 12 | self.func = func 13 | 14 | def __call__(self, *args, **kwargs): 15 | print(f"У функции {self.func.__name__} такие аргументы") 16 | print(f"{args=}") 17 | print(f"{kwargs=}") 18 | result = self.func(*args, **kwargs) 19 | return result 20 | 21 | 22 | @verbose 23 | def upper(string): 24 | return string.upper() 25 | 26 | 27 | Декоратор с аргументами 28 | ~~~~~~~~~~~~~~~~~~~~~~~ 29 | 30 | .. code:: python 31 | 32 | from functools import update_wrapper 33 | 34 | class verbose: 35 | def __init__(self, message): 36 | print("вызов verbose") 37 | self.msg = message 38 | 39 | def __call__(self, func): 40 | print(f"Декорация функции {func.__name__}") 41 | def inner(*args, **kwargs): 42 | print(f"У функции {func.__name__} такие аргументы") 43 | print(f"{args=}") 44 | print(f"{kwargs=}") 45 | result = func(*args, **kwargs) 46 | 47 | update_wrapper(wrapper=inner, wrapped=func) 48 | return inner 49 | 50 | 51 | @verbose("hello") 52 | def upper(string): 53 | return string.upper() 54 | -------------------------------------------------------------------------------- /docs/source/book/08_decorators/class_decorator.rst: -------------------------------------------------------------------------------- 1 | Декоратор класса 2 | ---------------- 3 | 4 | Регистрация классов 5 | ~~~~~~~~~~~~~~~~~~~ 6 | 7 | .. code:: python 8 | 9 | 10 | CLASS_MAPPER_BASE = {} 11 | 12 | def register_class(cls): 13 | CLASS_MAPPER_BASE[cls.device_type] = cls.__name__ 14 | return cls 15 | 16 | 17 | @register_class 18 | class CiscoSSH: 19 | device_type = 'cisco_ios' 20 | def __init__(self, ip, username, password): 21 | pass 22 | 23 | 24 | @register_class 25 | class JuniperSSH: 26 | device_type = 'juniper' 27 | def __init__(self, ip, username, password): 28 | pass 29 | 30 | 31 | Декоратор добавляет метод pprint 32 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 33 | 34 | .. code:: python 35 | 36 | from rich import print as rprint 37 | 38 | 39 | def add_pprint(cls): 40 | def pprint(self, methods=False): 41 | methods_class_attrs = vars(type(self)) 42 | methods = { 43 | name: method 44 | for name, method in methods_class_attrs.items() 45 | if not name.startswith("__") and callable(method) 46 | } 47 | self_attrs = vars(self) 48 | rprint(self_attrs) 49 | if methods: 50 | rprint(methods) 51 | 52 | cls.pprint = pprint 53 | return cls 54 | 55 | 56 | .. code:: python 57 | 58 | @add_pprint 59 | class IPv4Address: 60 | def __init__(self, ip): 61 | self.ip = ip 62 | self._int_ip = int(ip_address(ip)) 63 | 64 | def as_int(self): 65 | return self._int_ip 66 | 67 | 68 | In [15]: ip1 = IPv4Address("10.1.1.1") 69 | 70 | In [16]: ip1.pprint() 71 | {'ip': '10.1.1.1', '_int_ip': 167837953} 72 | { 73 | 'as_int': , 74 | 'pprint': .pprint at 0xb4235388> 75 | } 76 | 77 | In [17]: ip1.pprint(methods=True) 78 | {'ip': '10.1.1.1', '_int_ip': 167837953} 79 | { 80 | 'as_int': , 81 | 'pprint': .pprint at 0xb4235388> 82 | } 83 | 84 | 85 | Применение декоратора ко всем методам класса 86 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 87 | 88 | .. code:: python 89 | 90 | def verbose(func): 91 | @wraps(func) 92 | def inner(*args, **kwargs): 93 | print(f"Вызываю {func.__name__}") 94 | print("Аргументы", args[1:], kwargs) 95 | return func(*args, **kwargs) 96 | return inner 97 | 98 | 99 | def verbose_methods(cls): 100 | methods = { 101 | name: method 102 | for name, method in vars(cls).items() 103 | if not name.startswith("__") and callable(method) 104 | } 105 | for name, method in methods.items(): 106 | setattr(cls, name, verbose(method)) 107 | return cls 108 | 109 | -------------------------------------------------------------------------------- /docs/source/book/08_decorators/examples.rst: -------------------------------------------------------------------------------- 1 | Примеры декораторов 2 | ------------------- 3 | 4 | Декоратор отображает: имя функции и значение аргументов: 5 | 6 | .. code:: python 7 | 8 | def debugger_with_args(func): 9 | @wraps(func) 10 | def wrapper(*args, **kwargs): 11 | print(f'Вызываю функцию {func.__name__} с args {args} и kwargs {kwargs}') 12 | return func(*args, **kwargs) 13 | return wrapper 14 | 15 | 16 | @debugger_with_args 17 | def func(a, b, verbose=True): 18 | return a, b, verbose 19 | 20 | 21 | In [3]: func(1, 'a', verbose=False) 22 | Вызываю функцию func с args (1, 'a') и kwargs {'verbose': False} 23 | Out[3]: (1, 'a', False) 24 | 25 | Декоратор проверяет что все аргументы функции - строки: 26 | 27 | .. code:: python 28 | 29 | def all_args_str(func): 30 | @wraps(func) 31 | def wrapper(*args): 32 | if not all(isinstance(arg, str) for arg in args): 33 | raise ValueError('Все аргументы должны быть строками') 34 | return func(*args) 35 | return wrapper 36 | 37 | 38 | @all_args_str 39 | def to_upper(*args): 40 | result = [s.upper() for s in args] 41 | return result 42 | 43 | 44 | In [6]: to_upper('a', 'b') 45 | Out[6]: ['A', 'B'] 46 | 47 | In [7]: to_upper(1, 'b') 48 | --------------------------------------------------------------------------- 49 | ValueError Traceback (most recent call last) 50 | in 51 | ----> 1 to_upper(1, 'b') 52 | 53 | in wrapper(*args) 54 | 3 def wrapper(*args): 55 | 4 if not all(isinstance(arg, str) for arg in args): 56 | ----> 5 raise ValueError('Все аргументы должны быть строками') 57 | 6 return func(*args) 58 | 7 return wrapper 59 | 60 | ValueError: Все аргументы должны быть строками 61 | 62 | -------------------------------------------------------------------------------- /docs/source/book/08_decorators/further_reading.rst: -------------------------------------------------------------------------------- 1 | Дополнительные материалы 2 | ------------------------ 3 | 4 | Примеры декораторов 5 | 6 | * `scrapli ChannelTimeout `__ 7 | * `click `__ 8 | * `backoff `__ 9 | * `functools.lru_cache `__ 10 | * `dataclasses.dataclass `__ 11 | 12 | Ссылки 13 | 14 | * `Примеры и идеи декораторов `__ 15 | * `Primer on Python Decorators `__ 16 | -------------------------------------------------------------------------------- /docs/source/book/08_decorators/index.rst: -------------------------------------------------------------------------------- 1 | .. raw:: latex 2 | 3 | \newpage 4 | 5 | 8. Декораторы 6 | ============= 7 | 8 | Декоратор в Python это функция, которая используется для изменения 9 | функции, метода или класса. 10 | Декораторы используются для добавления какого-то функционала к функциям/классам. 11 | 12 | .. note:: 13 | 14 | Более полное определение декоратора: декоратор это вызываемый объект, 15 | который используется для изменения других вызываемых объектов. 16 | 17 | .. toctree:: 18 | :maxdepth: 1 19 | :hidden: 20 | 21 | basics 22 | examples 23 | modules_examples 24 | stack 25 | with_args 26 | with_args_examples 27 | builtin 28 | class_decorator 29 | class_as_decorator 30 | further_reading 31 | ../../exercises/08_exercises.rst 32 | -------------------------------------------------------------------------------- /docs/source/book/08_decorators/modules_examples.rst: -------------------------------------------------------------------------------- 1 | Примеры модулей которые используют декораторы 2 | -------------------------------------------- 3 | 4 | 5 | pytest 6 | ~~~~~~ 7 | 8 | .. code:: python 9 | 10 | @pytest.fixture(scope='module') 11 | def first_router_from_devices_yaml(): 12 | with open('devices.yaml') as f: 13 | devices = yaml.safe_load(f) 14 | r1 = devices[0] 15 | return r1 16 | 17 | 18 | click 19 | ~~~~~ 20 | 21 | .. code:: python 22 | 23 | @click.command() 24 | @click.option("--username", "-u", prompt=True) 25 | @click.option("--password", "-p", prompt=True, hide_input=True, confirmation_prompt=True) 26 | def cli(username, password): 27 | pass 28 | 29 | 30 | flask 31 | ~~~~~ 32 | 33 | .. code:: python 34 | 35 | @main.route('/') 36 | def index(): 37 | pass 38 | 39 | 40 | @main.route('/labs', methods=['GET', 'POST']) 41 | def labs(): 42 | pass 43 | 44 | 45 | @main.route('/stats') 46 | def stats(): 47 | pass 48 | 49 | 50 | backoff 51 | ~~~~~~~~ 52 | 53 | .. code:: python 54 | 55 | @backoff.on_exception( 56 | backoff.expo, requests.exceptions.RequestException 57 | ) 58 | def get_url(url): 59 | return requests.get(url) 60 | 61 | 62 | 63 | dataclasses.dataclass 64 | ~~~~~~~~~~~~~~~~~~~~~~ 65 | 66 | .. code:: python 67 | 68 | @dataclass 69 | class IPAddress: 70 | ip: str 71 | mask: int 72 | 73 | 74 | In [12]: ip1 = IPAddress('10.1.1.1', 28) 75 | 76 | In [13]: ip1 77 | Out[13]: IPAddress(ip='10.1.1.1', mask=28) 78 | 79 | -------------------------------------------------------------------------------- /docs/source/book/08_decorators/stack.rst: -------------------------------------------------------------------------------- 1 | Стек декораторов 2 | ---------------- 3 | 4 | К функции может применяться несколько декораторов. Порядок применения 5 | декораторов будет зависеть от того в каком порядке они записаны: 6 | 7 | .. code:: python 8 | 9 | def stars(func): 10 | @wraps(func) 11 | def wrapper(*args, **kwargs): 12 | print('*'*30) 13 | return func(*args, **kwargs) 14 | return wrapper 15 | 16 | 17 | def lines(func): 18 | @wraps(func) 19 | def wrapper(*args, **kwargs): 20 | print('-'*30) 21 | return func(*args, **kwargs) 22 | return wrapper 23 | 24 | 25 | def equals(func): 26 | @wraps(func) 27 | def wrapper(*args, **kwargs): 28 | print('='*30) 29 | return func(*args, **kwargs) 30 | return wrapper 31 | 32 | 33 | @stars 34 | @lines 35 | @equals 36 | def func(a, b): 37 | return a + b 38 | 39 | 40 | In [23]: func(4,5) 41 | ****************************** 42 | ------------------------------ 43 | ============================== 44 | Out[23]: 9 45 | 46 | In [24]: def func(a, b): 47 | ...: return a + b 48 | ...: func = stars(lines(equals(func))) 49 | 50 | In [30]: func(4,5) 51 | ****************************** 52 | ------------------------------ 53 | ============================== 54 | 55 | -------------------------------------------------------------------------------- /docs/source/book/08_decorators/wrapt.rst: -------------------------------------------------------------------------------- 1 | Модуль wrapt 2 | ------------ 3 | 4 | https://github.com/GrahamDumpleton/wrapt 5 | -------------------------------------------------------------------------------- /docs/source/book/09_oop_basics/class_example.rst: -------------------------------------------------------------------------------- 1 | Пример класса 2 | ~~~~~~~~~~~~~ 3 | 4 | Пример класса, который описывает сеть: 5 | 6 | .. code:: python 7 | 8 | class Network: 9 | def __init__(self, network): 10 | self.network = network 11 | self.hosts = tuple(str(ip) for ip in ipaddress.ip_network(network).hosts()) 12 | self.allocated = [] 13 | 14 | def allocate(self, ip): 15 | if ip in self.hosts: 16 | if ip not in self.allocated: 17 | self.allocated.append(ip) 18 | else: 19 | raise ValueError(f"IP-адрес {ip} уже находится в allocated") 20 | else: 21 | raise ValueError(f"IP-адрес {ip} не входит в сеть {self.network}") 22 | 23 | Использование класса: 24 | 25 | .. code:: python 26 | 27 | In [2]: net1 = Network("10.1.1.0/29") 28 | 29 | In [3]: net1.allocate("10.1.1.1") 30 | 31 | In [4]: net1.allocate("10.1.1.2") 32 | 33 | In [5]: net1.allocated 34 | Out[5]: ['10.1.1.1', '10.1.1.2'] 35 | 36 | In [6]: net1.allocate("10.1.1.100") 37 | --------------------------------------------------------------------------- 38 | ValueError Traceback (most recent call last) 39 | in 40 | ----> 1 net1.allocate("10.1.1.100") 41 | 42 | in allocate(self, ip) 43 | 12 raise ValueError(f"IP-адрес {ip} уже находится в allocated") 44 | 13 else: 45 | ---> 14 raise ValueError(f"IP-адрес {ip} не входит в сеть {self.network}") 46 | 15 47 | 48 | ValueError: IP-адрес 10.1.1.100 не входит в сеть 10.1.1.0/29 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /docs/source/book/09_oop_basics/class_namespace.rst: -------------------------------------------------------------------------------- 1 | Область видимости 2 | ~~~~~~~~~~~~~~~~~ 3 | 4 | У каждого метода в классе своя локальная область видимости. Это значит, 5 | что один метод класса не видит переменные другого метода класса. Для 6 | того чтобы переменные были доступны, надо присваивать их экземпляру 7 | через ``self.name``. 8 | По сути метод - это функция привязанная к объекту. Поэтому все 9 | нюансы, которые касаются функций, относятся и к методам. 10 | 11 | Переменные экземпляра доступны в другом методе, потому что каждому 12 | методу первым аргументом передается сам экзепляр. В примере ниже, в 13 | методе ``__init__`` переменные hostname и model присваиваются 14 | экземпляру, а затем в info используются, за счет того, что экземпляр 15 | передается первым аргументом: 16 | 17 | .. code:: python 18 | 19 | class Switch: 20 | def __init__(self, hostname, model): 21 | self.hostname = hostname 22 | self.model = model 23 | 24 | def info(self): 25 | print('Hostname: {}\nModel: {}'.format(self.hostname, self.model)) 26 | 27 | 28 | -------------------------------------------------------------------------------- /docs/source/book/09_oop_basics/class_variables.rst: -------------------------------------------------------------------------------- 1 | Переменные класса 2 | ~~~~~~~~~~~~~~~~~ 3 | 4 | 5 | Помимо переменных экземпляра, существуют также переменные класса. Они 6 | создаются, при указании переменных внутри самого класса, не метода: 7 | 8 | .. code:: python 9 | 10 | class Network: 11 | all_allocated_ip = [] 12 | 13 | def __init__(self, network): 14 | self.network = network 15 | self.hosts = tuple( 16 | str(ip) for ip in ipaddress.ip_network(network).hosts() 17 | ) 18 | self.allocated = [] 19 | 20 | def allocate(self, ip): 21 | if ip in self.hosts: 22 | if ip not in self.allocated: 23 | self.allocated.append(ip) 24 | type(self).all_allocated_ip.append(ip) 25 | else: 26 | raise ValueError(f"IP-адрес {ip} уже находится в allocated") 27 | else: 28 | raise ValueError(f"IP-адрес {ip} не входит в сеть {self.network}") 29 | 30 | К переменным класса можно обращаться по-разному: 31 | 32 | * ``self.all_allocated_ip`` 33 | * ``Network.all_allocated_ip`` 34 | * ``type(self).all_allocated_ip`` 35 | 36 | Вариант ``self.all_allocated_ip`` позволяет обратиться к значению переменной 37 | класса или добавить элемент, если переменная класса изменяемый тип данных. 38 | Минус этого варианта в том, что если в методе написать 39 | ``self.all_allocated_ip = ...``, вместо изменения переменной класса, 40 | будет создана переменная экземпляра. 41 | 42 | Вариант ``Network.all_allocated_ip`` будет работать корректно, но небольшой минус 43 | этого варианта в том, что имя класса прописано вручную. 44 | Вместо него можно использовать третий вариант ``type(self).all_allocated_ip``, 45 | так как ``type(self)`` возвращает класс. 46 | 47 | 48 | 49 | Теперь у класса есть переменная all_allocated_ip в которую записываются 50 | все IP-адреса, которые выделены в сетях: 51 | 52 | .. code:: python 53 | 54 | In [3]: net1 = Network("10.1.1.0/29") 55 | 56 | In [4]: net1.allocate("10.1.1.1") 57 | ...: net1.allocate("10.1.1.2") 58 | ...: net1.allocate("10.1.1.3") 59 | ...: 60 | 61 | In [5]: net1.allocated 62 | Out[5]: ['10.1.1.1', '10.1.1.2', '10.1.1.3'] 63 | 64 | In [6]: net2 = Network("10.2.2.0/29") 65 | 66 | In [7]: net2.allocate("10.2.2.1") 67 | ...: net2.allocate("10.2.2.2") 68 | ...: 69 | 70 | In [9]: net2.allocated 71 | Out[9]: ['10.2.2.1', '10.2.2.2'] 72 | 73 | In [10]: Network.all_allocated_ip 74 | Out[10]: ['10.1.1.1', '10.1.1.2', '10.1.1.3', '10.2.2.1', '10.2.2.2'] 75 | 76 | Переменная доступна не только через класс, но и через экземпляры: 77 | 78 | .. code:: python 79 | 80 | In [40]: Network.all_allocated_ip 81 | Out[40]: ['10.1.1.1', '10.1.1.2', '10.1.1.3', '10.2.2.1', '10.2.2.2'] 82 | 83 | In [41]: net1.all_allocated_ip 84 | Out[41]: ['10.1.1.1', '10.1.1.2', '10.1.1.3', '10.2.2.1', '10.2.2.2'] 85 | 86 | In [42]: net2.all_allocated_ip 87 | Out[42]: ['10.1.1.1', '10.1.1.2', '10.1.1.3', '10.2.2.1', '10.2.2.2'] 88 | 89 | 90 | -------------------------------------------------------------------------------- /docs/source/book/09_oop_basics/create_class.rst: -------------------------------------------------------------------------------- 1 | Создание класса 2 | --------------- 3 | 4 | .. note:: 5 | 6 | Обратите внимание, что тут основы поясняются с учетом того, что у 7 | читающего нет опыта работы с ООП. Некоторые примеры не очень 8 | правильны с точки зрения идеологии Python, но помогают лучше понять 9 | происходящее. В конце даются пояснения как это правильней делать. 10 | 11 | Для создания классов в питоне используется ключевое слово ``class``. 12 | Самый простой класс, который можно создать в Python: 13 | 14 | .. code:: python 15 | 16 | In [1]: class Switch: 17 | ...: pass 18 | ...: 19 | 20 | .. note:: 21 | 22 | Имена классов: в Python принято писать имена классов в формате 23 | CamelCase. 24 | 25 | Для создания экземпляра класса, надо вызвать класс: 26 | 27 | .. code:: python 28 | 29 | In [2]: sw1 = Switch() 30 | 31 | In [3]: print(sw1) 32 | <__main__.Switch object at 0xb44963ac> 33 | 34 | Используя точечную нотацию, можно получать значения переменных 35 | экземпляра, создавать новые переменные и присваивать новое значение 36 | существующим: 37 | 38 | .. code:: python 39 | 40 | In [5]: sw1.hostname = 'sw1' 41 | 42 | In [6]: sw1.model = 'Cisco 3850' 43 | 44 | В другом экземпляре класса Switch, переменные могут быть другие: 45 | 46 | .. code:: python 47 | 48 | In [7]: sw2 = Switch() 49 | 50 | In [8]: sw2.hostname = 'sw2' 51 | 52 | In [9]: sw2.model = 'Cisco 3750' 53 | 54 | Посмотреть значение переменных экземпляра можно используя ту же точечную 55 | нотацию: 56 | 57 | .. code:: python 58 | 59 | In [10]: sw1.model 60 | Out[10]: 'Cisco 3850' 61 | 62 | In [11]: sw2.model 63 | Out[11]: 'Cisco 3750' 64 | 65 | -------------------------------------------------------------------------------- /docs/source/book/09_oop_basics/create_methods.rst: -------------------------------------------------------------------------------- 1 | Создание метода 2 | ~~~~~~~~~~~~~~~ 3 | 4 | Прежде чем мы начнем разбираться с методами класса, посмотрим пример 5 | функции, которая ожидает как аргумент экземпляр класса Switch и выводит 6 | информацию о нем, используя переменные экземпляра hostname и model: 7 | 8 | .. code:: python 9 | 10 | In [1]: class Switch: 11 | ...: pass 12 | ...: 13 | 14 | In [2]: def info(sw_obj): 15 | ...: print('Hostname: {}\nModel: {}'.format(sw_obj.hostname, sw_obj.model)) 16 | ...: 17 | 18 | In [3]: sw1 = Switch() 19 | 20 | In [4]: sw1.hostname = 'sw1' 21 | 22 | In [5]: sw1.model = 'Cisco 3850' 23 | 24 | In [6]: info(sw1) 25 | Hostname: sw1 26 | Model: Cisco 3850 27 | 28 | В функции info параметр ``sw_obj`` ожидает экземпляр класса ``Switch``. 29 | Скорее всего, в этом примере нет ничего нового, ведь аналогичным образом 30 | ранее мы писали функции, которые ожидают строку, как аргумент, а затем 31 | вызывают какие-то методы у этой строки. 32 | 33 | Этот пример поможет разобраться с методом info, который мы добавим в 34 | класс Switch. 35 | 36 | Для добавления метода, необходимо создать функцию внутри класса: 37 | 38 | .. code:: python 39 | 40 | In [15]: class Switch: 41 | ...: def info(self): 42 | ...: print('Hostname: {}\nModel: {}'.format(self.hostname, self.model)) 43 | ...: 44 | 45 | Если присмотреться, метод info выглядит точно так же, как функция info, 46 | только вместо имени sw_obj, используется self. Почему тут используется 47 | странное имя self, мы разберемся позже, а пока посмотрим как вызвать 48 | метод info: 49 | 50 | .. code:: python 51 | 52 | In [16]: sw1 = Switch() 53 | 54 | In [17]: sw1.hostname = 'sw1' 55 | 56 | In [18]: sw1.model = 'Cisco 3850' 57 | 58 | In [19]: sw1.info() 59 | Hostname: sw1 60 | Model: Cisco 3850 61 | 62 | В примере выше сначала создается экземпляр класса Switch, затем в 63 | экземпляр добавляются переменные hostname и model, и только после этого 64 | вызывается метод info. Метод info выводит информацию про коммутатор, 65 | используя значения, которые хранятся в переменных экземпляра. 66 | 67 | Вызов метода отличается, от вызова функции: мы не передаем ссылку на 68 | экземпляр класса Switch. Нам это не нужно, потому что мы вызываем метод 69 | у самого экземпляра. Еще один непонятный момент - зачем же мы тогда 70 | писали self. 71 | 72 | Все дело в том, что Python преобразует такой вызов: 73 | 74 | .. code:: python 75 | 76 | In [39]: sw1.info() 77 | Hostname: sw1 78 | Model: Cisco 3850 79 | 80 | Вот в такой: 81 | 82 | .. code:: python 83 | 84 | In [38]: Switch.info(sw1) 85 | Hostname: sw1 86 | Model: Cisco 3850 87 | 88 | Во втором случае, в параметре self уже больше смысла, он действительно 89 | принимает ссылку на экземпляр и на основании этого выводит информацию. 90 | 91 | С точки зрения использования объектов, удобней вызывать методы используя 92 | первый вариант синтаксиса, поэтому, практически всегда именно он и 93 | используется. 94 | 95 | .. note:: 96 | 97 | При вызове метода экземпляра класса, ссылка на экземпляр передается 98 | первым аргументом. При этом, экземпляр передается неявно, но 99 | параметр надо указывать явно. 100 | 101 | Такое преобразование не является особенностью пользовательских классов и 102 | работает и для встроенных типов данных аналогично. Например, стандартный 103 | способ вызова метода append в списке, выглядит так: 104 | 105 | .. code:: python 106 | 107 | In [4]: a = [1,2,3] 108 | 109 | In [5]: a.append(5) 110 | 111 | In [6]: a 112 | Out[6]: [1, 2, 3, 5] 113 | 114 | При этом, то же самое можно сделать и используя второй вариант, вызова 115 | через класс: 116 | 117 | .. code:: python 118 | 119 | In [7]: a = [1,2,3] 120 | 121 | In [8]: list.append(a, 5) 122 | 123 | In [9]: a 124 | Out[9]: [1, 2, 3, 5] 125 | 126 | -------------------------------------------------------------------------------- /docs/source/book/09_oop_basics/index.rst: -------------------------------------------------------------------------------- 1 | .. raw:: latex 2 | 3 | \newpage 4 | 5 | 9. Основы ООП 6 | ============================ 7 | 8 | .. toctree:: 9 | :maxdepth: 1 10 | :hidden: 11 | 12 | terminology 13 | create_class 14 | create_methods 15 | parameter_self 16 | init_method 17 | class_example 18 | class_namespace 19 | class_variables 20 | -------------------------------------------------------------------------------- /docs/source/book/09_oop_basics/init_method.rst: -------------------------------------------------------------------------------- 1 | Метод ``__init__`` 2 | ~~~~~~~~~~~~~~~~~~ 3 | 4 | Для корректной работы метода info, необходимо чтобы у экземпляра были 5 | переменные hostname и model. Если этих переменных нет, возникнет ошибка: 6 | 7 | .. code:: python 8 | 9 | In [15]: class Switch: 10 | ...: def info(self): 11 | ...: print('Hostname: {}\nModel: {}'.format(self.hostname, self.model)) 12 | ...: 13 | 14 | In [59]: sw2 = Switch() 15 | 16 | In [60]: sw2.info() 17 | --------------------------------------------------------------------------- 18 | AttributeError Traceback (most recent call last) 19 | in () 20 | ----> 1 sw2.info() 21 | 22 | in info(self) 23 | 1 class Switch: 24 | 2 def info(self): 25 | ----> 3 print('Hostname: {}\nModel: {}'.format(self.hostname, self.model)) 26 | 27 | AttributeError: 'Switch' object has no attribute 'hostname' 28 | 29 | Практически всегда, при создании объекта, у него есть какие-то начальные 30 | данные. Например, чтобы создать подключение к оборудование с помощью 31 | netmiko, надо передать параметры подключения. 32 | 33 | В Python эти начальные данные про объект указываются в методе 34 | ``__init__``. Метод ``__init__`` выполняется после того как Python 35 | создал новый экземпляр и, при этом, методу ``__init__`` передаются 36 | аргументы с которыми был создан экземпляр: 37 | 38 | .. code:: python 39 | 40 | In [32]: class Switch: 41 | ...: def __init__(self, hostname, model): 42 | ...: self.hostname = hostname 43 | ...: self.model = model 44 | ...: 45 | ...: def info(self): 46 | ...: print(f'Hostname: {self.hostname}\nModel: {self.model}') 47 | ...: 48 | 49 | Обратите внимание на то, что у каждого экземпляра, который создан из этого класса, 50 | будут созданы переменные: ``self.model`` и ``self.hostname``. 51 | 52 | Теперь, при создании экземпляра класса Switch, обязательно надо указать 53 | hostname и model: 54 | 55 | .. code:: python 56 | 57 | In [33]: sw1 = Switch('sw1', 'Cisco 3850') 58 | 59 | И, соответственно, метод info отрабатывает без ошибок: 60 | 61 | .. code:: python 62 | 63 | In [36]: sw1.info() 64 | Hostname: sw1 65 | Model: Cisco 3850 66 | 67 | .. note:: 68 | 69 | Метод ``__init__`` иногда называют конструктором класса, хотя 70 | технически в Python сначала выполняется метод ``__new__``, а затем 71 | ``__init__``. В большинстве случаев, метод ``__new__`` использовать 72 | не нужно. 73 | 74 | Важной особенностью метода ``__init__`` является то, что он не должен 75 | ничего возвращать. Python сгенерирует исключение, если попытаться это 76 | сделать. 77 | 78 | -------------------------------------------------------------------------------- /docs/source/book/09_oop_basics/parameter_self.rst: -------------------------------------------------------------------------------- 1 | Параметр self 2 | ~~~~~~~~~~~~~ 3 | 4 | Параметр self указывался выше в определении методов, а также при 5 | использовании переменных экземпляра в методе. Параметр self это ссылка 6 | на конкретный экземпляр класса. При этом, само имя self не является 7 | особенным, а лишь договоренностью. Вместо self можно использовать другое 8 | имя, но так делать не стоит. 9 | 10 | Пример с использованием другого имени, вместо self: 11 | 12 | .. code:: python 13 | 14 | In [15]: class Switch: 15 | ...: def info(sw_object): 16 | ...: print(f'Hostname: {sw_object.hostname}\nModel: {sw_object.model}') 17 | ...: 18 | 19 | Работать все будет аналогично: 20 | 21 | .. code:: python 22 | 23 | In [16]: sw1 = Switch() 24 | 25 | In [17]: sw1.hostname = 'sw1' 26 | 27 | In [18]: sw1.model = 'Cisco 3850' 28 | 29 | In [19]: sw1.info() 30 | Hostname: sw1 31 | Model: Cisco 3850 32 | 33 | .. warning:: 34 | 35 | Хотя технически использовать другое имя можно, всегда используйте 36 | self. 37 | 38 | Во всех "обычных" методах класса первым параметром всегда будет self. 39 | Кроме того, создание переменной экземпляра внутри класса также 40 | выполняется через self. 41 | 42 | Пример класса Switch с новым методом generate_interfaces: метод 43 | generate_interfaces должен сгенерировать список с интерфейсами на 44 | основании указанного типа и количества и создать переменную в экземпляре 45 | класса. Для начала, вариант создания обычно переменной внутри метода: 46 | 47 | .. code:: python 48 | 49 | In [5]: class Switch: 50 | ...: def generate_interfaces(self, intf_type, number_of_intf): 51 | ...: interfaces = [f"{intf_type}{number}" for number in range(1, number_of_intf + 1)] 52 | ...: 53 | 54 | В этом случае, в экземплярах класса не будет переменной interfaces: 55 | 56 | .. code:: python 57 | 58 | In [6]: sw1 = Switch() 59 | 60 | In [7]: sw1.generate_interfaces('Fa', 10) 61 | 62 | In [8]: sw1.interfaces 63 | --------------------------------------------------------------------------- 64 | AttributeError Traceback (most recent call last) 65 | in () 66 | ----> 1 sw1.interfaces 67 | 68 | AttributeError: 'Switch' object has no attribute 'interfaces' 69 | 70 | Этой переменной нет, потому что она существует только внутри метода, а 71 | область видимости у метода такая же, как и у функции. Даже другие методы 72 | одного и того же класса, не видят переменные в других методах. 73 | 74 | Чтобы список с интерфейсами был доступен как переменная в экземплярах, 75 | надо присвоить значение в self.interfaces: 76 | 77 | .. code:: python 78 | 79 | In [9]: class Switch: 80 | ...: def info(self): 81 | ...: print(f"Hostname: {self.hostname}\nModel: {self.model}") 82 | ...: 83 | ...: def generate_interfaces(self, intf_type, number_of_intf): 84 | ...: interfaces = [f"{intf_type}{number}" for number in range(1, number_of_intf+1)] 85 | ...: self.interfaces = interfaces 86 | ...: 87 | 88 | Теперь, после вызова метода generate_interfaces, в экземпляре создается 89 | переменная interfaces: 90 | 91 | .. code:: python 92 | 93 | In [10]: sw1 = Switch() 94 | 95 | In [11]: sw1.generate_interfaces('Fa', 10) 96 | 97 | In [12]: sw1.interfaces 98 | Out[12]: ['Fa1', 'Fa2', 'Fa3', 'Fa4', 'Fa5', 'Fa6', 'Fa7', 'Fa8', 'Fa9', 'Fa10'] 99 | 100 | -------------------------------------------------------------------------------- /docs/source/book/10_oop_special_methods/index.rst: -------------------------------------------------------------------------------- 1 | .. raw:: latex 2 | 3 | \newpage 4 | 5 | 10. Специальные методы 6 | ====================== 7 | 8 | Специальные методы в Python - это методы, которые отвечают за "стандартные" возможности 9 | объектов и вызываются автоматически при использовани этих возможностей. 10 | Например, выражение ``a + b``, где a и b это числа, преобразуется в такой вызов 11 | ``a.__add__(b)``, то есть, специальный метод __add__ отвечает за операцию сложения. 12 | Все специальные методы начинаются и заканчиваются двойным подчеркиванием, поэтому 13 | на английском их часто называют dunder методы, сокращенно от "double underscore". 14 | 15 | 16 | .. note:: 17 | 18 | Специальные методы часто называют волшебными (magic) методами. 19 | 20 | Специальные методы отвечают за такие возможности как работа в менеджерах контекста, 21 | создание итераторов и итерируемых объектов, операции сложения, умножения и другие. 22 | Добавляя специальные методы в объекты, которые созданы пользователем, мы делаем 23 | их похожими на встроенные объекты. 24 | 25 | .. toctree:: 26 | :maxdepth: 1 27 | :hidden: 28 | 29 | underscore_names 30 | str_repr 31 | add_method 32 | protocols 33 | ../../exercises/10_exercises.rst 34 | -------------------------------------------------------------------------------- /docs/source/book/10_oop_special_methods/protocols.rst: -------------------------------------------------------------------------------- 1 | Протоколы 2 | --------- 3 | 4 | Специальные методы отвечают не только за поддержку операций типа сложение, сравнение, 5 | но и за поддержку протоколов. 6 | Протокол - это набор методов, которые должны быть реализованы в объекте, 7 | чтобы он поддерживал определенное поведение. Например, в Python есть такие протоколы: итерации, 8 | менеджер контекста, контейнеры и другие. После создания в объекте определенных методов, 9 | объект будет вести себя как встроенный и использовать интерфейс понятный всем, 10 | кто пишет на Python. 11 | 12 | .. note:: 13 | 14 | `Таблица с абстрактных классов в которой описаны какие методы должны присутствовать у объекта, чтобы он поддерживал определенный протокол `__ 15 | 16 | 17 | .. toctree:: 18 | :maxdepth: 1 19 | :hidden: 20 | 21 | iterable_iterator 22 | sequence_protocol 23 | context_manager 24 | -------------------------------------------------------------------------------- /docs/source/book/10_oop_special_methods/str_repr.rst: -------------------------------------------------------------------------------- 1 | Методы __str__, __repr__ 2 | ~~~~~~~~~~~~~~~~~~~~~~~~ 3 | 4 | Специальные методы __str__ и __repr__ отвечают за строковое представления 5 | объекта. При этом используются они в разных местах. 6 | 7 | Рассмотрим пример класса IPAddress, который отвечает за представление 8 | IPv4 адреса: 9 | 10 | .. code:: python 11 | 12 | In [1]: class IPAddress: 13 | ...: def __init__(self, ip): 14 | ...: self.ip = ip 15 | ...: 16 | 17 | После создания экземпляров класса, у них есть строковое представление по 18 | умолчанию, которое выглядит так (этот же вывод отображается при использовании print): 19 | 20 | .. code:: python 21 | 22 | In [2]: ip1 = IPAddress('10.1.1.1') 23 | 24 | In [3]: ip2 = IPAddress('10.2.2.2') 25 | 26 | In [4]: str(ip1) 27 | Out[4]: '<__main__.IPAddress object at 0xb4e4e76c>' 28 | 29 | In [5]: str(ip2) 30 | Out[5]: '<__main__.IPAddress object at 0xb1bd376c>' 31 | 32 | К сожалению, это представление не очень информативно. И было бы лучше, если бы 33 | отображалась информация о том, какой именно адрес представляет этот экземпляр. 34 | За отображение информации при применении функции str, отвечает специальный метод __str__ - 35 | как аргумент метод ожидает только экземпляр и должен возвращать строку 36 | 37 | .. code:: python 38 | 39 | In [6]: class IPAddress: 40 | ...: def __init__(self, ip): 41 | ...: self.ip = ip 42 | ...: 43 | ...: def __str__(self): 44 | ...: return f"IPAddress: {self.ip}" 45 | ...: 46 | 47 | In [7]: ip1 = IPAddress('10.1.1.1') 48 | 49 | In [8]: ip2 = IPAddress('10.2.2.2') 50 | 51 | In [9]: str(ip1) 52 | Out[9]: 'IPAddress: 10.1.1.1' 53 | 54 | In [10]: str(ip2) 55 | Out[10]: 'IPAddress: 10.2.2.2' 56 | 57 | Второе строковое представление, которое используется в объектах Python, отображается 58 | при использовании функции repr, а также при добавлении объектов в контейнеры типа списков: 59 | 60 | 61 | .. code:: python 62 | 63 | In [11]: ip_addresses = [ip1, ip2] 64 | 65 | In [12]: ip_addresses 66 | Out[12]: [<__main__.IPAddress at 0xb4e40c8c>, <__main__.IPAddress at 0xb1bc46ac>] 67 | 68 | In [13]: repr(ip1) 69 | Out[13]: '<__main__.IPAddress object at 0xb4e40c8c>' 70 | 71 | За это отображение отвечает метод __repr__, он тоже должен возвращать строку, 72 | но при этом принято, чтобы метод возвращал строку, скопировав которую, можно 73 | получить экземпляр класса: 74 | 75 | .. code:: python 76 | 77 | In [14]: class IPAddress: 78 | ...: def __init__(self, ip): 79 | ...: self.ip = ip 80 | ...: 81 | ...: def __str__(self): 82 | ...: return f"IPAddress: {self.ip}" 83 | ...: 84 | ...: def __repr__(self): 85 | ...: return f"IPAddress('{self.ip}')" 86 | ...: 87 | 88 | In [15]: ip1 = IPAddress('10.1.1.1') 89 | 90 | In [16]: ip2 = IPAddress('10.2.2.2') 91 | 92 | In [17]: ip_addresses = [ip1, ip2] 93 | 94 | In [18]: ip_addresses 95 | Out[18]: [IPAddress('10.1.1.1'), IPAddress('10.2.2.2')] 96 | 97 | In [19]: repr(ip1) 98 | Out[19]: "IPAddress('10.1.1.1')" 99 | 100 | -------------------------------------------------------------------------------- /docs/source/book/11_oop_method_decorators/base_ssh.py: -------------------------------------------------------------------------------- 1 | import paramiko 2 | import time 3 | 4 | 5 | class BaseSSH: 6 | def __init__(self, ip, username, password): 7 | self.ip = ip 8 | self.username = username 9 | self.password = password 10 | self._MAX_READ = 10000 11 | 12 | client = paramiko.SSHClient() 13 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 14 | 15 | client.connect( 16 | hostname=ip, 17 | username=username, 18 | password=password, 19 | look_for_keys=False, 20 | allow_agent=False) 21 | 22 | self._ssh = client.invoke_shell() 23 | time.sleep(1) 24 | self._ssh.recv(self._MAX_READ) 25 | 26 | def __enter__(self): 27 | return self 28 | 29 | def __exit__(self, exc_type, exc_value, traceback): 30 | self._ssh.close() 31 | 32 | def close(self): 33 | self._ssh.close() 34 | 35 | def send_show_command(self, command): 36 | self._ssh.send(command + '\n') 37 | time.sleep(2) 38 | result = self._ssh.recv(self._MAX_READ).decode('ascii') 39 | return result 40 | 41 | def send_config_commands(self, commands): 42 | if isinstance(commands, str): 43 | commands = [commands] 44 | for command in commands: 45 | self._ssh.send(command + '\n') 46 | time.sleep(0.5) 47 | result = self._ssh.recv(self._MAX_READ).decode('ascii') 48 | return result 49 | 50 | -------------------------------------------------------------------------------- /docs/source/book/11_oop_method_decorators/classmethod.rst: -------------------------------------------------------------------------------- 1 | Декоратор classmethod 2 | --------------------- 3 | 4 | Иногда нужно реализовать несколько способов создания экземпяра, 5 | при этом в Python можно создавать только один метод __init__. 6 | Конечно, можно реализовать все варианты в одном __init__, 7 | но при этом часто параметры __init__ становятся или слишком общими, 8 | или их слишком много. 9 | 10 | Существует другой вариант решения проблемы - создать альтернативный 11 | конструктор с помощью декоратора classmethod. 12 | 13 | Пример альтернативного конструктора в стандартной библиотеке: 14 | 15 | .. code:: python 16 | 17 | In [25]: r1 = { 18 | ...: 'hostname': 'R1', 19 | ...: 'OS': 'IOS', 20 | ...: 'Vendor': 'Cisco' 21 | ...: } 22 | 23 | In [28]: dict.fromkeys(['hostname', 'os', 'vendor']) 24 | Out[28]: {'hostname': None, 'os': None, 'vendor': None} 25 | 26 | In [29]: dict.fromkeys(['hostname', 'os', 'vendor'], '') 27 | Out[29]: {'hostname': '', 'os': '', 'vendor': ''} 28 | 29 | 30 | .. code:: python 31 | 32 | import time 33 | from textfsm import clitable 34 | from base_ssh import BaseSSH 35 | 36 | class CiscoSSH(BaseSSH): 37 | def __init__(self, ip, username, password, enable_password, 38 | disable_paging=True): 39 | super().__init__(ip, username, password) 40 | self._ssh.send('enable\n') 41 | self._ssh.send(enable_password + '\n') 42 | if disable_paging: 43 | self._ssh.send('terminal length 0\n') 44 | time.sleep(1) 45 | self._ssh.recv(self._MAX_READ) 46 | self._mgmt_ip = None 47 | 48 | @classmethod 49 | def default_params(cls, ip): 50 | params = { 51 | 'ip': ip, 52 | 'username': 'cisco', 53 | 'password': 'cisco', 54 | 'enable_password': 'cisco'} 55 | return cls(**params) 56 | 57 | 58 | .. code:: python 59 | 60 | In [8]: r1 = CiscoSSH.default_params('192.168.100.1') 61 | 62 | In [9]: r1.send_show_command('sh clock') 63 | Out[9]: '*16:38:01.883 UTC Sun Jan 28 2018' 64 | 65 | -------------------------------------------------------------------------------- /docs/source/book/11_oop_method_decorators/further_reading.rst: -------------------------------------------------------------------------------- 1 | Дополнительные материалы 2 | ------------------------ 3 | 4 | * `Descriptor HowTo Guide `__ 5 | * `Pure Python property `__ 6 | -------------------------------------------------------------------------------- /docs/source/book/11_oop_method_decorators/index.rst: -------------------------------------------------------------------------------- 1 | .. raw:: latex 2 | 3 | \newpage 4 | 5 | 11. Classmethod, staticmethod, property 6 | ====================================== 7 | 8 | В Python есть ряд полезных встроенных декораторов, которые позволяют менять поведение 9 | методов класса. 10 | 11 | 12 | .. toctree:: 13 | :maxdepth: 1 14 | :hidden: 15 | 16 | property 17 | classmethod 18 | staticmethod 19 | ../../exercises/11_exercises.rst 20 | -------------------------------------------------------------------------------- /docs/source/book/11_oop_method_decorators/staticmethod.rst: -------------------------------------------------------------------------------- 1 | Декоратор staticmethod 2 | ---------------------- 3 | 4 | Статический метод - это метод, который не привязан к состоянию 5 | экземпляра или класса. Для создания статического метода 6 | используется декоратор staticmethod. 7 | 8 | Преимущества использования staticmethod: 9 | 10 | * Это подсказка для тех, кто читает код, которая указывает на то, 11 | что метод не зависит от состояния экземпляра класса. 12 | 13 | Большинству методов для работы нужна ссылка на экземпляр, 14 | поэтому как первый аргумент используется self. 15 | Однако иногда бывают методы, которые никак не связаны с 16 | экземпляром и зависят только от аргументов. 17 | Как правило, в таком случае можно даже вынести метод из класса 18 | и сделать его функцией. 19 | Если же метод логически связан с работой класса, но работает одинаково 20 | независимо от состояния экземпляров, метод декорируют декоратором staticmethod, 21 | чтобы указать это явно. 22 | 23 | .. code:: python 24 | 25 | import time 26 | from textfsm import clitable 27 | from base_ssh import BaseSSH 28 | 29 | class CiscoSSH(BaseSSH): 30 | def __init__(self, ip, username, password, enable_password, 31 | disable_paging=True): 32 | super().__init__(ip, username, password) 33 | self._ssh.send('enable\n') 34 | self._ssh.send(enable_password + '\n') 35 | if disable_paging: 36 | self._ssh.send('terminal length 0\n') 37 | time.sleep(1) 38 | self._ssh.recv(self._MAX_READ) 39 | self._mgmt_ip = None 40 | 41 | @staticmethod 42 | def _parse_show(command, command_output, 43 | index_file='index', templates='templates'): 44 | attributes = {'Command': command, 45 | 'Vendor': 'cisco_ios'} 46 | cli_table = clitable.CliTable(index_file, templates) 47 | cli_table.ParseCmd(command_output, attributes) 48 | return [dict(zip(cli_table.header, row)) for row in cli_table] 49 | 50 | def send_show_command(self, command, parse=True): 51 | command_output = super().send_show_command(command) 52 | if not parse: 53 | return command_output 54 | return self._parse_show(command, command_output) 55 | 56 | 57 | .. code:: python 58 | 59 | In [6]: r1 = CiscoSSH('192.168.100.1', 'cisco', 'cisco', 'cisco') 60 | 61 | In [7]: r1.send_show_command('sh ip int br') 62 | Out[7]: 63 | [{'intf': 'Ethernet0/0', 64 | 'address': '192.168.100.1', 65 | 'status': 'up', 66 | 'protocol': 'up'}, 67 | {'intf': 'Ethernet0/1', 68 | 'address': '192.168.200.1', 69 | 'status': 'up', 70 | 'protocol': 'up'}, 71 | {'intf': 'Ethernet0/2', 72 | 'address': '19.1.1.1', 73 | 'status': 'up', 74 | 'protocol': 'up'}, 75 | {'intf': 'Ethernet0/3', 76 | 'address': '192.168.230.1', 77 | 'status': 'up', 78 | 'protocol': 'up'}, 79 | {'intf': 'Loopback0', 80 | 'address': '10.4.4.4', 81 | 'status': 'up', 82 | 'protocol': 'up'}, 83 | {'intf': 'Loopback90', 84 | 'address': '90.1.1.1', 85 | 'status': 'up', 86 | 'protocol': 'up'}] 87 | 88 | -------------------------------------------------------------------------------- /docs/source/book/11_oop_method_decorators/templates/index: -------------------------------------------------------------------------------- 1 | Template, Hostname, Vendor, Command 2 | sh_cdp_n_det.template, .*, cisco_ios, sh[[ow]] cdp ne[[ighbors]] de[[tail]] 3 | sh_clock.template, .*, cisco_ios, sh[[ow]] clo[[ck]] 4 | sh_ip_int_br.template, .*, cisco_ios, sh[[ow]] ip int[[erface]] br[[ief]] 5 | sh_ip_route_ospf.template, .*, cisco_ios, sh[[ow]] ip rou[[te]] o[[spf]] 6 | -------------------------------------------------------------------------------- /docs/source/book/11_oop_method_decorators/templates/sh_cdp_n_det.template: -------------------------------------------------------------------------------- 1 | Value Filldown LOCAL_HOST (\S+) 2 | Value Required DEST_HOST (\S+) 3 | Value MGMNT_IP (.*) 4 | Value PLATFORM (.*) 5 | Value LOCAL_PORT (.*) 6 | Value REMOTE_PORT (.*) 7 | Value IOS_VERSION (\S+) 8 | 9 | Start 10 | ^${LOCAL_HOST}[>#]. 11 | ^Device ID: ${DEST_HOST} 12 | ^.*IP address: ${MGMNT_IP} 13 | ^Platform: ${PLATFORM}, 14 | ^Interface: ${LOCAL_PORT}, Port ID \(outgoing port\): ${REMOTE_PORT} 15 | ^.*Version ${IOS_VERSION}, -> Record 16 | -------------------------------------------------------------------------------- /docs/source/book/11_oop_method_decorators/templates/sh_clock.template: -------------------------------------------------------------------------------- 1 | Value Time (..:..:..) 2 | Value Timezone (\S+) 3 | Value WeekDay (\w+) 4 | Value Month (\w+) 5 | Value MonthDay (\d+) 6 | Value Year (\d+) 7 | 8 | Start 9 | ^${Time}.* ${Timezone} ${WeekDay} ${Month} ${MonthDay} ${Year} -> Record 10 | -------------------------------------------------------------------------------- /docs/source/book/11_oop_method_decorators/templates/sh_ip_int_br.template: -------------------------------------------------------------------------------- 1 | Value intf (\S+) 2 | Value address (\S+) 3 | Value status (up|down|administratively down) 4 | Value protocol (up|down) 5 | 6 | Start 7 | ^${intf}\s+${address}\s+\w+\s+\w+\s+${status}\s+${protocol} -> Record 8 | -------------------------------------------------------------------------------- /docs/source/book/11_oop_method_decorators/templates/sh_ip_route_ospf.template: -------------------------------------------------------------------------------- 1 | Value Network (([0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3})) 2 | Value Mask (\/\d{1,2}) 3 | Value Distance (\d+) 4 | Value Metric (\d+) 5 | Value List NextHop ([0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}) 6 | 7 | Start 8 | ^O -> Continue.Record 9 | ^O +${Network}${Mask}\s\[${Distance}\/${Metric}\]\svia\s${NextHop}, 10 | ^\s+\[${Distance}\/${Metric}\]\svia\s${NextHop}, 11 | 12 | -------------------------------------------------------------------------------- /docs/source/book/12_oop_inheritance/abc_example.py: -------------------------------------------------------------------------------- 1 | import paramiko 2 | import time 3 | import abc 4 | 5 | 6 | class BaseSSH(abc.ABC): 7 | def __init__(self, ip, username, password): 8 | self.ip = ip 9 | self.username = username 10 | self.password = password 11 | self._MAX_READ = 10000 12 | 13 | client = paramiko.SSHClient() 14 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 15 | 16 | client.connect( 17 | hostname=ip, 18 | username=username, 19 | password=password, 20 | look_for_keys=False, 21 | allow_agent=False) 22 | 23 | self._ssh = client.invoke_shell() 24 | time.sleep(1) 25 | self._ssh.recv(self._MAX_READ) 26 | 27 | def __enter__(self): 28 | return self 29 | 30 | def __exit__(self, exc_type, exc_value, traceback): 31 | self._ssh.close() 32 | 33 | def close(self): 34 | self._ssh.close() 35 | 36 | @abc.abstractmethod 37 | def send_command(self, command): 38 | """Send command and get command output""" 39 | 40 | @abc.abstractmethod 41 | def send_config_commands(self, commands): 42 | """Send configuration command(s)""" 43 | 44 | 45 | class CiscoSSH(BaseSSH): 46 | device_type = 'cisco_ios' 47 | def __init__(self, ip, username, password, enable_password, 48 | disable_paging=True): 49 | super().__init__(ip, username, password) 50 | self._ssh.send('enable\n') 51 | self._ssh.send(enable_password + '\n') 52 | if disable_paging: 53 | self._ssh.send('terminal length 0\n') 54 | time.sleep(1) 55 | self._ssh.recv(self._MAX_READ) 56 | 57 | def config_mode(self): 58 | self._ssh.send('conf t\n') 59 | time.sleep(0.5) 60 | result = self._ssh.recv(self._MAX_READ).decode('ascii') 61 | return result 62 | 63 | def exit_config_mode(self): 64 | self._ssh.send('end\n') 65 | time.sleep(0.5) 66 | result = self._ssh.recv(self._MAX_READ).decode('ascii') 67 | return result 68 | 69 | def send_config_commands(self, commands): 70 | result = self.config_mode() 71 | result += super().send_config_commands(commands) 72 | result += self.exit_config_mode() 73 | return result 74 | 75 | 76 | class JuniperSSH(BaseSSH): 77 | device_type = 'juniper' 78 | def __init__(self, ip, username, password, enable_password, 79 | disable_paging=True): 80 | pass 81 | 82 | 83 | -------------------------------------------------------------------------------- /docs/source/book/12_oop_inheritance/base_ssh.py: -------------------------------------------------------------------------------- 1 | import paramiko 2 | import time 3 | 4 | 5 | class BaseSSH: 6 | def __init__(self, ip, username, password): 7 | self.ip = ip 8 | self.username = username 9 | self.password = password 10 | self._MAX_READ = 10000 11 | 12 | client = paramiko.SSHClient() 13 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 14 | 15 | client.connect( 16 | hostname=ip, 17 | username=username, 18 | password=password, 19 | look_for_keys=False, 20 | allow_agent=False) 21 | 22 | self._ssh = client.invoke_shell() 23 | time.sleep(1) 24 | self._ssh.recv(self._MAX_READ) 25 | 26 | def __enter__(self): 27 | return self 28 | 29 | def __exit__(self, exc_type, exc_value, traceback): 30 | self._ssh.close() 31 | 32 | def close(self): 33 | self._ssh.close() 34 | 35 | def send_show_command(self, command): 36 | self._ssh.send(command + '\n') 37 | time.sleep(2) 38 | result = self._ssh.recv(self._MAX_READ).decode('ascii') 39 | return result 40 | 41 | def send_config_commands(self, commands): 42 | if isinstance(commands, str): 43 | commands = [commands] 44 | for command in commands: 45 | self._ssh.send(command + '\n') 46 | time.sleep(0.5) 47 | result = self._ssh.recv(self._MAX_READ).decode('ascii') 48 | return result 49 | 50 | -------------------------------------------------------------------------------- /docs/source/book/12_oop_inheritance/builtin.rst: -------------------------------------------------------------------------------- 1 | Наследование встроенных типов данных 2 | ------------------------------------ 3 | 4 | collections.UserDict, collections.UserList 5 | -------------------------------------------------------------------------------- /docs/source/book/12_oop_inheritance/exceptions.rst: -------------------------------------------------------------------------------- 1 | Исключения 2 | ---------- 3 | 4 | Встроенные исключения 5 | ~~~~~~~~~~~~~~~~~~~~~ 6 | 7 | :: 8 | 9 | BaseException 10 | +-- SystemExit 11 | +-- KeyboardInterrupt 12 | +-- GeneratorExit 13 | +-- Exception 14 | +-- StopIteration 15 | +-- StopAsyncIteration 16 | +-- ArithmeticError 17 | | +-- FloatingPointError 18 | | +-- OverflowError 19 | | +-- ZeroDivisionError 20 | +-- AssertionError 21 | +-- AttributeError 22 | +-- BufferError 23 | +-- EOFError 24 | +-- ImportError 25 | | +-- ModuleNotFoundError 26 | +-- LookupError 27 | | +-- IndexError 28 | | +-- KeyError 29 | +-- MemoryError 30 | +-- NameError 31 | | +-- UnboundLocalError 32 | +-- OSError 33 | | +-- BlockingIOError 34 | | +-- ChildProcessError 35 | | +-- ConnectionError 36 | | | +-- BrokenPipeError 37 | | | +-- ConnectionAbortedError 38 | | | +-- ConnectionRefusedError 39 | | | +-- ConnectionResetError 40 | | +-- FileExistsError 41 | | +-- FileNotFoundError 42 | | +-- InterruptedError 43 | | +-- IsADirectoryError 44 | | +-- NotADirectoryError 45 | | +-- PermissionError 46 | | +-- ProcessLookupError 47 | | +-- TimeoutError 48 | +-- ReferenceError 49 | +-- RuntimeError 50 | | +-- NotImplementedError 51 | | +-- RecursionError 52 | +-- SyntaxError 53 | | +-- IndentationError 54 | | +-- TabError 55 | +-- SystemError 56 | +-- TypeError 57 | +-- ValueError 58 | | +-- UnicodeError 59 | | +-- UnicodeDecodeError 60 | | +-- UnicodeEncodeError 61 | | +-- UnicodeTranslateError 62 | +-- Warning 63 | +-- DeprecationWarning 64 | +-- PendingDeprecationWarning 65 | +-- RuntimeWarning 66 | +-- SyntaxWarning 67 | +-- UserWarning 68 | +-- FutureWarning 69 | +-- ImportWarning 70 | +-- UnicodeWarning 71 | +-- BytesWarning 72 | +-- ResourceWarning 73 | 74 | 75 | Пользовательские исключения 76 | ~~~~~~~~~~~~~~~~~~~~~~~~ 77 | 78 | Пользователь может создать свое исключение, которое описывает работу своего приложения/скрипта. Для этого, необходимо наследовать класс Exception. 79 | 80 | .. code:: python 81 | 82 | class Error(Exception): 83 | """Свое исключение для модуля.""" 84 | pass 85 | -------------------------------------------------------------------------------- /docs/source/book/12_oop_inheritance/further_reading.rst: -------------------------------------------------------------------------------- 1 | Дополнительные материалы 2 | ------------------------ 3 | 4 | Наследование 5 | ~~~~~~~~~~~~~ 6 | 7 | * `Real Python Inheritance and Composition: A Python OOP Guide `__ 8 | * `python-course.eu Inheritance `__ 9 | * `python-course.eu Multiple Inheritance `__ 10 | * `mro `__ 11 | * `Guido van Rossum Method Resolution Order `__ 12 | * `The wonders of cooperative inheritance, or using super in Python 3 `__ 13 | 14 | super 15 | ~~~~~~~~~~~~~ 16 | 17 | * `Python docs super `__ 18 | * `Статья Python’s super() considered super! `__ 19 | * `Raymond Hettinger - Super considered super! - PyCon 2015 `__ 20 | * `Real Python Supercharge Your Classes With Python super() `__ 21 | 22 | Исключения 23 | ~~~~~~~~~~~~~ 24 | 25 | * `Python Built-in Exceptions `__ 26 | * `User-defined Exceptions `__ 27 | * `raise `__ 28 | 29 | Mixin 30 | ~~~~~~~~~~~~~ 31 | 32 | * `What is a mixin, and why are they useful? `__ 33 | 34 | Примеры mixin: 35 | 36 | * `class contextlib.ContextDecorator `__ 37 | 38 | 39 | ABC 40 | ~~~~~~~~~~~~~ 41 | 42 | * `python docs abc `__ 43 | * `python docs collections.abc `__ 44 | * `python-course.eu ABC `__ 45 | 46 | 47 | SOLID 48 | ~~~~~~~~~~~~~ 49 | 50 | * `SOLID wikipedia `__ 51 | * `Open-closed principle `__ 52 | * `Liskov substitution principle `__ 53 | -------------------------------------------------------------------------------- /docs/source/book/12_oop_inheritance/index.rst: -------------------------------------------------------------------------------- 1 | .. raw:: latex 2 | 3 | \newpage 4 | 5 | 12. Наследование 6 | ============================ 7 | 8 | 9 | .. toctree:: 10 | :maxdepth: 1 11 | :hidden: 12 | 13 | terms 14 | inheritance 15 | exceptions 16 | multiple_inheritance 17 | abc 18 | mixin 19 | further_reading 20 | ../../exercises/12_exercises.rst 21 | -------------------------------------------------------------------------------- /docs/source/book/12_oop_inheritance/mixin.rst: -------------------------------------------------------------------------------- 1 | Mixin классы 2 | ------------ 3 | 4 | Mixin классы - это классы у которых нет данных, но есть методы. 5 | Mixin используются для добавления одних и тех же методов в разные 6 | классы. 7 | 8 | В Python примеси делаются с помощью классов. Так как в Python нет отдельного типа 9 | для примесей, классам-примесям принято давать имена заканчивающиеся на Mixin. 10 | 11 | 12 | С одной стороны, то же самое можно сделать с помощью наследования обычных классов, 13 | но не всегда те методы, которые нужны в разных дочерних классах, 14 | имеют смысл в родительском. 15 | 16 | 17 | .. code:: python 18 | 19 | import time 20 | import inspect 21 | from base_ssh import BaseSSH 22 | 23 | 24 | class SourceCodeMixin: 25 | @property 26 | def sourcecode(self): 27 | return inspect.getsource(self.__class__) 28 | 29 | 30 | class AttributesMixin: 31 | @property 32 | def attributes(self): 33 | # data attributes 34 | for name, value in self.__dict__.items(): 35 | print(f"{name:25}{str(value):<20}") 36 | # methods 37 | for name, value in self.__class__.__dict__.items(): 38 | if not name.startswith('__'): 39 | print(f"{name:25}{str(value):<20}") 40 | 41 | 42 | .. code:: python 43 | 44 | In [1]: from mixin_example import CiscoSSH 45 | 46 | In [2]: r1 = CiscoSSH('192.168.100.1', 'cisco', 'cisco', 'cisco') 47 | 48 | In [3]: r1.attributes 49 | ip 192.168.100.1 50 | username cisco 51 | password cisco 52 | _MAX_READ 10000 53 | _ssh > 54 | config_mode 55 | exit_config_mode 56 | send_config_commands 57 | 58 | 59 | In [4]: print(r1.sourcecode) 60 | class CiscoSSH(SourceCodeMixin, AttributesMixin, BaseSSH): 61 | def __init__(self, ip, username, password, enable_password, 62 | disable_paging=True): 63 | super().__init__(ip, username, password) 64 | self._ssh.send('enable\n') 65 | self._ssh.send(enable_password + '\n') 66 | if disable_paging: 67 | self._ssh.send('terminal length 0\n') 68 | time.sleep(1) 69 | self._ssh.recv(self._MAX_READ) 70 | 71 | def config_mode(self): 72 | self._ssh.send('conf t\n') 73 | time.sleep(0.5) 74 | result = self._ssh.recv(self._MAX_READ).decode('ascii') 75 | return result 76 | 77 | def exit_config_mode(self): 78 | self._ssh.send('end\n') 79 | time.sleep(0.5) 80 | result = self._ssh.recv(self._MAX_READ).decode('ascii') 81 | return result 82 | 83 | def send_config_commands(self, commands): 84 | result = self.config_mode() 85 | result += super().send_config_commands(commands) 86 | result += self.exit_config_mode() 87 | return result 88 | 89 | 90 | -------------------------------------------------------------------------------- /docs/source/book/12_oop_inheritance/mixin_example.py: -------------------------------------------------------------------------------- 1 | import time 2 | import inspect 3 | from base_ssh import BaseSSH 4 | 5 | 6 | class SourceCodeMixin: 7 | @property 8 | def sourcecode(self): 9 | return inspect.getsource(self.__class__) 10 | 11 | 12 | class AttributesMixin: 13 | @property 14 | def attributes(self): 15 | # data attributes 16 | for name, value in self.__dict__.items(): 17 | print(f"{name:25}{str(value):<20}") 18 | # methods 19 | for name, value in self.__class__.__dict__.items(): 20 | if not name.startswith('__'): 21 | if value.__doc__: 22 | value = value.__doc__ 23 | print(f"{name:25}{str(value):<20}") 24 | 25 | 26 | class CiscoSSH(SourceCodeMixin, AttributesMixin, BaseSSH): 27 | def __init__(self, ip, username, password, enable_password, 28 | disable_paging=True): 29 | super().__init__(ip, username, password) 30 | self._ssh.send('enable\n') 31 | self._ssh.send(enable_password + '\n') 32 | if disable_paging: 33 | self._ssh.send('terminal length 0\n') 34 | time.sleep(1) 35 | self._ssh.recv(self._MAX_READ) 36 | 37 | def config_mode(self): 38 | """Переходит в конфигурационный режим""" 39 | self._ssh.send('conf t\n') 40 | time.sleep(0.5) 41 | result = self._ssh.recv(self._MAX_READ).decode('ascii') 42 | return result 43 | 44 | def exit_config_mode(self): 45 | self._ssh.send('end\n') 46 | time.sleep(0.5) 47 | result = self._ssh.recv(self._MAX_READ).decode('ascii') 48 | return result 49 | 50 | def send_config_commands(self, commands): 51 | """Отправляет команды в конфигурационном режиме""" 52 | result = self.config_mode() 53 | result += super().send_config_commands(commands) 54 | result += self.exit_config_mode() 55 | return result 56 | 57 | 58 | if __name__ == "__main__": 59 | r1 = CiscoSSH('192.168.100.1', 'cisco', 'cisco', 'cisco') 60 | r1.attributes() 61 | 62 | 63 | -------------------------------------------------------------------------------- /docs/source/book/12_oop_inheritance/multiple_inheritance.rst: -------------------------------------------------------------------------------- 1 | Множественное наследование 2 | -------------------------- 3 | 4 | В Python дочерний класс может наследовать несколько родительских. 5 | 6 | 7 | class A: 8 | def __init__(self): 9 | print('A.__init__') 10 | 11 | class B: 12 | def __init__(self): 13 | print('B.__init__') 14 | 15 | class C(A, B): 16 | def __init__(self): 17 | print('C.__init__') 18 | -------------------------------------------------------------------------------- /docs/source/book/12_oop_inheritance/netmiko_codebase_examples.py: -------------------------------------------------------------------------------- 1 | #base_connection.py 2 | def commit(self): 3 | """Commit method for platforms that support this.""" 4 | raise AttributeError("Network device does not support 'commit()' method") 5 | 6 | def save_config(self, *args, **kwargs): 7 | """Not Implemented""" 8 | raise NotImplementedError 9 | -------------------------------------------------------------------------------- /docs/source/book/12_oop_inheritance/terms.rst: -------------------------------------------------------------------------------- 1 | Терминология 2 | ------------ 3 | 4 | Интерфейс/Протокол (Interface/Protocol) 5 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 6 | 7 | Интерфейс - набор атрибутов и методов, которые реализуют определенное поведение. Примеры: итератор, менеджер контекста, последовательность. 8 | 9 | 10 | Наследование (Inheritance) 11 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 12 | 13 | Наследование - концепция ООП, которая возволяет дочернему классу использовать компоненты (методы и переменные) родительского класса. 14 | 15 | Как правило, для наследования есть две основные причины: 16 | 17 | * создание подтипа (interface inheritance) 18 | * наследование для использования кода 19 | 20 | В Python синтаксис наследования используется с абстрактными классами для наследования интерфейса/протокола. 21 | Кроме того, синтаксис наследования используется с Mixin. 22 | 23 | `Принцип подстановки Барбары Лисков `__ 24 | 25 | 26 | 27 | Агрегирование (Aggregation) 28 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 29 | 30 | Агрегация (агрегирование по ссылке) — отношение «часть-целое» между двумя равноправными объектами, 31 | когда один объект (контейнер) имеет ссылку на другой объект. 32 | Оба объекта могут существовать независимо: если контейнер будет уничтожен, то его содержимое — нет. 33 | 34 | 35 | Композиция (Composition) 36 | ~~~~~~~~~~~~~~~~~~~~~~~~ 37 | 38 | Композиция (агрегирование по значению) — более строгий вариант агрегирования, 39 | когда включаемый объект может существовать только как часть контейнера. 40 | Если контейнер будет уничтожен, то и включённый объект тоже будет уничтожен. 41 | 42 | 43 | .. code:: python 44 | 45 | from jinja2 import Environment, FileSystemLoader 46 | 47 | env = Environment(loader=FileSystemLoader('templates')) 48 | template = env.get_template('router_template.txt') 49 | 50 | 51 | Полиморфизм (Polymorphism) 52 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 53 | 54 | Как правило, различают два варианта полиморфизма: 55 | 56 | 1. способность функции/метода обрабатывать данные разных типов 57 | 2. один интерфейс - много реализаций. Пример: одно и то же имя метода в разных классах 58 | 59 | Метакласс (Metaclass) 60 | ~~~~~~~~~~~~~~~~~~~~~ 61 | 62 | Метакласс - это класс экземпляры которого тоже являются классами. 63 | 64 | Абстрактный класс (abstract class) 65 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 66 | 67 | `Абстрактный класс `__ - базовый класс, 68 | который не предполагает создания экземпляров. 69 | Как правило, содержит абстрактные методы - методы, которые обязательно должны быть 70 | созданы в дочерних классах. 71 | 72 | В Python абстрактные классы часто используются для создания интерфейса/протокола. 73 | 74 | 75 | Примесь (Mixin) 76 | ~~~~~~~~~~~~~~~ 77 | 78 | `Примесь `__ это класс, который реализует какое-то одно ограниченное поведение (метод). 79 | 80 | В Python примеси делаются с помощью классов. Так как в Python нет отдельного типа для примесей, 81 | классам-примесям принято давать имена заканчивающиеся на Mixin. 82 | 83 | -------------------------------------------------------------------------------- /docs/source/book/13_data_classes/further_reading.rst: -------------------------------------------------------------------------------- 1 | Дополнительные материалы 2 | ------------------------ 3 | 4 | Документация: 5 | 6 | * `Data Classes `__ 7 | 8 | 9 | Полезные статьи: 10 | 11 | * `Python dataclasses: A revolution `__ 12 | * `Reconciling Dataclasses And Properties In Python `__ 13 | 14 | Видео: 15 | 16 | * `Raymond Hettinger - Dataclasses: The code generator to end all code generators - PyCon 2018 `__ 17 | 18 | 19 | Дополнительные возможности: 20 | 21 | * `pydantic for full type validation for dataclasses `__ 22 | * `Модуль attrs похож на dataclasses, но у него больше возможностей `__ 23 | -------------------------------------------------------------------------------- /docs/source/book/13_data_classes/index.rst: -------------------------------------------------------------------------------- 1 | .. raw:: latex 2 | 3 | \newpage 4 | 5 | 13. Data classes 6 | =============== 7 | 8 | 9 | .. toctree:: 10 | :maxdepth: 1 11 | :hidden: 12 | 13 | dataclasses 14 | further_reading 15 | ../../exercises/13_exercises.rst 16 | -------------------------------------------------------------------------------- /docs/source/book/13_data_classes/ipaddress_dataclass.py: -------------------------------------------------------------------------------- 1 | from dataclasses import asdict, astuple, replace, dataclass, field 2 | 3 | @dataclass(order=True, frozen=True) 4 | class IPAddress: 5 | ip: str 6 | mask: int = 24 7 | 8 | 9 | if __name__ == "__main__": 10 | ip1 = IPAddress('10.1.1.1', 28) 11 | ip2 = IPAddress('10.2.2.1', 28) 12 | ip3 = IPAddress('10.3.3.1', 28) 13 | ip4 = IPAddress('10.4.4.1', 28) 14 | ip_list = [ip3, ip2, ip1, ip4] 15 | print(sorted(ip_list)) 16 | 17 | -------------------------------------------------------------------------------- /docs/source/book/14_generators/further_reading.rst: -------------------------------------------------------------------------------- 1 | Дополнительные материалы 2 | ------------------------ 3 | 4 | Документация: 5 | 6 | - `Iterator 7 | types `__ 8 | - `Functional Programming 9 | HOWTO `__ 10 | 11 | Статьи: 12 | 13 | - `Iterables vs. Iterators vs. 14 | Generators `__ 15 | - `Improve Your Python: 'yield' and Generators 16 | Explained `__ 17 | - generator and generator expressions 18 | - `Generator Tricks for Systems Programmers. David 19 | Beazley `__ 20 | 21 | Ответ на stackoverflow: 22 | 23 | - `Difference between Python's Generators and 24 | Iterators `__ 25 | - `Understanding Generators in 26 | Python `__ 27 | - `What can you use Python generator functions 28 | for? `__ 29 | 30 | В книге Fluent Python этой теме посвящен 14 раздел: 31 | 32 | - `Fluent Python. Chapter 14 Iterables, Iterators, and 33 | Generators `__ 34 | - `Примеры из 35 | книги `__ 36 | 37 | -------------------------------------------------------------------------------- /docs/source/book/14_generators/generator_example.rst: -------------------------------------------------------------------------------- 1 | Пример использования генератора для обработки вывода sh cdp neighbors detail 2 | ---------------------------------------------------------------------------- 3 | 4 | Генераторы могут использоваться не только в том случае, когда надо возвращать элементы по одному. 5 | 6 | Например, генератор get_cdp_neighbor читает файл с выводом sh cdp neighbor detail и выдает вывод частями, по одному соседу: 7 | 8 | .. code:: python 9 | 10 | def get_one_neighbor(filename): 11 | with open(filename) as f: 12 | line = '' 13 | while True: 14 | while not 'Device ID' in line: 15 | line = f.readline() 16 | neighbor = line 17 | for line in f: 18 | if '----------' in line: 19 | break 20 | neighbor += line 21 | yield neighbor 22 | line = f.readline() 23 | if not line: 24 | return 25 | 26 | 27 | Полный скрипт выглядит таким образом (файл parse_cdp_neighbors.py): 28 | 29 | .. code:: python 30 | 31 | import re 32 | from pprint import pprint 33 | 34 | 35 | def get_one_neighbor(filename): 36 | with open(filename) as f: 37 | line = '' 38 | while True: 39 | while not 'Device ID' in line: 40 | line = f.readline() 41 | neighbor = line 42 | for line in f: 43 | if '----------' in line: 44 | break 45 | neighbor += line 46 | yield neighbor 47 | line = f.readline() 48 | if not line: 49 | return 50 | 51 | 52 | def parse_neighbor(output): 53 | regex = ( 54 | r'Device ID: (\S+).+?' 55 | r' IP address: (?P\S+).+?' 56 | r'Platform: (?P\S+ \S+), .+?' 57 | r', Version (?P\S+),') 58 | 59 | result = {} 60 | match = re.search(regex, output, re.DOTALL) 61 | if match: 62 | device = match.group(1) 63 | result[device] = match.groupdict() 64 | return result 65 | 66 | if __name__ == "__main__": 67 | data = get_one_neighbor('sh_cdp_neighbors_detail.txt') 68 | for n in data: 69 | pprint(parse_neighbor(n), width=120) 70 | 71 | 72 | Так как генератор get_cdp_neighbor выдает каждый раз вывод про одного соседа, можно проходиться по результату в цикле и передавать каждый вывод функции parse_cdp. 73 | И конечно же, полученный результат тоже можно не собирать в один большой словарь, а передавать куда-то дальше на обработку или запись. 74 | 75 | Результат выполнения: 76 | 77 | .. code:: python 78 | 79 | $ python parse_cdp_neighbors.py 80 | {'SW2': {'ios': '12.2(55)SE9', 'ip': '10.1.1.2', 'platform': 'cisco WS-C2960-8TC-L'}} 81 | {'R1': {'ios': '12.4(24)T1', 'ip': '10.1.1.1', 'platform': 'Cisco 3825'}} 82 | {'R2': {'ios': '15.2(2)T1', 'ip': '10.2.2.2', 'platform': 'Cisco 2911'}} 83 | {'R3': {'ios': '15.2(2)T1', 'ip': '10.3.3.3', 'platform': 'Cisco 2911'}} 84 | 85 | -------------------------------------------------------------------------------- /docs/source/book/14_generators/genexpr.rst: -------------------------------------------------------------------------------- 1 | generator expression (генераторное выражение) 2 | --------------------------------------------- 3 | 4 | Генераторное выражение использует такой же синтаксис, как list comprehentions, но возвращает итератор, а не список. 5 | 6 | Генераторное выражение выглядит точно так же, как list comprehentions, но используются круглые скобки: 7 | 8 | .. code:: python 9 | 10 | In [1]: genexpr = (x**2 for x in range(10000)) 11 | 12 | In [2]: genexpr 13 | Out[2]: at 0xb571ec8c> 14 | 15 | In [3]: next(genexpr) 16 | Out[3]: 0 17 | 18 | In [4]: next(genexpr) 19 | Out[4]: 1 20 | 21 | In [5]: next(genexpr) 22 | Out[5]: 4 23 | 24 | 25 | Обратите внимание, что это не tuple comprehentions, а генераторное выражение. 26 | 27 | Оно полезно в том случае, когда надо работать с большим итерируемым объектом или бесконечным итератором. 28 | 29 | -------------------------------------------------------------------------------- /docs/source/book/14_generators/index.rst: -------------------------------------------------------------------------------- 1 | .. raw:: latex 2 | 3 | \newpage 4 | 5 | 14. Генераторы 6 | ============= 7 | 8 | Генератор - функция, которая позволяет легко создавать свои итераторы. 9 | В отличии от обычных функций, генератор не просто возвращает значение и завершает работу, а возвращает итератор, который отдает элементы по одному. 10 | 11 | 12 | .. toctree:: 13 | :maxdepth: 1 14 | :hidden: 15 | 16 | syntax 17 | generator 18 | generator_example 19 | genexpr 20 | further_reading 21 | ../../exercises/14_exercises.rst 22 | -------------------------------------------------------------------------------- /docs/source/book/14_generators/syntax.rst: -------------------------------------------------------------------------------- 1 | Создание генератора 2 | ------------------- 3 | 4 | Функция-генератор - это функция, в которой присутствует ключевое слово yield. 5 | При вызове, эта функция возвращает объект генератор. 6 | 7 | 8 | Обычная функция завершает работу если: 9 | 10 | * встретилось выражение return 11 | * закончился код функции (это срабатывает как выражение ``return None``) 12 | * возникло исключение 13 | 14 | После выполнения функции, управление возвращается и программа выполняется дальше. 15 | Все аргументы, которые передавались в функцию, локальные переменные, все это теряется. 16 | Остается только результат, который вернула функция. 17 | 18 | Функция может возвращать список элементов, несколько объектов или возвращать разные результаты, в зависимости от аргументов, но она всегда возвращает какой-то один результат. 19 | 20 | С точки зрения синтаксиса, генератор выглядит как обычная функция, 21 | но, вместо return, используется оператор ``yield``. 22 | Каждый раз, когда внутри функции встречается yield, генератор приостанавливается и возвращает значение. 23 | При следующем запросе, генератор начинает работать с того же места, где он завершил работу в прошлый раз. 24 | Так как yield не завершает работу генератора, он может использоваться несколько раз. 25 | -------------------------------------------------------------------------------- /docs/source/book/15_itertools/chain.rst: -------------------------------------------------------------------------------- 1 | chain 2 | ~~~~~ 3 | 4 | Функция chain ожидает несколько итерируемых объектов как аргумент и возвращает 5 | единый итератор, который перебирает элементы каждого итерируемого объекта 6 | так, как будто они составляют единый объект: 7 | 8 | .. code:: python 9 | 10 | itertools.chain(*iterables) 11 | 12 | Пример использования: 13 | 14 | .. code:: python 15 | 16 | In [4]: line = 'test' 17 | 18 | In [5]: items = [1, 2, 3] 19 | 20 | In [6]: mapping = {'ios': '15.4', 'vendor': 'Cisco'} 21 | 22 | In [7]: for item in chain(line, items, mapping): 23 | ...: print(item) 24 | ...: 25 | t 26 | e 27 | s 28 | t 29 | 1 30 | 2 31 | 3 32 | ios 33 | vendor 34 | 35 | -------------------------------------------------------------------------------- /docs/source/book/15_itertools/compress.rst: -------------------------------------------------------------------------------- 1 | compress 2 | ~~~~~~~~ 3 | 4 | Функция compress позволяет фильтровать данные: она возвращает те элементы из data, которые 5 | соответветствуют истинному значению в selectors: 6 | 7 | .. code:: python 8 | 9 | itertools.compress(data, selectors) 10 | 11 | Пример использования compress для фильтрации полей с ненулевым значением: 12 | 13 | .. code:: python 14 | 15 | In [9]: headers = ['tx_packets', 'rx_packets', 'tx_bytes', 'rx_bytes', 'broadcasts'] 16 | 17 | In [10]: data = [294785, 0, 22275381, 0, 253218] 18 | 19 | In [12]: list(compress(headers, data)) 20 | Out[12]: ['tx_packets', 'tx_bytes', 'broadcasts'] 21 | 22 | In [14]: list(compress(zip(headers, data), data)) 23 | Out[14]: [('tx_packets', 294785), ('tx_bytes', 22275381), ('broadcasts', 253218)] 24 | 25 | In [24]: dict(compress(zip(headers, data), data)) 26 | Out[24]: {'tx_packets': 294785, 'tx_bytes': 22275381, 'broadcasts': 253218} 27 | 28 | Пример фильтрации None: 29 | 30 | .. code:: python 31 | 32 | In [25]: data2 33 | Out[25]: [294785, 0, 22275381, None, None] 34 | 35 | In [26]: headers 36 | Out[26]: ['tx_packets', 'rx_packets', 'tx_bytes', 'rx_bytes', 'broadcasts'] 37 | 38 | In [27]: list(compress(zip(headers, data2), selectors=map(lambda x: x != None, data2))) 39 | Out[27]: [('tx_packets', 294785), ('rx_packets', 0), ('tx_bytes', 22275381)] 40 | -------------------------------------------------------------------------------- /docs/source/book/15_itertools/count.rst: -------------------------------------------------------------------------------- 1 | count 2 | ~~~~~ 3 | 4 | Функция count возвращает итератор, который генерирует числа бесконечно, начиная с указанного 5 | в start и используя шаг step: 6 | 7 | .. code:: python 8 | 9 | itertools.count(start=0, step=1) 10 | 11 | 12 | Пример использования count: 13 | 14 | .. code:: python 15 | 16 | from itertools import count 17 | 18 | 19 | In [13]: ip_list 20 | Out[13]: 21 | ['192.168.100.1', 22 | '192.168.100.2', 23 | '192.168.100.3', 24 | '192.168.100.4', 25 | '192.168.100.5'] 26 | 27 | In [18]: for num, ip in zip(count(1), ip_list): 28 | ...: print((num, ip)) 29 | ...: 30 | (1, '192.168.100.1') 31 | (2, '192.168.100.2') 32 | (3, '192.168.100.3') 33 | (4, '192.168.100.4') 34 | (5, '192.168.100.5') 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /docs/source/book/15_itertools/cycle.rst: -------------------------------------------------------------------------------- 1 | cycle 2 | ~~~~~ 3 | 4 | Функция cycle создает итератор, которые возвращает элементы итерируемого объекта по кругу: 5 | 6 | .. code:: python 7 | 8 | itertools.cycle(iterable) 9 | 10 | Пример использования cycle: 11 | 12 | .. code:: python 13 | 14 | from itertools import cycle 15 | 16 | 17 | spinner = it.cycle('\|/-') 18 | for _ in range(20): 19 | print(f'\r{next(spinner)}', end='') 20 | time.sleep(0.5) 21 | 22 | 23 | -------------------------------------------------------------------------------- /docs/source/book/15_itertools/dropwhile_takewhile.rst: -------------------------------------------------------------------------------- 1 | dropwhile и takewhile 2 | ~~~~~~~~~~~~~~~~~~~~~ 3 | 4 | Функция dropwhile ожидает как аргументы функцию, которая возвращает True или False, в зависимости от условия, и итерируемый объект. 5 | Функция dropwhile отбрасывает элементы итерируемого объекта до тех пор, пока функция переданная как аргумент возвращает True. 6 | Как только dropwhile встречает False, он возвращает итератор с оставшимися объектами. 7 | 8 | .. code:: python 9 | 10 | In [1]: from itertools import dropwhile 11 | 12 | In [2]: list(dropwhile(lambda x: x < 5, [0,2,3,5,10,2,3])) 13 | Out[2]: [5, 10, 2, 3] 14 | 15 | 16 | В данном случае, как только функция dropwhile дошла до числа, которое больше или равно пяти, она вернула все оставшиеся числа. 17 | При этом, даже если далее есть числа, которые меньше 5, функция уже не проверяет их. 18 | 19 | 20 | Функция takewhile - противоположность функции dropwhile: она возвращает итератор 21 | с теми элементами, которые соответствуют условию, до первого ложного условия: 22 | 23 | .. code:: python 24 | 25 | In [3]: from itertools import takewhile 26 | 27 | In [4]: list(takewhile(lambda x: x < 5, [0,2,3,5,10,2,3])) 28 | Out[4]: [0, 2, 3] 29 | 30 | 31 | Пример использования takewhile и dropwhile 32 | 33 | .. code:: python 34 | 35 | def get_cdp_neighbor(sh_cdp_neighbor_detail): 36 | with open(sh_cdp_neighbor_detail) as f: 37 | while True: 38 | begin = dropwhile(lambda x: not 'Device ID' in x, f) 39 | lines = takewhile(lambda y: not '-----' in y, begin) 40 | neighbor = ''.join(lines) 41 | if not neighbor: 42 | return 43 | yield neighbor 44 | 45 | 46 | Файл parse_cdp_file.py: 47 | 48 | .. code:: python 49 | 50 | import re 51 | from pprint import pprint 52 | from itertools import dropwhile, takewhile 53 | 54 | 55 | def get_cdp_neighbor(sh_cdp_neighbor_detail): 56 | with open(sh_cdp_neighbor_detail) as f: 57 | while True: 58 | f = dropwhile(lambda x: not 'Device ID' in x, f) 59 | lines = takewhile(lambda y: not '-----' in y, f) 60 | neighbor = ''.join(lines) 61 | if not neighbor: 62 | return None 63 | yield neighbor 64 | 65 | 66 | def parse_cdp_neighbor(output): 67 | regex = ('Device ID: (\S+)\n.*?' 68 | ' +IP address: (?P\S+).+?' 69 | 'Platform: (?P\S+ \S+),.+?' 70 | 'Version (?P\S+),') 71 | 72 | result = {} 73 | match = re.search(regex, output, re.DOTALL) 74 | if match: 75 | device = match.group(1) 76 | result[device] = match.groupdict() 77 | return result 78 | 79 | 80 | def parse_cdp_output(filename): 81 | result = get_cdp_neighbor(filename) 82 | all_cdp = {} 83 | for neighbor in result: 84 | all_cdp.update(parse_cdp_neighbor(neighbor)) 85 | return all_cdp 86 | 87 | 88 | if __name__ == "__main__": 89 | filename = 'sh_cdp_neighbors_detail.txt' 90 | pprint(parse_cdp_output(filename), width=120) 91 | 92 | 93 | Результат: 94 | 95 | .. code:: python 96 | 97 | $ python parse_cdp_file.py 98 | {'R1': {'ios': '12.4(24)T1', 'ip': '10.1.1.1', 'platform': 'Cisco 3825'}, 99 | 'R2': {'ios': '15.2(2)T1', 'ip': '10.2.2.2', 'platform': 'Cisco 2911'}, 100 | 'R3': {'ios': '15.2(2)T1', 'ip': '10.3.3.3', 'platform': 'Cisco 2911'}, 101 | 'SW2': {'ios': '12.2(55)SE9', 'ip': '10.1.1.2', 'platform': 'cisco WS-C2960-8TC-L'}} 102 | 103 | -------------------------------------------------------------------------------- /docs/source/book/15_itertools/further_reading.rst: -------------------------------------------------------------------------------- 1 | Дополнительные материалы 2 | ------------------------ 3 | 4 | Документация: 5 | 6 | - `itertools `__ 7 | - `PyMOTW itertools `__ 8 | 9 | -------------------------------------------------------------------------------- /docs/source/book/15_itertools/index.rst: -------------------------------------------------------------------------------- 1 | .. raw:: latex 2 | 3 | \newpage 4 | 5 | 15. Модули itertools, more-itertools 6 | =================================== 7 | 8 | В этом разделе рассматриваются два модуля: модуль из стандартной библиотеки itertools 9 | и сторонний модуль more-itertools. Оба модуля предоставляют набор функций для работы 10 | с итераторами. 11 | 12 | Оба модуля содержат большое количество функций, поэтому в этом разделе рассматриваются 13 | только некоторые из них. 14 | 15 | .. toctree:: 16 | :maxdepth: 5 17 | :hidden: 18 | 19 | itertools 20 | more_itertools 21 | -------------------------------------------------------------------------------- /docs/source/book/15_itertools/islice.rst: -------------------------------------------------------------------------------- 1 | islice 2 | ~~~~~~ 3 | 4 | Функция islice 5 | 6 | .. code:: python 7 | 8 | itertools.islice(iterable, stop) 9 | itertools.islice(iterable, start, stop[, step]) 10 | 11 | Пример использования: 12 | 13 | .. code:: python 14 | 15 | In [59]: list(islice(range(100), 5)) 16 | Out[59]: [0, 1, 2, 3, 4] 17 | 18 | In [60]: list(islice(range(100), 5, 10)) 19 | Out[60]: [5, 6, 7, 8, 9] 20 | 21 | In [61]: list(islice(range(100), 5, 10, 2)) 22 | Out[61]: [5, 7, 9] 23 | 24 | In [62]: list(islice(range(100), 5, 20, 2)) 25 | Out[62]: [5, 7, 9, 11, 13, 15, 17, 19] 26 | 27 | In [63]: list(islice(range(100), 5, 20, 3)) 28 | Out[63]: [5, 8, 11, 14, 17] 29 | 30 | -------------------------------------------------------------------------------- /docs/source/book/15_itertools/itertools.rst: -------------------------------------------------------------------------------- 1 | itertools 2 | ========= 3 | 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | :hidden: 8 | 9 | repeat 10 | cycle 11 | count 12 | zip_longest 13 | chain 14 | compress 15 | tee 16 | islice 17 | groupby 18 | dropwhile_takewhile 19 | 20 | -------------------------------------------------------------------------------- /docs/source/book/15_itertools/mit_collapse.rst: -------------------------------------------------------------------------------- 1 | collapse 2 | -------- 3 | 4 | .. code:: python 5 | 6 | more_itertools.collapse(iterable, base_type=None, levels=None) 7 | 8 | Пример 9 | 10 | .. code:: python 11 | 12 | In [37]: iterable = [(1, 2), ([3, 4], [[5], [6]])] 13 | 14 | In [38]: list(more_itertools.collapse(iterable)) 15 | Out[38]: [1, 2, 3, 4, 5, 6] 16 | 17 | -------------------------------------------------------------------------------- /docs/source/book/15_itertools/mit_grouping.rst: -------------------------------------------------------------------------------- 1 | Группировка 2 | ----------- 3 | 4 | chunked 5 | ~~~~~~~ 6 | 7 | Разбивает итерируемый объект на списки указанной длины: 8 | 9 | .. code:: python 10 | 11 | more_itertools.chunked(iterable, n) 12 | 13 | Пример: 14 | 15 | .. code:: python 16 | 17 | In [6]: list(more_itertools.chunked(data, 2)) 18 | Out[6]: [[1, 2], [3, 4], [5, 6], [7]] 19 | 20 | In [7]: list(more_itertools.chunked(data, 3)) 21 | Out[7]: [[1, 2, 3], [4, 5, 6], [7]] 22 | 23 | divide 24 | ~~~~~~ 25 | 26 | Разбивает итерируемый объект на n частей: 27 | 28 | .. code:: python 29 | 30 | more_itertools.divide(n, iterable) 31 | 32 | Пример: 33 | 34 | .. code:: python 35 | 36 | In [25]: data 37 | Out[25]: [1, 2, 3, 4, 5, 6, 7] 38 | 39 | In [26]: g1, g2, g3 = more_itertools.divide(3, data) 40 | 41 | In [27]: list(g1) 42 | Out[27]: [1, 2, 3] 43 | 44 | In [28]: list(g2) 45 | Out[28]: [4, 5] 46 | 47 | In [29]: list(g3) 48 | Out[29]: [6, 7] 49 | 50 | split_at 51 | ~~~~~~~~ 52 | 53 | Генерирует списки элементов из итерируемого объекта, где каждый список разделен 54 | тем значением, для которого pred возвращает True (разедлитель не включен). 55 | 56 | .. code:: python 57 | 58 | more_itertools.split_at(iterable, pred) 59 | 60 | Пример: 61 | 62 | .. code:: python 63 | 64 | import time 65 | 66 | def file_gen(filename): 67 | with open(filename) as f: 68 | for idx, line in enumerate(f): 69 | print(idx) 70 | yield line 71 | 72 | f = file_gen('sh_cdp_neighbors_detail.txt') 73 | for items in more_itertools.split_at(f, lambda x: '------' in x): 74 | print(items) 75 | time.sleep(2) 76 | 77 | 78 | unzip 79 | ~~~~~ 80 | 81 | Выполняет операцию противоположную zip: 82 | 83 | .. code:: python 84 | 85 | more_itertools.unzip(iterable) 86 | 87 | Пример: 88 | 89 | .. code:: python 90 | 91 | In [2]: data = [('status', '*'), 92 | ...: ('network', '1.23.78.0'), 93 | ...: ('netmask', '24'), 94 | ...: ('nexthop', '200.219.145.45'), 95 | ...: ('metric', 'NA'), 96 | ...: ('locprf', 'NA'), 97 | ...: ('weight', '0'), 98 | ...: ('path', '28135 18881 3549 6453 4755 45528'), 99 | ...: ('origin', 'i')] 100 | 101 | In [3]: headers, values = more_itertools.unzip(data) 102 | 103 | In [4]: list(headers) 104 | Out[4]: 105 | ['status', 106 | 'network', 107 | 'netmask', 108 | 'nexthop', 109 | 'metric', 110 | 'locprf', 111 | 'weight', 112 | 'path', 113 | 'origin'] 114 | 115 | In [5]: list(values) 116 | Out[5]: 117 | ['*', 118 | '1.23.78.0', 119 | '24', 120 | '200.219.145.45', 121 | 'NA', 122 | 'NA', 123 | '0', 124 | '28135 18881 3549 6453 4755 45528', 125 | 'i'] 126 | 127 | grouper 128 | ~~~~~~~ 129 | 130 | .. code:: python 131 | 132 | more_itertools.grouper(iterable, n, fillvalue=None) 133 | 134 | Пример: 135 | 136 | .. code:: python 137 | 138 | In [6]: data = [1, 2, 3, 4, 5, 6, 7] 139 | 140 | In [8]: list(more_itertools.grouper(data, 3, 0)) 141 | Out[8]: [(1, 2, 3), (4, 5, 6), (7, 0, 0)] 142 | 143 | partition 144 | ~~~~~~~~~ 145 | 146 | .. code:: python 147 | 148 | more_itertools.partition(pred, iterable) 149 | 150 | 151 | Пример: 152 | 153 | .. code:: python 154 | 155 | In [10]: data = [1, 2, 'a', 'b', 5, 'c', 7] 156 | 157 | In [15]: is_false, is_true = more_itertools.partition(lambda x: str(x).isdigit(), data) 158 | 159 | In [16]: list(is_false) 160 | Out[16]: ['a', 'b', 'c'] 161 | 162 | In [17]: list(is_true) 163 | Out[17]: [1, 2, 5, 7] 164 | -------------------------------------------------------------------------------- /docs/source/book/15_itertools/mit_look.rst: -------------------------------------------------------------------------------- 1 | spy 2 | --- 3 | 4 | .. code:: python 5 | 6 | more_itertools.spy(iterable, n=1) 7 | 8 | Пример 9 | 10 | .. code:: python 11 | 12 | In [19]: def file_gen(filename): 13 | ...: with open(filename) as f: 14 | ...: for idx, line in enumerate(f): 15 | ...: print(idx) 16 | ...: yield line 17 | ...: 18 | 19 | In [20]: f = file_gen('sh_cdp_neighbors_detail.txt') 20 | 21 | In [21]: f 22 | Out[21]: 23 | 24 | In [23]: first, f = more_itertools.spy(f) 25 | 0 26 | 27 | In [24]: first 28 | Out[24]: ['SW1#show cdp neighbors detail\n'] 29 | 30 | In [25]: f 31 | Out[25]: 32 | 33 | In [26]: next(f) 34 | Out[26]: 'SW1#show cdp neighbors detail\n' 35 | 36 | -------------------------------------------------------------------------------- /docs/source/book/15_itertools/mit_summarizing.rst: -------------------------------------------------------------------------------- 1 | Агрегирование значений 2 | ---------------------- 3 | 4 | first и last 5 | ~~~~~~~~~~~~ 6 | 7 | .. code:: python 8 | 9 | more_itertools.first(iterable[, default]) 10 | more_itertools.last(iterable[, default]) 11 | 12 | .. code:: python 13 | 14 | In [42]: data = [1, 2, 'a', 'b', 5, 'c', 7] 15 | 16 | In [43]: more_itertools.first(data) 17 | Out[43]: 1 18 | 19 | In [44]: more_itertools.last(data) 20 | Out[44]: 7 21 | 22 | all_equal 23 | ~~~~~~~~~ 24 | 25 | .. code:: python 26 | 27 | more_itertools.all_equal(iterable) 28 | 29 | .. code:: python 30 | 31 | In [46]: more_itertools.all_equal([1, 1, 1]) 32 | Out[46]: True 33 | 34 | In [47]: more_itertools.all_equal([1, 2, 1]) 35 | Out[47]: False 36 | 37 | -------------------------------------------------------------------------------- /docs/source/book/15_itertools/mit_windowed.rst: -------------------------------------------------------------------------------- 1 | windowed 2 | -------- 3 | 4 | .. code:: python 5 | 6 | more_itertools.windowed(seq, n, fillvalue=None, step=1) 7 | 8 | .. code:: python 9 | 10 | In [33]: windows = more_itertools.windowed(f, 5) 11 | 12 | In [34]: for win in windows: 13 | ...: print(win) 14 | ...: 15 | 0 16 | 1 17 | 2 18 | 3 19 | 4 20 | ('SW1#show cdp neighbors detail\n', '-------------------------\n', 'Device ID: SW2\n', 'Entry address(es):\n', ' IP address: 10.1.1.2\n') 21 | 5 22 | ('-------------------------\n', 'Device ID: SW2\n', 'Entry address(es):\n', ' IP address: 10.1.1.2\n', 'Platform: cisco WS-C2960-8TC-L, Capabilities: Switch IGMP\n') 23 | 6 24 | ('Device ID: SW2\n', 'Entry address(es):\n', ' IP address: 10.1.1.2\n', 'Platform: cisco WS-C2960-8TC-L, Capabilities: Switch IGMP\n', 'Interface: GigabitEthernet1/0/16, Port ID (outgoing port): GigabitEthernet0/1\n') 25 | 7 26 | ('Entry address(es):\n', ' IP address: 10.1.1.2\n', 'Platform: cisco WS-C2960-8TC-L, Capabilities: Switch IGMP\n', 'Interface: GigabitEthernet1/0/16, Port ID (outgoing port): GigabitEthernet0/1\n', 'Holdtime : 164 sec\n') 27 | 8 28 | (' IP address: 10.1.1.2\n', 'Platform: cisco WS-C2960-8TC-L, Capabilities: Switch IGMP\n', 'Interface: GigabitEthernet1/0/16, Port ID (outgoing port): GigabitEthernet0/1\n', 'Holdtime : 164 sec\n', '\n') 29 | 30 | -------------------------------------------------------------------------------- /docs/source/book/15_itertools/more_itertools.rst: -------------------------------------------------------------------------------- 1 | more-itertools 2 | ============== 3 | 4 | .. toctree:: 5 | :maxdepth: 5 6 | :hidden: 7 | 8 | mit_grouping 9 | mit_look 10 | mit_windowed 11 | mit_collapse 12 | mit_summarizing 13 | -------------------------------------------------------------------------------- /docs/source/book/15_itertools/repeat.rst: -------------------------------------------------------------------------------- 1 | repeat 2 | ~~~~~~ 3 | 4 | Функция repeat возвращает итератор, который повторяет указанный объект 5 | бесконечно или указанное количество раз: 6 | 7 | .. code:: python 8 | 9 | itertools.repeat(object[, times]) 10 | 11 | Пример использования repeat для повторения команды: 12 | 13 | .. code:: python 14 | 15 | from itertools import repeat 16 | from concurrent.futures import ThreadPoolExecutor 17 | 18 | import netmiko 19 | import yaml 20 | 21 | 22 | def send_show(device, show): 23 | with netmiko.ConnectHandler(**device) as ssh: 24 | ssh.enable() 25 | result = ssh.send_command(show) 26 | return result 27 | 28 | 29 | with open('devices.yaml') as f: 30 | devices = yaml.safe_load(f) 31 | 32 | with ThreadPoolExecutor(max_workers=3) as executor: 33 | result = executor.map(send_show, devices, repeat('sh clock')) 34 | for device, output in zip(devices, result): 35 | print(device['ip'], output) 36 | 37 | 38 | -------------------------------------------------------------------------------- /docs/source/book/15_itertools/tee.rst: -------------------------------------------------------------------------------- 1 | tee 2 | ~~~ 3 | 4 | Функция tee создает несколько независимых итераторов на основе исходных данных: 5 | 6 | .. code:: python 7 | 8 | itertools.tee(iterable, n=2) 9 | 10 | Пример использования: 11 | 12 | .. code:: python 13 | 14 | In [30]: data = [1,2,3,4,5,6] 15 | 16 | In [31]: data_iter = iter(data) 17 | 18 | In [32]: duplicate_1, duplicate_2 = tee(data_iter) 19 | 20 | In [33]: list(duplicate_1) 21 | Out[33]: [1, 2, 3, 4, 5, 6] 22 | 23 | In [34]: list(duplicate_2) 24 | Out[34]: [1, 2, 3, 4, 5, 6] 25 | 26 | Важная особенность tee - исходный итератор лучше не использовать, 27 | иначе полученные итераторы начнут перебор не с начала: 28 | 29 | .. code:: python 30 | 31 | In [35]: data_iter = iter(data) 32 | 33 | In [36]: duplicate_1, duplicate_2 = tee(data_iter) 34 | 35 | In [37]: next(data_iter) 36 | Out[37]: 1 37 | 38 | In [38]: next(data_iter) 39 | Out[38]: 2 40 | 41 | In [39]: list(duplicate_1) 42 | Out[39]: [3, 4, 5, 6] 43 | 44 | In [40]: list(duplicate_2) 45 | Out[40]: [3, 4, 5, 6] 46 | 47 | При этом перебор одной копии, не влияет на вторую: 48 | 49 | .. code:: python 50 | 51 | In [41]: data_iter = iter(data) 52 | 53 | In [42]: duplicate_1, duplicate_2 = tee(data_iter) 54 | 55 | In [43]: next(duplicate_1) 56 | Out[43]: 1 57 | 58 | In [44]: next(duplicate_1) 59 | Out[44]: 2 60 | 61 | In [45]: list(duplicate_1) 62 | Out[45]: [3, 4, 5, 6] 63 | 64 | In [46]: list(duplicate_2) 65 | Out[46]: [1, 2, 3, 4, 5, 6] 66 | 67 | -------------------------------------------------------------------------------- /docs/source/book/15_itertools/zip_longest.rst: -------------------------------------------------------------------------------- 1 | zip_longest 2 | ~~~~~~~~~~~ 3 | 4 | Функция zip_longest работает аналогично встроенной функции zip, 5 | но не останавливается на самом коротком итерируемом объекте. 6 | 7 | .. code:: python 8 | 9 | itertools.zip_longest(*iterables, fillvalue=None) 10 | 11 | Пример использования: 12 | 13 | .. code:: python 14 | 15 | In [20]: list(zip([1,2,3,4,5], [10,20])) 16 | Out[20]: [(1, 10), (2, 20)] 17 | 18 | In [21]: list(zip_longest([1,2,3,4,5], [10,20])) 19 | Out[21]: [(1, 10), (2, 20), (3, None), (4, None), (5, None)] 20 | 21 | In [22]: list(zip_longest([1,2,3,4,5], [10,20], fillvalue=0)) 22 | Out[22]: [(1, 10), (2, 20), (3, 0), (4, 0), (5, 0)] 23 | 24 | -------------------------------------------------------------------------------- /docs/source/book/16_asyncio_basics/coroutine_mechanics.rst: -------------------------------------------------------------------------------- 1 | Особенности работы сопрограмм 2 | ----------------------------- 3 | 4 | `Сопрограмма `__ 5 | это `awaitable `__ 6 | объект. Сопрограммы поддерживают такие методы: 7 | 8 | * ``coroutine.send(value)`` 9 | * ``coroutine.throw(type[, value[, traceback]])`` 10 | * ``coroutine.close()`` 11 | 12 | Все эти методы не нужно вызывать напрямую, это делает цикл событий и задачи. 13 | 14 | Аналогичные методы есть у генераторов, однако сопрограммы не поддерживают итерацию 15 | напрямую. 16 | 17 | send 18 | ~~~~ 19 | 20 | .. code:: python 21 | 22 | In [1]: async def do_thing(arg1): 23 | ...: print('do_thing', arg1) 24 | ...: return 42 25 | ...: 26 | 27 | In [2]: type(do_thing) 28 | Out[2]: function 29 | 30 | In [4]: my_coroutine = do_thing(4) 31 | 32 | In [5]: my_coroutine 33 | Out[5]: 34 | 35 | In [6]: type(my_coroutine) 36 | Out[6]: coroutine 37 | 38 | 39 | Для инициализации сопрограммы, ей передают None с помощью метода send: 40 | 41 | .. code:: python 42 | 43 | In [8]: my_coroutine.send(None) 44 | do_thing 4 45 | --------------------------------------------------------------------------- 46 | StopIteration Traceback (most recent call last) 47 | in 48 | ----> 1 my_coroutine.send(None) 49 | 50 | StopIteration: 42 51 | 52 | Фактически это никогда не нужно будет делать руками, так как цикл событий 53 | передает это автоматически. 54 | 55 | .. code:: python 56 | 57 | In [12]: my_coroutine = do_thing(4) 58 | 59 | In [13]: try: 60 | ...: my_coroutine.send(None) 61 | ...: except StopIteration as exc: 62 | ...: print('Result:', exc.value) 63 | ...: 64 | do_thing 4 65 | Result: 42 66 | 67 | 68 | throw 69 | ~~~~~ 70 | 71 | .. code:: python 72 | -------------------------------------------------------------------------------- /docs/source/book/16_asyncio_basics/draft_notes.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Синхронный (блокирующий) 6 | Асинхронный (неблокирующий) 7 | 8 | 9 | При работе с потоками, ОС решает когда надо делать переключение и когда вернуться 10 | к потоку. Для создания каждого потока нужно выделить память. Плюс. переключение между 11 | потоками тоже занимает время, пусть небольшое, но оно может накапливаться в заметное увеличение времени. 12 | При работе с потоками работа функции может быть прервана в любое время и из-за 13 | этого возникает масса проблем характерных для потоков, например: race conditions, dead locks. 14 | 15 | Асинхронное программирование отличается тем, что операции ввода-вывода неблокирующие. 16 | Вместо ожидания ответа на какую-то операцию, асинхронный вариант, например, 17 | отправляет команду на оборудование и говорит "позовите меня, когда будет ответ". 18 | Всеми операциями в асинхронном программировании управляет event loop. Именно 19 | event loop ждет разных событий ввода-вывода и вызывает соответствующие сопрограммы. 20 | 21 | При работе с asyncio используется кооперативная многозадачность - все участники 22 | взаимодействия должны сами отдавать управление. 23 | 24 | -------------------------------------------------------------------------------- /docs/source/book/16_asyncio_basics/further_reading.rst: -------------------------------------------------------------------------------- 1 | Дополнительные материалы 2 | ------------------------ 3 | 4 | Документация: 5 | 6 | * `Модуль asyncio `__ 7 | 8 | Статьи: 9 | 10 | * `Overview of Async IO in Python 3.7 `__. `Перевод статьи `__ 11 | * `Asynchronous Python `__, `Вторая часть `__ - статья о том зачем asyncio нужен, чем отличается от потоков и немного истории 12 | * `Относительно простое объяснение зачем нужен asyncio `__ 13 | * `Multiprocessing VS Threading VS AsyncIO in Python `__ 14 | * `What are the advantages of asyncio over threads? `__ 15 | -------------------------------------------------------------------------------- /docs/source/book/16_asyncio_basics/index.rst: -------------------------------------------------------------------------------- 1 | .. raw:: latex 2 | 3 | \newpage 4 | 5 | 16. Основы модуля asyncio 6 | ========================= 7 | 8 | Модуль asyncio можно разделить на две части: высокоуровневый интерфейс для пользователей, 9 | которые пишут программы и низкоуровневый интерфейс для авторов модулей, библиотек 10 | и фреймворков на основе asyncio. В книге рассматривается только первая часть и чаще всего, 11 | вторая не понадобится. 12 | 13 | .. note:: 14 | 15 | На мой взгляд, asyncio это одна из тех тем, которые сложно понять 16 | по одному источнику. Поэтому я очень рекомендую читать/смотреть разные 17 | материалы, если вы действительно хотите в этом разобраться. Если нужно 18 | лишь общее понимание, может хватить и одного источника информации. 19 | 20 | .. toctree:: 21 | :maxdepth: 1 22 | :hidden: 23 | 24 | asyncio_vs_threads 25 | terms 26 | basics 27 | run_awaitables 28 | further_reading 29 | ../../exercises/16_exercises.rst 30 | -------------------------------------------------------------------------------- /docs/source/book/16_asyncio_basics/terms.rst: -------------------------------------------------------------------------------- 1 | Терминология 2 | ------------ 3 | 4 | * цикл событий (event loop) - менеджер управляющий различными задачами и сопрограммами 5 | * сопрограмма (coroutine) в asyncio - специальный тип функций, которые создаются 6 | со словом async перед определением. 7 | * future - это объект, который представляет отложенное вычисление. 8 | * задача (task) - отвечает за управление работой сопрограммы, запрашивает статус 9 | сопрограммы. Является подклассом Future. 10 | 11 | Блокирующий/неблокирующий вызов 12 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 13 | 14 | .. note:: 15 | 16 | Технически любой вызов функции блокирующий, но тут обсуждается терминология относящаяся 17 | к операциям ввода-вывода. 18 | 19 | Блокирующий вызов останавливает работу программы, пока не будет получен результат вызова. 20 | Например, ``subprocess.run`` это блокирующий вызов и если вызвать, к примеру, 21 | ping какого-то адреса, то строка с subprocess.run будет блокировать выполнение 22 | скрипта пока не отработает ping. 23 | 24 | Неблокирующий вызов возвращает какой-то объект сразу и, как правило, 25 | предполагается что позже надо будет еще раз вызывать какой-то метод этого 26 | объекта, чтобы получить результат вызова. Например, ``subprocess.Popen``: 27 | 28 | .. code:: python 29 | 30 | import subprocess 31 | 32 | def ping_ip_addresses(ip_list): 33 | reachable = [] 34 | unreachable = [] 35 | result = [] 36 | for ip in ip_list: 37 | p = subprocess.Popen( 38 | ["ping", "-c", "3", "-n", ip], 39 | stdout=subprocess.PIPE, 40 | stderr=subprocess.PIPE, 41 | ) 42 | result.append(p) 43 | for ip, p in zip(ip_list, result): 44 | returncode = p.wait() 45 | if returncode == 0: 46 | reachable.append(ip) 47 | else: 48 | unreachable.append(ip) 49 | return reachable, unreachable 50 | 51 | Тут вызов ``subprocess.Popen`` неблокирующий и с помощью этого вызова 52 | запущены несколько ping, но затем, чтобы получить результат, надо 53 | вызвать блокирующий ``p.wait``. 54 | 55 | Awaitables 56 | ~~~~~~~~~~ 57 | 58 | Awaitables (ожидаемые объекты) - это объекты, которые можно использовать в выражении 59 | вместе с ``await``. Три базовых типа awaitables: 60 | 61 | * сопрограммы (coroutines) 62 | * задачи (tasks) 63 | * future 64 | 65 | 66 | Coroutine (сопрограмма) 67 | ~~~~~~~~~~~~~~~~~~~~~~~ 68 | 69 | Как и с генераторами, различают: 70 | 71 | * функцию сопрограмму - функция, которая создается с помощью ``async def`` 72 | * объект сопрограмму - объект, который возвращается при вызове функции сопрограммы 73 | 74 | Функции-сопрограммы возвращают объекты сопрограммы, которые запускаются 75 | менеджером (циклом событий). Сопрограмма может периодически прерывать выполнение 76 | и отдавать управление менеджеру, но при этом она не теряет состояние. 77 | 78 | Task (задача) 79 | ~~~~~~~~~~~~~ 80 | 81 | Объекты класса Task используются для запуска сопрограмм в цикле событий и для отслеживания 82 | их состояния. Как только сопрограмма "обернута" в Task, например, с помощью функции 83 | ``asyncio.create_task``, сопрограмма автоматически запущена для выполнения. 84 | 85 | 86 | asyncio.Future 87 | ~~~~~~~~~~~~~~ 88 | 89 | Future - это специальный низкоуровневый объект, который представляет отложенное 90 | вычисление асинхронных операций. Чаще всего, при работе с модулем asyncio, нет 91 | необходимости создавать Future напрямую, но некоторые функции могут возвращать Future. 92 | Task является подклассом Future. 93 | Менеджер может следить за future и ожидать их завершения. 94 | -------------------------------------------------------------------------------- /docs/source/book/16_asyncio_basics/useful_functions.rst: -------------------------------------------------------------------------------- 1 | Полезные функции 2 | ================ 3 | 4 | asyncio.wait_for 5 | ---------------- 6 | 7 | Функция ``asyncio.wait_for``: 8 | 9 | .. code:: python 10 | 11 | coroutine asyncio.wait_for(aw, timeout) 12 | 13 | asyncio.current_task 14 | -------------------- 15 | -------------------------------------------------------------------------------- /docs/source/book/17_async_libraries/api_ssh_read_stream.rst: -------------------------------------------------------------------------------- 1 | class asyncssh.SSHReader[source] 2 | SSH read stream handler 3 | 4 | channel 5 | The SSH channel associated with this stream 6 | 7 | 8 | logger 9 | The SSH logger associated with this stream 10 | 11 | 12 | at_eof()[source] 13 | Return whether the stream is at EOF 14 | 15 | This method returns True when EOF has been received and all data in 16 | the stream has been read. 17 | 18 | 19 | read(n=-1)[source] 20 | Read data from the stream 21 | 22 | This method is a coroutine which reads up to n bytes or characters from the stream. 23 | If n is not provided or set to -1, it reads until EOF or a signal is received. 24 | 25 | If EOF is received and the receive buffer is empty, an empty bytes or str object 26 | is returned. 27 | 28 | If the next data in the stream is a signal, 29 | the signal is delivered as a raised exception. 30 | 31 | Note Unlike traditional asyncio stream readers, the data will be delivered as 32 | either bytes or a str depending on whether an encoding was specified when 33 | the underlying channel was opened. 34 | 35 | readline()[source] 36 | Read one line from the stream 37 | 38 | This method is a coroutine which reads one line, ending in 'n'. 39 | 40 | If EOF is received before 'n' is found, the partial line is returned. 41 | If EOF is received and the receive buffer is empty, an empty bytes or 42 | str object is returned. 43 | 44 | If the next data in the stream is a signal, the signal is delivered 45 | as a raised exception. 46 | 47 | Note In Python 3.5 and later, SSHReader objects can also be used as 48 | async iterators, returning input data one line at a time. 49 | 50 | readuntil(separator) 51 | Read data from the stream until separator is seen 52 | 53 | This method is a coroutine which reads from the stream until the requested 54 | separator is seen. If a match is found, the returned data will include the 55 | separator at the end. 56 | 57 | The separator argument can be either a single bytes or str value or a 58 | sequence of multiple values to match against, returning data as soon as any 59 | of the separators are found in the stream. 60 | 61 | If EOF or a signal is received before a match occurs, an IncompleteReadError 62 | is raised and its partial attribute will contain the data in the stream prior 63 | to the EOF or signal. 64 | 65 | If the next data in the stream is a signal, the signal is delivered as a 66 | raised exception. 67 | -------------------------------------------------------------------------------- /docs/source/book/17_async_libraries/api_ssh_write_stream.rst: -------------------------------------------------------------------------------- 1 | SSHWriter 2 | class asyncssh.SSHWriter 3 | SSH write stream handler 4 | 5 | channel 6 | The SSH channel associated with this stream 7 | 8 | 9 | logger 10 | The SSH logger associated with this stream 11 | 12 | 13 | get_extra_info(name, default=None) 14 | Return additional information about this stream 15 | 16 | This method returns extra information about the channel associated with 17 | this stream. See get_extra_info() on SSHClientChannel for additional information. 18 | 19 | 20 | can_write_eof() 21 | Return whether the stream supports write_eof() 22 | 23 | 24 | drain() 25 | Wait until the write buffer on the channel is flushed 26 | 27 | This method is a coroutine which blocks the caller if the stream is currently 28 | paused for writing, returning when enough data has been sent on the channel to 29 | allow writing to resume. This can be used to avoid buffering an excessive 30 | amount of data in the channel’s send buffer. 31 | 32 | 33 | write(data) 34 | Write data to the stream 35 | 36 | This method writes bytes or characters to the stream. 37 | 38 | Note Unlike traditional asyncio stream writers, the data must be supplied as either 39 | bytes or a str depending on whether an encoding was specified when the underlying 40 | channel was opened. 41 | 42 | writelines(list_of_data) 43 | Write a collection of data to the stream 44 | 45 | 46 | write_eof() 47 | Write EOF on the channel 48 | 49 | This method sends an end-of-file indication on the channel, after which no 50 | more data can be written. 51 | 52 | Note On an SSHServerChannel where multiple output streams are created, 53 | writing EOF on one stream signals EOF for all of them, since it applies to 54 | the channel as a whole. 55 | 56 | close() 57 | Close the channel 58 | 59 | Note After this is called, no data can be read or written from any of the streams 60 | associated with this channel. 61 | 62 | is_closing() 63 | Return if the stream is closing or is closed 64 | 65 | 66 | wait_closed() 67 | Wait until the stream is closed 68 | 69 | This should be called after close() to wait until the underlying connection is closed. 70 | -------------------------------------------------------------------------------- /docs/source/book/17_async_libraries/async_timeout.rst: -------------------------------------------------------------------------------- 1 | async-timeout 2 | ============= 3 | 4 | `Установка `__ 5 | 6 | :: 7 | 8 | $ pip install async-timeout 9 | 10 | 11 | Пример использования: 12 | 13 | .. code:: python 14 | 15 | ssh_coroutine = asyncssh.connect('192.168.100.1', username='cisco', password='cisco') 16 | async with timeout(20): 17 | ssh = await asyncio.wait_for(ssh_coroutine, timeout=10) 18 | writer, reader, stderr = await ssh.open_session( 19 | term_type="Dumb", term_size=(200, 24)) 20 | 21 | Может использоваться в ``async with`` и ``with``. 22 | Работает `быстрее чем ``asyncio.wait_for`` `__ потому что wait_for создает новую задачу. 23 | -------------------------------------------------------------------------------- /docs/source/book/17_async_libraries/further_reading.rst: -------------------------------------------------------------------------------- 1 | Дополнительные материалы 2 | ------------------------ 3 | 4 | Документация: 5 | 6 | * `Модуль asyncio `__ 7 | * `asyncssh `__ 8 | * `Примеры asyncssh `__ 9 | * `scrapli `__ 10 | * `netdev `__ 11 | * `aiofiles `__ 12 | * `aiohttp `__ 13 | * `async-timeout `__ 14 | 15 | Полезные ссылки: 16 | 17 | * `The current state and future of Asyncio `__ 18 | * `Другие модули async `__ 19 | * `aiojobs `__ 20 | * `Structured concurrency in Python with AnyIO `__ 21 | * `Designing Libraries for Async and Sync I/O `__ 22 | 23 | 24 | Примеры кода: 25 | 26 | * `asyncssh `__ 27 | * `scrapli `__ 28 | * `netdev `__ 29 | -------------------------------------------------------------------------------- /docs/source/book/17_async_libraries/index.rst: -------------------------------------------------------------------------------- 1 | .. raw:: latex 2 | 3 | \newpage 4 | 5 | 17. Модули async 6 | ================ 7 | 8 | Почти все модули, которые работали в многопоточном варианте, не будут работать 9 | с asyncio, так как они будут блокировать работу и не будут отдавать управление 10 | циклу событий. 11 | 12 | В этом разделе рассматриваются модули: 13 | 14 | * asyncssh 15 | * scrapli 16 | * netdev 17 | 18 | .. toctree:: 19 | :maxdepth: 1 20 | :hidden: 21 | 22 | asyncssh 23 | scrapli 24 | netdev 25 | further_reading 26 | ../../exercises/17_exercises.rst 27 | -------------------------------------------------------------------------------- /docs/source/book/18_using_asyncio/async_class.rst: -------------------------------------------------------------------------------- 1 | Создание классов с asyncio 2 | ========================== 3 | 4 | В целом асинхронные классы пишутся так же, как синхронные, но есть несколько 5 | отличий: 6 | 7 | * особенности работы ``__init__`` 8 | * некоторые специальные методы имеют отдельные версии для асинхронных классов, 9 | например, ``__aenter__``, ``__aexit__``, ``__anext__``, ``__aiter__`` 10 | 11 | 12 | Методы класса могут быть сопрограммами или обычными функциями. 13 | 14 | Метод __init__ 15 | --------------- 16 | 17 | В синхронном варианте, если создавать класс который подключается 18 | по SSH, то само подключение обычно будет выполняться в методе ``__init__``. 19 | В асинхронных классах так сделать не получится, так как для выполнения 20 | подключения в ``__init__``, надо сделать init сопрограммой. Так как init 21 | должен возвращать None, если сделать init сопрограммой, возникнет ошибка: 22 | 23 | .. code:: python 24 | 25 | In [3]: class ConnectSSH: 26 | ...: async def __init__(self): 27 | ...: await asyncio.sleep(0.1) 28 | ...: 29 | 30 | In [5]: r1 = ConnectSSH() 31 | --------------------------------------------------------------------------- 32 | TypeError Traceback (most recent call last) 33 | in 34 | ----> 1 r1 = ConnectSSH() 35 | 36 | TypeError: __init__() should return None, not 'coroutine' 37 | 38 | 39 | Распространенный вариант решения проблемы - создание отдельного метода для подключения. 40 | -------------------------------------------------------------------------------- /docs/source/book/18_using_asyncio/event_loop.rst: -------------------------------------------------------------------------------- 1 | Цикл событий (Event loop) 2 | ------------------------- 3 | 4 | Функция asyncio.get_event_loop возвращает цикл событий. Если цикл событий еще не был создан, 5 | создает новый и возвращает его: 6 | 7 | .. code:: python 8 | 9 | loop = asyncio.get_event_loop() 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/source/book/18_using_asyncio/index.rst: -------------------------------------------------------------------------------- 1 | .. raw:: latex 2 | 3 | \newpage 4 | 5 | 18. Использование модуля asyncio 6 | ================================ 7 | 8 | * async class 9 | * loop 10 | * wait 11 | * cancel 12 | * Генераторы 13 | * list comprehensions 14 | * async with 15 | * async for 16 | * run in thread 17 | 18 | 19 | .. toctree:: 20 | :maxdepth: 1 21 | :hidden: 22 | 23 | async_class 24 | to_thread 25 | ../../exercises/18_exercises.rst 26 | -------------------------------------------------------------------------------- /docs/source/book/18_using_asyncio/task_vs_future.rst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natenka/advpyneng-book/6a66da6307a01add9c1f81d0bb97ea18f40b6754/docs/source/book/18_using_asyncio/task_vs_future.rst -------------------------------------------------------------------------------- /docs/source/book/18_using_asyncio/to_thread.rst: -------------------------------------------------------------------------------- 1 | asyncio.to_thread 2 | ================= 3 | 4 | Сопрограмма ``asyncio.to_thread`` позволяет запустить блокирующую операцию 5 | в потоке. 6 | 7 | Эта сопрограмма появилась в Python 3.9, до этого использовалась ``loop.run_in_executor``. 8 | При этом to_thread это по сути `обертка вокруг loop.run_in_executor `__ 9 | с использованием ThreadPoolExecutor по умолчанию, с максимальным количеством потоков 10 | по умолчанию: 11 | 12 | .. code:: python 13 | 14 | async def to_thread(func, /, *args, **kwargs): 15 | """Asynchronously run function *func* in a separate thread. 16 | Any *args and **kwargs supplied for this function are directly passed 17 | to *func*. Also, the current :class:`contextvars.Context` is propogated, 18 | allowing context variables from the main thread to be accessed in the 19 | separate thread. 20 | Return a coroutine that can be awaited to get the eventual result of *func*. 21 | """ 22 | loop = events.get_running_loop() 23 | ctx = contextvars.copy_context() 24 | func_call = functools.partial(ctx.run, func, *args, **kwargs) 25 | return await loop.run_in_executor(None, func_call) 26 | 27 | 28 | .. note:: 29 | 30 | Начиная с версии Python 3.8 значение по умолчанию для max_workers высчитывается 31 | так ``min(32, os.cpu_count() + 4)``. 32 | 33 | -------------------------------------------------------------------------------- /docs/source/book/Part_I.rst: -------------------------------------------------------------------------------- 1 | I. Полезные модули 2 | ################## 3 | 4 | 5 | .. toctree:: 6 | :maxdepth: 1 7 | 8 | 01_pytest_basics/index.rst 9 | 02_type_annotations/index.rst 10 | 03_code_formatters/index.rst 11 | 04_click/index.rst 12 | 05_logging/index.rst 13 | 06_pdb/index.rst 14 | -------------------------------------------------------------------------------- /docs/source/book/Part_II.rst: -------------------------------------------------------------------------------- 1 | II. Декораторы 2 | ############### 3 | 4 | 5 | .. toctree:: 6 | :maxdepth: 1 7 | 8 | 06_functions/index.rst 9 | 07_closure/index.rst 10 | 08_decorators/index.rst 11 | -------------------------------------------------------------------------------- /docs/source/book/Part_III.rst: -------------------------------------------------------------------------------- 1 | III. Объектно-ориентированное программирование 2 | ############################################ 3 | 4 | .. toctree:: 5 | :maxdepth: 1 6 | 7 | 09_oop_basics/index.rst 8 | 10_oop_special_methods/index.rst 9 | 11_oop_method_decorators/index.rst 10 | 12_oop_inheritance/index.rst 11 | 13_data_classes/index.rst 12 | -------------------------------------------------------------------------------- /docs/source/book/Part_IV.rst: -------------------------------------------------------------------------------- 1 | IV. Генераторы 2 | ############### 3 | 4 | 5 | .. toctree:: 6 | :maxdepth: 1 7 | 8 | 14_generators/index.rst 9 | 15_itertools/index.rst 10 | -------------------------------------------------------------------------------- /docs/source/book/Part_V.rst: -------------------------------------------------------------------------------- 1 | V. Основы asyncio 2 | ################## 3 | 4 | Разделы 16-18 переехали в отдельную книгу `Основы asyncio для сетевых инженеров `__ 5 | 6 | Если вы делаете задания из курса "Advanced Python для сетевых инженеров", то соответствие разделов такое: 7 | 8 | * `17 раздел заданий `__ - 1, 2 разделы книги 9 | * `18 раздел заданий `__ - 3, 4 и 5 разделы книги 10 | -------------------------------------------------------------------------------- /docs/source/book/Part_VI.rst: -------------------------------------------------------------------------------- 1 | VI. Дополнительная информация 2 | ############################# 3 | 4 | В этом разделе собрана информация, которая не вошла в основные разделы книги, 5 | но которая, тем не менее, может быть полезна. 6 | 7 | .. toctree:: 8 | :maxdepth: 2 9 | 10 | additional_info/index 11 | -------------------------------------------------------------------------------- /docs/source/book/additional_info/collections/chainmap.rst: -------------------------------------------------------------------------------- 1 | collections.ChainMap 2 | -------------------- 3 | 4 | ChainMap - класс похожий на словарь для создания единого интерфейса для доступа к нескольким словарям 5 | 6 | .. code:: python 7 | 8 | class collections.ChainMap(*maps) 9 | 10 | 11 | Методы: 12 | 13 | * ``maps`` 14 | * ``new_child(m=None, **kwargs)`` 15 | * ``parents`` 16 | 17 | .. code:: python 18 | 19 | In [1]: from collections import ChainMap 20 | 21 | In [2]: r1 = { 22 | ...: "host": "192.168.100.1", 23 | ...: "auth_username": "cisco", 24 | ...: "auth_password": "cisco", 25 | ...: "auth_secondary": "cisco", 26 | ...: "platform": "cisco_iosxe", 27 | ...: "timeout_socket": 15, 28 | ...: } 29 | 30 | In [3]: default_params = { 31 | ...: "auth_strict_key": False, 32 | ...: "timeout_socket": 5, 33 | ...: "timeout_transport": 10, 34 | ...: } 35 | 36 | In [4]: scrapli_params = ChainMap(r1, default_params) 37 | 38 | In [5]: pprint(scrapli_params) 39 | ChainMap({'auth_password': 'cisco', 40 | 'auth_secondary': 'cisco', 41 | 'auth_username': 'cisco', 42 | 'host': '192.168.100.1', 43 | 'platform': 'cisco_iosxe', 44 | 'timeout_socket': 15}, 45 | {'auth_strict_key': False, 46 | 'timeout_socket': 5, 47 | 'timeout_transport': 10}) 48 | 49 | In [6]: scrapli_params["timeout_socket"] 50 | Out[6]: 15 51 | 52 | In [7]: scrapli_params["timeout_transport"] 53 | Out[7]: 10 54 | 55 | In [8]: scrapli_params["host"] 56 | Out[8]: '192.168.100.1' 57 | 58 | 59 | maps, parents 60 | ~~~~~~~~~~~~~ 61 | 62 | .. code:: python 63 | 64 | In [12]: scrapli_params.maps 65 | Out[12]: 66 | [{'host': '192.168.100.1', 67 | 'auth_username': 'cisco', 68 | 'auth_password': 'cisco', 69 | 'auth_secondary': 'cisco', 70 | 'platform': 'cisco_iosxe', 71 | 'timeout_socket': 15}, 72 | {'auth_strict_key': False, 'timeout_socket': 5, 'timeout_transport': 10}] 73 | 74 | In [13]: scrapli_params.parents 75 | Out[13]: ChainMap({'auth_strict_key': False, 'timeout_socket': 5, 'timeout_transport': 10}) 76 | 77 | 78 | ``new_child`` 79 | ~~~~~~~~~~~~~ 80 | 81 | .. code:: python 82 | 83 | In [18]: pprint(scrapli_params) 84 | ChainMap({'auth_password': 'cisco', 85 | 'auth_secondary': 'cisco', 86 | 'auth_username': 'cisco', 87 | 'host': '192.168.100.1', 88 | 'platform': 'cisco_iosxe', 89 | 'timeout_socket': 15}, 90 | {'auth_strict_key': False, 91 | 'timeout_socket': 5, 92 | 'timeout_transport': 10}) 93 | 94 | In [19]: updated_info = {"host": "10.1.1.1"} 95 | 96 | In [20]: new_params = scrapli_params.new_child(updated_info) 97 | 98 | In [22]: pprint(new_params) 99 | ChainMap({'host': '10.1.1.1'}, 100 | {'auth_password': 'cisco', 101 | 'auth_secondary': 'cisco', 102 | 'auth_username': 'cisco', 103 | 'host': '192.168.100.1', 104 | 'platform': 'cisco_iosxe', 105 | 'timeout_socket': 15}, 106 | {'auth_strict_key': False, 107 | 'timeout_socket': 5, 108 | 'timeout_transport': 10}) 109 | 110 | In [23]: new_params["host"] 111 | Out[23]: '10.1.1.1' 112 | 113 | In [25]: pprint(scrapli_params) 114 | ChainMap({'auth_password': 'cisco', 115 | 'auth_secondary': 'cisco', 116 | 'auth_username': 'cisco', 117 | 'host': '192.168.100.1', 118 | 'platform': 'cisco_iosxe', 119 | 'timeout_socket': 15}, 120 | {'auth_strict_key': False, 121 | 'timeout_socket': 5, 122 | 'timeout_transport': 10}) 123 | 124 | -------------------------------------------------------------------------------- /docs/source/book/additional_info/collections/counter.rst: -------------------------------------------------------------------------------- 1 | Counter 2 | -------------------- 3 | 4 | Counter - подкласс словаря для подсчета хешируемых объектов 5 | 6 | .. code:: python 7 | 8 | class collections.Counter([iterable-or-mapping]) 9 | 10 | 11 | * ``elements()`` 12 | * ``most_common([n])`` 13 | * ``subtract([iterable-or-mapping])`` 14 | * ``total()`` (New in version 3.10) 15 | * ``fromkeys(iterable)`` 16 | 17 | 18 | .. code:: python 19 | 20 | In [2]: c1 = Counter("aaabbbccccddddd") 21 | 22 | In [3]: c1 23 | Out[3]: Counter({'a': 3, 'b': 3, 'c': 4, 'd': 5}) 24 | 25 | In [4]: list(c1.elements()) 26 | Out[4]: ['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'c', 'c', 'd', 'd', 'd', 'd', 'd'] 27 | 28 | In [5]: c1.most_common(2) 29 | Out[5]: [('d', 5), ('c', 4)] 30 | 31 | Операции с Counter 32 | 33 | .. code:: python 34 | 35 | In [3]: c1 36 | Out[3]: Counter({'a': 3, 'b': 3, 'c': 4, 'd': 5}) 37 | 38 | In [6]: c2 = Counter(a=1, d=5) 39 | 40 | In [7]: c2 41 | Out[7]: Counter({'a': 1, 'd': 5}) 42 | 43 | In [9]: c1 - c2 44 | Out[9]: Counter({'a': 2, 'b': 3, 'c': 4}) 45 | 46 | In [11]: c1.subtract(c2) 47 | 48 | In [12]: c1 + c2 49 | Out[12]: Counter({'a': 3, 'b': 3, 'c': 4, 'd': 5}) 50 | 51 | 52 | Пример использования 53 | ~~~~~~~~~~~~~~~~~~~~ 54 | 55 | .. code:: python 56 | 57 | import re 58 | import string 59 | from pprint import pprint 60 | from collections import Counter 61 | 62 | 63 | with open("text.txt") as f: 64 | content = f.read() 65 | 66 | clear_text = re.sub(f"[{string.punctuation}»«]", " ", content.lower()) 67 | words = re.split("\s+", clear_text) 68 | stats = Counter(words) 69 | 70 | pprint(stats.most_common(20)) 71 | 72 | Результат 73 | 74 | .. code:: python 75 | 76 | [('с', 9), 77 | ('не', 8), 78 | ('потоков', 6), 79 | ('в', 6), 80 | ('из', 5), 81 | ('это', 5), 82 | ('print', 5), 83 | ('при', 4), 84 | ('потоками', 4), 85 | ('если', 4), 86 | ('будет', 4), 87 | ('на', 4), 88 | ('работает', 4), 89 | ('работать', 3), 90 | ('и', 3), 91 | ('например', 3), 92 | ('нормально', 3), 93 | ('что', 3), 94 | ('сообщения', 3), 95 | ('может', 3)] 96 | 97 | -------------------------------------------------------------------------------- /docs/source/book/additional_info/collections/defaultdict.rst: -------------------------------------------------------------------------------- 1 | collections.defaultdict 2 | ------------------------ 3 | 4 | defaultdict - подкласс словаря, который вызывает указанную функцию для подстановки несуществующих значений 5 | 6 | .. code:: python 7 | 8 | class collections.defaultdict(default_factory=None, /[, ...]) 9 | 10 | 11 | .. code:: python 12 | 13 | In [2]: from collections import defaultdict 14 | 15 | In [3]: data = "some very important text" 16 | 17 | In [4]: d = defaultdict(int) 18 | 19 | In [5]: d 20 | Out[5]: defaultdict(int, {}) 21 | 22 | In [6]: for letter in data: 23 | ...: d[letter] += 1 24 | ...: 25 | 26 | In [7]: d 27 | Out[7]: 28 | defaultdict(int, 29 | {'s': 1, 30 | 'o': 2, 31 | 'm': 2, 32 | 'e': 3, 33 | ' ': 3, 34 | 'v': 1, 35 | 'r': 2, 36 | 'y': 1, 37 | 'i': 1, 38 | 'p': 1, 39 | 't': 4, 40 | 'a': 1, 41 | 'n': 1, 42 | 'x': 1}) 43 | 44 | In [9]: sorted(d.items(), key=lambda x: x[-1], reverse=True) 45 | Out[9]: 46 | [('t', 4), 47 | ('e', 3), 48 | (' ', 3), 49 | ('o', 2), 50 | ('m', 2), 51 | ('r', 2), 52 | ('s', 1), 53 | ('v', 1), 54 | ('y', 1), 55 | ('i', 1), 56 | ('p', 1), 57 | ('a', 1), 58 | ('n', 1), 59 | ('x', 1)] 60 | 61 | 62 | Пример использования 63 | ~~~~~~~~~~~~~~~~~~~~~~~ 64 | 65 | config_sw1.txt 66 | 67 | :: 68 | 69 | interface FastEthernet0/0 70 | switchport mode access 71 | switchport access vlan 10 72 | ! 73 | interface FastEthernet0/1 74 | switchport trunk encapsulation dot1q 75 | switchport trunk allowed vlan 100,200 76 | switchport mode trunk 77 | ! 78 | interface FastEthernet0/2 79 | switchport mode access 80 | switchport access vlan 20 81 | ! 82 | interface FastEthernet0/3 83 | switchport trunk encapsulation dot1q 84 | switchport trunk allowed vlan 100,300,400,500,600 85 | switchport mode trunk 86 | 87 | 88 | .. code:: python 89 | 90 | {'FastEthernet0/0': ['switchport mode access', 'switchport access vlan 10'], 91 | 'FastEthernet0/1': ['switchport trunk encapsulation dot1q', 92 | 'switchport trunk allowed vlan 100,200', 93 | 'switchport mode trunk'], 94 | 'FastEthernet0/2': ['switchport mode access', 'switchport access vlan 20'], 95 | 'FastEthernet0/3': ['switchport trunk encapsulation dot1q', 96 | 'switchport trunk allowed vlan 100,300,400,500,600', 97 | 'switchport mode trunk'], 98 | 'FastEthernet1/0': ['switchport mode access', 'switchport access vlan 20'], 99 | 'FastEthernet1/1': ['switchport mode access', 'switchport access vlan 30'], 100 | 'FastEthernet1/2': ['switchport trunk encapsulation dot1q', 101 | 'switchport trunk allowed vlan 400,500,600', 102 | 'switchport mode trunk']} 103 | 104 | .. code:: python 105 | 106 | from pprint import pprint 107 | from collections import defaultdict 108 | 109 | 110 | def get_ip_from_cfg(filename): 111 | result = defaultdict(list) 112 | with open(filename) as f: 113 | for line in f: 114 | if line.startswith("interface"): 115 | intf = line.split()[-1] 116 | elif line.startswith(" switchport"): 117 | result[intf].append(line.strip()) 118 | return result 119 | 120 | 121 | if __name__ == "__main__": 122 | pprint(get_ip_from_cfg("config_sw1.txt")) 123 | 124 | -------------------------------------------------------------------------------- /docs/source/book/additional_info/collections/deque.rst: -------------------------------------------------------------------------------- 1 | collections.deque 2 | ----------------- 3 | 4 | Deque поддерживает потокобезопасные, эффективные с точки зрения памяти 5 | добавления и извлечения с обеих сторон двухсторонней очереди с примерно 6 | одинаковой производительностью O(1) в любом направлении. 7 | 8 | .. code:: python 9 | 10 | class collections.deque([iterable[, maxlen]]) 11 | 12 | Методы deque: 13 | 14 | * ``append(x)`` 15 | * ``appendleft(x)`` 16 | * ``clear()`` 17 | * ``copy()`` 18 | * ``count(x)`` 19 | * ``extend(iterable)`` 20 | * ``extendleft(iterable)`` 21 | * ``index(x[, start[, stop]])`` 22 | * ``insert(i, x)`` 23 | * ``pop()`` 24 | * ``popleft()`` 25 | * ``remove(value)`` 26 | * ``reverse()`` 27 | * ``rotate(n=1)`` 28 | * ``maxlen`` 29 | 30 | 31 | append 32 | ~~~~~~ 33 | 34 | .. code:: python 35 | 36 | In [1]: from collections import deque 37 | 38 | In [2]: d = deque([1, 2, 3]) 39 | 40 | In [3]: d.append(4) 41 | 42 | In [4]: d 43 | Out[4]: deque([1, 2, 3, 4]) 44 | 45 | In [5]: d.appendleft(0) 46 | 47 | In [6]: d 48 | Out[6]: deque([0, 1, 2, 3, 4]) 49 | 50 | 51 | pop 52 | ~~~~ 53 | 54 | .. code:: python 55 | 56 | In [7]: d.pop() 57 | Out[7]: 4 58 | 59 | In [9]: d 60 | Out[9]: deque([0, 1, 2, 3]) 61 | 62 | In [10]: d.popleft() 63 | Out[10]: 0 64 | 65 | In [11]: d 66 | Out[11]: deque([1, 2, 3]) 67 | 68 | 69 | index 70 | ~~~~~ 71 | 72 | .. code:: python 73 | 74 | In [12]: d[0] 75 | Out[12]: 1 76 | 77 | In [13]: d[-1] 78 | Out[13]: 3 79 | 80 | 81 | rotate 82 | ~~~~~~ 83 | 84 | .. code:: python 85 | 86 | In [14]: d.rotate(1) 87 | 88 | In [15]: d 89 | Out[15]: deque([3, 1, 2]) 90 | 91 | In [16]: d.rotate(-2) 92 | 93 | In [17]: d 94 | Out[17]: deque([2, 3, 1]) 95 | 96 | 97 | maxlen 98 | ~~~~~~ 99 | 100 | .. code:: python 101 | 102 | In [19]: d = deque([1, 2, 3, 4, 5], maxlen=5) 103 | 104 | In [20]: d 105 | Out[20]: deque([1, 2, 3, 4, 5]) 106 | 107 | In [21]: d.append(6) 108 | 109 | In [22]: d 110 | Out[22]: deque([2, 3, 4, 5, 6]) 111 | 112 | In [23]: d.appendleft(100) 113 | 114 | In [24]: d 115 | Out[24]: deque([100, 2, 3, 4, 5]) 116 | 117 | Пример использования 118 | ~~~~~~~~~~~~~~~~~~~~ 119 | 120 | .. code:: python 121 | 122 | def tail(filename, n=10): 123 | 'Return the last n lines of a file' 124 | with open(filename) as f: 125 | return deque(f, n) 126 | 127 | -------------------------------------------------------------------------------- /docs/source/book/additional_info/collections/index.rst: -------------------------------------------------------------------------------- 1 | .. raw:: latex 2 | 3 | \newpage 4 | 5 | Collections 6 | =============== 7 | 8 | * namedtuple - factory функция для создания подклассов кортежа с именованными атрибутами 9 | * deque - контейнер похожий на список с быстрыми добавляениями и удалениями с двух сторон 10 | * ChainMap - класс похожий на словарь для создания единого интерфейса для доступа к нескольким словарям 11 | * Counter - подкласс словаря для подсчета хешируемых объектов 12 | * OrderedDict 13 | * defaultdict 14 | * UserDict, UserList, UserString 15 | 16 | .. toctree:: 17 | :maxdepth: 1 18 | :hidden: 19 | 20 | time_complexity 21 | namedtuple 22 | deque 23 | chainmap 24 | counter 25 | ordereddict 26 | defaultdict 27 | userlist_userdict_userstr 28 | 29 | -------------------------------------------------------------------------------- /docs/source/book/additional_info/collections/ordereddict.rst: -------------------------------------------------------------------------------- 1 | collections.OrderedDict 2 | ------------------------- 3 | 4 | OrderedDict похож на обычные словари, но имеют некоторые дополнительные возможности, 5 | связанные с операциями упорядочивания. Начиная с Python 3.7, обычные словари 6 | также стали упорядоченными, но при этом в OrderedDict остались несколько полезных возможностей: 7 | 8 | * Обычный dict оптимизирован на операции mapping (getitem, setitem, deleteitem) 9 | * OrderedDict оптимизирован под операции переупорядочивание 10 | * Алгоритмически OrderedDict может обрабатывать частые операции переупорядочения лучше, чем dict 11 | * При сравнении равенства словарей, OrderedDict учитывает соответствие порядка 12 | * У OrderedDict есть метод ``move_to_end()`` для эффективного перемещения элемента в конец словаря 13 | 14 | .. code:: python 15 | 16 | class collections.OrderedDict([items]) 17 | 18 | 19 | * ``popitem(last=True)`` 20 | * ``move_to_end(key, last=True)`` 21 | 22 | .. code:: python 23 | 24 | In [10]: d1 = {1: 100, 2: 200} 25 | 26 | In [11]: d2 = {2: 200, 1: 100} 27 | 28 | In [12]: d1 == d2 29 | Out[12]: True 30 | 31 | In [13]: from collections import OrderedDict 32 | 33 | In [14]: o1 = OrderedDict({1: 100, 2: 200}) 34 | 35 | In [15]: o2 = OrderedDict({2: 200, 1: 100}) 36 | 37 | In [16]: o1 == o2 38 | Out[16]: False 39 | 40 | 41 | move_to_end 42 | ~~~~~~~~~~~~ 43 | 44 | .. code:: python 45 | 46 | In [17]: o1 47 | Out[17]: OrderedDict([(1, 100), (2, 200)]) 48 | 49 | In [18]: o1.move_to_end(1) 50 | 51 | In [19]: o1 52 | Out[19]: OrderedDict([(2, 200), (1, 100)]) 53 | -------------------------------------------------------------------------------- /docs/source/book/additional_info/collections/time_complexity.rst: -------------------------------------------------------------------------------- 1 | Временная сложность алгоритма 2 | ----------------------------- 3 | 4 | .. note:: 5 | 6 | `Source `__ 7 | 8 | Список 9 | ~~~~~~ 10 | 11 | +------------------+--------------+ 12 | | Operation | Average Case | 13 | +==================+==============+ 14 | | Copy | O(n) | 15 | +------------------+--------------+ 16 | | Append | O(1) | 17 | +------------------+--------------+ 18 | | Pop last | O(1) | 19 | +------------------+--------------+ 20 | | Pop intermediate | O(n) | 21 | +------------------+--------------+ 22 | | Insert | O(n) | 23 | +------------------+--------------+ 24 | | Get Item | O(1) | 25 | +------------------+--------------+ 26 | | Set Item | O(1) | 27 | +------------------+--------------+ 28 | | Delete Item | O(n) | 29 | +------------------+--------------+ 30 | | Iteration | O(n) | 31 | +------------------+--------------+ 32 | | Get Slice | O(k) | 33 | +------------------+--------------+ 34 | | Del Slice | O(n) | 35 | +------------------+--------------+ 36 | | Set Slice | O(k+n) | 37 | +------------------+--------------+ 38 | | Extend | O(k) | 39 | +------------------+--------------+ 40 | | Sort | O(n log n) | 41 | +------------------+--------------+ 42 | | Multiply | O(nk) | 43 | +------------------+--------------+ 44 | | x in s | O(n) | 45 | +------------------+--------------+ 46 | | min(s), max(s) | O(n) | 47 | +------------------+--------------+ 48 | | Get Length | O(1) | 49 | +------------------+--------------+ 50 | 51 | Deque 52 | ~~~~~~ 53 | 54 | +-----------+--------------+ 55 | | Operation | Average Case | 56 | +===========+==============+ 57 | | copy | O(n) | 58 | +-----------+--------------+ 59 | | append | O(1) | 60 | +-----------+--------------+ 61 | | appendleft| O(1) | 62 | +-----------+--------------+ 63 | | pop | O(1) | 64 | +-----------+--------------+ 65 | | popleft | O(1) | 66 | +-----------+--------------+ 67 | | extend | O(k) | 68 | +-----------+--------------+ 69 | | extendleft| O(k) | 70 | +-----------+--------------+ 71 | | rotate | O(k) | 72 | +-----------+--------------+ 73 | | remove | O(n) | 74 | +-----------+--------------+ 75 | 76 | 77 | Dict 78 | ~~~~ 79 | 80 | +------------+--------------+ 81 | | Operation | Average Case | 82 | +============+==============+ 83 | | k in d | O(1) | 84 | +------------+--------------+ 85 | | copy | O(n) | 86 | +------------+--------------+ 87 | | Get Item | O(1) | 88 | +------------+--------------+ 89 | | Set Item | O(1) | 90 | +------------+--------------+ 91 | | Delete Item| O(1) | 92 | +------------+--------------+ 93 | | Iteration | O(n) | 94 | +------------+--------------+ 95 | 96 | -------------------------------------------------------------------------------- /docs/source/book/additional_info/collections/userlist_userdict_userstr.rst: -------------------------------------------------------------------------------- 1 | UserList, UserDict, UserStr 2 | --------------------------- 3 | 4 | Класс UserList действует как оболочка для list. Это полезный базовый 5 | класс для создания своих классов, которые могут наследовать UserList 6 | и переопределять существующие методы или добавлять новые. 7 | 8 | Почему бы не наследовать встроенные list, dict, str? 9 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 10 | 11 | При наследовании UserDict 12 | 13 | .. code:: python 14 | 15 | from collections import UserDict 16 | 17 | class MyDict(UserDict): 18 | def __setitem__(self, key, value): 19 | print("__setitem__", key) 20 | 21 | 22 | In [20]: d1 = MyDict({1: 100, 2: 200}) 23 | __setitem__ 1 24 | __setitem__ 2 25 | 26 | In [21]: d1.update({3: 300}) 27 | __setitem__ 3 28 | 29 | 30 | Встроенный dict 31 | 32 | .. code:: python 33 | 34 | class MyDict(dict): 35 | def __setitem__(self, key, value): 36 | print("__setitem__", key) 37 | 38 | 39 | In [12]: d1 = MyDict({1: 100, 2: 200}) 40 | 41 | In [15]: d1.update({3: 300}) 42 | 43 | 44 | UserList 45 | ~~~~~~~~ 46 | 47 | .. code:: python 48 | 49 | from collections import UserList 50 | 51 | 52 | class Task: 53 | def __init__(self, name): 54 | self.name = name 55 | 56 | def __repr__(self): 57 | return f"Task('{self.name}')" 58 | 59 | 60 | class ToDo(UserList): 61 | def postpone(self): 62 | first_task = self.data.pop(0) 63 | self.data.append(first_task) 64 | 65 | 66 | task1 = Task("task1") 67 | task2 = Task("task2") 68 | task3 = Task("task3") 69 | task4 = Task("task4") 70 | task5 = Task("task5") 71 | 72 | todo_list = [task1, task2, task3] 73 | todo = ToDo(todo_list) 74 | 75 | Использование 76 | 77 | .. code:: python 78 | 79 | In [2]: todo 80 | Out[2]: [Task('task1'), Task('task2'), Task('task3')] 81 | 82 | In [3]: todo[0] 83 | Out[3]: Task('task1') 84 | 85 | In [4]: todo[:-1] 86 | Out[4]: [Task('task1'), Task('task2')] 87 | 88 | In [5]: todo.postpone() 89 | 90 | In [6]: todo 91 | Out[6]: [Task('task2'), Task('task3'), Task('task1')] 92 | 93 | 94 | Для сравнения версия Todo с использованием collections.abc.MutableSequence 95 | 96 | .. code:: python 97 | 98 | from collections.abc import MutableSequence 99 | 100 | 101 | class Task: 102 | def __init__(self, name): 103 | self.name = name 104 | 105 | def __repr__(self): 106 | return f"Task('{self.name}')" 107 | 108 | 109 | class ToDo(MutableSequence): 110 | def __init__(self, tasks): 111 | self.tasks = tasks 112 | 113 | def __repr__(self): 114 | return f"ToDo('{self.tasks}')" 115 | 116 | def __getitem__(self, index): 117 | print("__getitem__", index) 118 | return self.tasks[index] 119 | 120 | def __setitem__(self, index, value): 121 | print("__setitem__", index) 122 | self.tasks[index] = value 123 | 124 | def __delitem__(self, index): 125 | print("__detitem__", index) 126 | del self.tasks[index] 127 | 128 | def __len__(self): 129 | return len(self.tasks) 130 | 131 | def insert(self, index, value): 132 | self.tasks.insert(index, value) 133 | 134 | 135 | task1 = Task("task1") 136 | task2 = Task("task2") 137 | task3 = Task("task3") 138 | task4 = Task("task4") 139 | task5 = Task("task5") 140 | todo_list = [task1, task2, task3, task4, task5] 141 | todo = ToDo(todo_list) 142 | 143 | -------------------------------------------------------------------------------- /docs/source/book/additional_info/index.rst: -------------------------------------------------------------------------------- 1 | .. raw:: latex 2 | 3 | \newpage 4 | 5 | 6 | .. toctree:: 7 | :maxdepth: 1 8 | :hidden: 9 | 10 | memory_test 11 | oop_extra/index 12 | collections/index 13 | pdb/index 14 | -------------------------------------------------------------------------------- /docs/source/book/additional_info/memory_test.rst: -------------------------------------------------------------------------------- 1 | Использование памяти 2 | -------------------- 3 | 4 | .. code:: python 5 | 6 | import resource 7 | from base_ssh import BaseSSH 8 | 9 | memory_start = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss 10 | 11 | ip_list = ['192.168.100.1', '192.168.100.2', '192.168.100.3']*5 12 | sessions = [BaseSSH(ip, 'cisco', 'cisco') for ip in ip_list] 13 | 14 | memory_end = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss 15 | 16 | print('Start ', memory_start) 17 | print('End ', memory_end) 18 | print(sessions) 19 | -------------------------------------------------------------------------------- /docs/source/book/additional_info/oop_extra/index.rst: -------------------------------------------------------------------------------- 1 | .. raw:: latex 2 | 3 | \newpage 4 | 5 | Дополнительные темы по ООП 6 | ========================== 7 | 8 | 9 | .. toctree:: 10 | :maxdepth: 1 11 | :hidden: 12 | 13 | descriptor 14 | metaclass 15 | slots 16 | -------------------------------------------------------------------------------- /docs/source/book/additional_info/oop_extra/metaclass.rst: -------------------------------------------------------------------------------- 1 | Метаклассы 2 | ---------- 3 | 4 | .. code:: python 5 | 6 | import paramiko 7 | import time 8 | 9 | 10 | CLASS_MAPPER_BASE = {} 11 | 12 | class Base(type): 13 | def __init__(cls, clsname, bases, methods): 14 | super().__init__(clsname, bases, methods) 15 | if hasattr(cls, 'device_type'): 16 | CLASS_MAPPER_BASE[cls.device_type] = cls 17 | 18 | 19 | class BaseSSH(metaclass=Base): 20 | def __init__(self, ip, username, password): 21 | self.ip = ip 22 | self.username = username 23 | self.password = password 24 | self._MAX_READ = 10000 25 | 26 | client = paramiko.SSHClient() 27 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 28 | 29 | client.connect( 30 | hostname=ip, 31 | username=username, 32 | password=password, 33 | look_for_keys=False, 34 | allow_agent=False) 35 | 36 | self._ssh = client.invoke_shell() 37 | time.sleep(1) 38 | self._ssh.recv(self._MAX_READ) 39 | 40 | def __enter__(self): 41 | return self 42 | 43 | def __exit__(self, exc_type, exc_value, traceback): 44 | self._ssh.close() 45 | 46 | def close(self): 47 | self._ssh.close() 48 | 49 | def send_show_command(self, command): 50 | self._ssh.send(command + '\n') 51 | time.sleep(2) 52 | result = self._ssh.recv(self._MAX_READ).decode('ascii') 53 | return result 54 | 55 | def send_config_commands(self, commands): 56 | if isinstance(commands, str): 57 | commands = [commands] 58 | for command in commands: 59 | self._ssh.send(command + '\n') 60 | time.sleep(0.5) 61 | result = self._ssh.recv(self._MAX_READ).decode('ascii') 62 | return result 63 | 64 | 65 | class CiscoSSH(BaseSSH): 66 | device_type = 'cisco_ios' 67 | def __init__(self, ip, username, password, enable_password, 68 | disable_paging=True): 69 | super().__init__(ip, username, password) 70 | self._ssh.send('enable\n') 71 | self._ssh.send(enable_password + '\n') 72 | if disable_paging: 73 | self._ssh.send('terminal length 0\n') 74 | time.sleep(1) 75 | self._ssh.recv(self._MAX_READ) 76 | 77 | def config_mode(self): 78 | self._ssh.send('conf t\n') 79 | time.sleep(0.5) 80 | result = self._ssh.recv(self._MAX_READ).decode('ascii') 81 | return result 82 | 83 | def exit_config_mode(self): 84 | self._ssh.send('end\n') 85 | time.sleep(0.5) 86 | result = self._ssh.recv(self._MAX_READ).decode('ascii') 87 | return result 88 | 89 | def send_config_commands(self, commands): 90 | result = self.config_mode() 91 | result += super().send_config_commands(commands) 92 | result += self.exit_config_mode() 93 | return result 94 | 95 | 96 | class JuniperSSH(BaseSSH): 97 | device_type = 'juniper' 98 | def __init__(self, ip, username, password, enable_password, 99 | disable_paging=True): 100 | pass 101 | 102 | 103 | -------------------------------------------------------------------------------- /docs/source/book/additional_info/oop_extra/slots.rst: -------------------------------------------------------------------------------- 1 | Атрибут __slots__ 2 | ----------------- 3 | 4 | 5 | Пример кода из модуля `ipaddress `__ 6 | 7 | .. code:: python 8 | 9 | class _IPAddressBase: 10 | 11 | """The mother class.""" 12 | 13 | __slots__ = () 14 | 15 | @property 16 | def exploded(self): 17 | """Return the longhand version of the IP address as a string.""" 18 | return self._explode_shorthand_ip_string() 19 | 20 | @property 21 | def compressed(self): 22 | """Return the shorthand version of the IP address as a string.""" 23 | return str(self) 24 | 25 | 26 | @functools.total_ordering 27 | class _BaseAddress(_IPAddressBase): 28 | 29 | """A generic IP object. 30 | This IP class contains the version independent methods which are 31 | used by single IP addresses. 32 | """ 33 | 34 | __slots__ = () 35 | 36 | def __int__(self): 37 | return self._ip 38 | 39 | def __eq__(self, other): 40 | try: 41 | return (self._ip == other._ip 42 | and self._version == other._version) 43 | except AttributeError: 44 | return NotImplemented 45 | 46 | def __lt__(self, other): 47 | if not isinstance(other, _BaseAddress): 48 | return NotImplemented 49 | if self._version != other._version: 50 | raise TypeError('%s and %s are not of the same version' % ( 51 | self, other)) 52 | if self._ip != other._ip: 53 | return self._ip < other._ip 54 | return False 55 | 56 | class _BaseV4: 57 | 58 | """Base IPv4 object. 59 | The following methods are used by IPv4 objects in both single IP 60 | addresses and networks. 61 | """ 62 | 63 | __slots__ = () 64 | _version = 4 65 | # Equivalent to 255.255.255.255 or 32 bits of 1's. 66 | _ALL_ONES = (2**IPV4LENGTH) - 1 67 | _DECIMAL_DIGITS = frozenset('0123456789') 68 | 69 | # the valid octets for host and netmasks. only useful for IPv4. 70 | _valid_mask_octets = frozenset({255, 254, 252, 248, 240, 224, 192, 128, 0}) 71 | 72 | _max_prefixlen = IPV4LENGTH 73 | # There are only a handful of valid v4 netmasks, so we cache them all 74 | # when constructed (see _make_netmask()). 75 | _netmask_cache = {} 76 | 77 | def _explode_shorthand_ip_string(self): 78 | return str(self) 79 | 80 | 81 | class IPv4Address(_BaseV4, _BaseAddress): 82 | 83 | """Represent and manipulate single IPv4 Addresses.""" 84 | 85 | __slots__ = ('_ip', '__weakref__') 86 | 87 | def __init__(self, address): 88 | 89 | """ 90 | Args: 91 | address: A string or integer representing the IP 92 | Additionally, an integer can be passed, so 93 | IPv4Address('192.0.2.1') == IPv4Address(3221225985). 94 | or, more generally 95 | IPv4Address(int(IPv4Address('192.0.2.1'))) == 96 | IPv4Address('192.0.2.1') 97 | Raises: 98 | AddressValueError: If ipaddress isn't a valid IPv4 address. 99 | """ 100 | -------------------------------------------------------------------------------- /docs/source/book/additional_info/pdb/further_reading.rst: -------------------------------------------------------------------------------- 1 | Дополнительные материалы 2 | ------------------------ 3 | 4 | 5 | - `The Python Debugger 6 | (pdb) `__ - основы 7 | работы с pdb 8 | - `Python 3 Module of the Week. pdb — Interactive 9 | Debugger `__. `Перевод на 10 | русский `__ 11 | - `Python Debugging With 12 | Pdb `__ - в конце 13 | статьи есть PDF со списком команд 14 | - `Nathan Yergler: In Depth PDB - PyCon 15 | 2014 `__ 16 | 17 | -------------------------------------------------------------------------------- /docs/source/book/additional_info/pdb/index.rst: -------------------------------------------------------------------------------- 1 | .. raw:: latex 2 | 3 | \newpage 4 | 5 | Отладчик pdb 6 | ============== 7 | 8 | 9 | .. toctree:: 10 | :maxdepth: 1 11 | 12 | basics 13 | further_reading 14 | -------------------------------------------------------------------------------- /docs/source/download.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _download: 3 | 4 | Скачать PDF/Epub 5 | ================ 6 | 7 | * `Epub `__ 8 | * `PDF `__ 9 | -------------------------------------------------------------------------------- /docs/source/exercises/exercises_intro.rst: -------------------------------------------------------------------------------- 1 | Все задания и вспомогательные файлы можно скачать в 2 | `репозитории `__. 3 | Если в заданиях раздела есть задания с буквами (например, 5.2a), то 4 | лучше выполнить сначала задания без букв, а затем с буквами. Задания с 5 | буквами, как правило, немного сложнее заданий без букв и развивают или 6 | усложняют идею в соответствующем задании без буквы. 7 | 8 | .. note:: 9 | Например, в разделе есть задания 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. 10 | Сначала лучше выполнить задания 5.1, 5.2, 5.3, а затем 5.2a, 5.2b, 11 | 5.3a 12 | 13 | Если задания с буквами получается сделать сразу, лучше делать их по 14 | порядку. 15 | -------------------------------------------------------------------------------- /docs/source/exercises/old_exercises/02_exercises.rst: -------------------------------------------------------------------------------- 1 | .. raw:: latex 2 | 3 | \newpage 4 | 5 | Задания 6 | ======= 7 | 8 | .. include:: ./exercises_intro.rst 9 | 10 | 11 | Задание 2.1 12 | ~~~~~~~~~~~~ 13 | 14 | Скопировать класс IPv4Network из задания 1.1 и добавить ему все методы, 15 | которые необходимы для реализации протокола последовательности (sequence): 16 | 17 | * __getitem__, __len__, __contains__, __iter__ 18 | * index, count - должны работать аналогично методам в списках и кортежах 19 | 20 | Плюс оба метода, которые отвечают за строковое представление экземпляров 21 | класса IPv4Network. 22 | 23 | Пример создания экземпляра класса: 24 | 25 | .. code:: python 26 | 27 | In [2]: net1 = IPv4Network('8.8.4.0/29') 28 | 29 | Проверка методов: 30 | 31 | .. code:: python 32 | 33 | In [3]: for ip in net1: 34 | ...: print(ip) 35 | ...: 36 | 8.8.4.1 37 | 8.8.4.2 38 | 8.8.4.3 39 | 8.8.4.4 40 | 8.8.4.5 41 | 8.8.4.6 42 | 43 | In [4]: net1[2] 44 | Out[4]: '8.8.4.3' 45 | 46 | In [5]: net1[-1] 47 | Out[5]: '8.8.4.6' 48 | 49 | In [6]: net1[1:4] 50 | Out[6]: ('8.8.4.2', '8.8.4.3', '8.8.4.4') 51 | 52 | In [7]: '8.8.4.4' in net1 53 | Out[7]: True 54 | 55 | In [8]: net1.index('8.8.4.4') 56 | Out[8]: 3 57 | 58 | In [9]: net1.count('8.8.4.4') 59 | Out[9]: 1 60 | 61 | In [10]: len(net1) 62 | Out[10]: 6 63 | 64 | Строковое представление: 65 | 66 | .. code:: python 67 | 68 | In [13]: net1 69 | Out[13]: IPv4Network(8.8.4.0/29) 70 | 71 | In [14]: str(net1) 72 | Out[14]: 'IPv4Network 8.8.4.0/29' 73 | 74 | 75 | Задание 2.2 76 | ~~~~~~~~~~~~ 77 | 78 | Скопировать класс PingNetwork из задания 1.2 и изменить его таким образом, 79 | чтобы адреса пинговались не при вызове метода scan, а при вызове экземпляра. 80 | 81 | Вся функциональность метода scan должна быть перенесена в метод, который отвечает 82 | за вызов экземпляра. 83 | 84 | Пример работы с классом PingNetwork. Сначала создаем сеть: 85 | 86 | .. code:: python 87 | 88 | In [2]: net1 = IPv4Network('8.8.4.0/29') 89 | 90 | И выделяем несколько адресов: 91 | 92 | .. code:: python 93 | 94 | In [3]: net1.allocate('8.8.4.2') 95 | ...: net1.allocate('8.8.4.4') 96 | ...: net1.allocate('8.8.4.6') 97 | ...: 98 | 99 | Затем создается экземпляр класса PingNetwork, сеть передается как аргумент: 100 | 101 | .. code:: python 102 | 103 | In [6]: ping_net = PingNetwork(net1) 104 | 105 | После этого экземпляр должен быть вызываемым объектом (callable): 106 | 107 | .. code:: python 108 | 109 | In [7]: ping_net() 110 | Out[7]: (['8.8.4.4'], ['8.8.4.2', '8.8.4.6']) 111 | 112 | In [8]: ping_net(include_unassigned=True) 113 | Out[8]: (['8.8.4.4'], ['8.8.4.2', '8.8.4.6', '8.8.4.1', '8.8.4.3', '8.8.4.5']) 114 | 115 | -------------------------------------------------------------------------------- /docs/source/exercises/old_exercises/09_exercises.rst: -------------------------------------------------------------------------------- 1 | 2 | .. raw:: latex 3 | 4 | \newpage 5 | 6 | Задания 7 | ======= 8 | 9 | .. include:: ./exercises_intro.rst 10 | 11 | -------------------------------------------------------------------------------- /docs/source/exercises/old_exercises/10_exercises.rst: -------------------------------------------------------------------------------- 1 | 2 | .. raw:: latex 3 | 4 | \newpage 5 | 6 | Задания 7 | ======= 8 | 9 | .. include:: ./exercises_intro.rst 10 | 11 | -------------------------------------------------------------------------------- /docs/source/exercises/old_exercises/12_exercises.rst: -------------------------------------------------------------------------------- 1 | 2 | .. raw:: latex 3 | 4 | \newpage 5 | 6 | Задания 7 | ======= 8 | 9 | .. include:: ./exercises_intro.rst 10 | 11 | -------------------------------------------------------------------------------- /docs/source/exercises/old_exercises/13_exercises.rst: -------------------------------------------------------------------------------- 1 | 2 | .. raw:: latex 3 | 4 | \newpage 5 | 6 | Задания 7 | ======= 8 | 9 | .. include:: ./exercises_intro.rst 10 | 11 | -------------------------------------------------------------------------------- /docs/source/exercises/old_exercises/14_exercises.rst: -------------------------------------------------------------------------------- 1 | 2 | .. raw:: latex 3 | 4 | \newpage 5 | 6 | Задания 7 | ======= 8 | 9 | .. include:: ./exercises_intro.rst 10 | 11 | -------------------------------------------------------------------------------- /docs/source/exercises/old_exercises/15_exercises.rst: -------------------------------------------------------------------------------- 1 | 2 | .. raw:: latex 3 | 4 | \newpage 5 | 6 | Задания 7 | ======= 8 | 9 | .. include:: ./exercises_intro.rst 10 | 11 | -------------------------------------------------------------------------------- /docs/source/exercises/old_exercises/16_exercises.rst: -------------------------------------------------------------------------------- 1 | 2 | .. raw:: latex 3 | 4 | \newpage 5 | 6 | Задания 7 | ======= 8 | 9 | .. include:: ./exercises_intro.rst 10 | 11 | -------------------------------------------------------------------------------- /docs/source/exercises/old_exercises/17_exercises.rst: -------------------------------------------------------------------------------- 1 | 2 | .. raw:: latex 3 | 4 | \newpage 5 | 6 | Задания 7 | ======= 8 | 9 | .. include:: ./exercises_intro.rst 10 | 11 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | Advanced Python для сетевых инженеров 2 | ===================================== 3 | 4 | В книге рассматриваются продвинутые возможности Python на примерах 5 | для сетевых инженеров. 6 | 7 | 8 | .. toctree:: 9 | :maxdepth: 2 10 | 11 | book/Part_I 12 | book/Part_II 13 | book/Part_III 14 | book/Part_IV 15 | book/Part_V 16 | book/Part_VI 17 | resources 18 | download 19 | 20 | 21 | .. toctree:: 22 | :caption: Ресурсы 23 | :maxdepth: 1 24 | :hidden: 25 | 26 | Задания, примеры кода 27 | -------------------------------------------------------------------------------- /docs/source/resources.rst: -------------------------------------------------------------------------------- 1 | Продолжение обучения 2 | ==================== 3 | 4 | * `Fluent Python первое издание `__ (второе издание `выйдет осенью 2021 года `__) 5 | --------------------------------------------------------------------------------