Вы здесь

Python. 2.1 Функции

Функция - это блок кода, выполняющий определенные действия:

- у функции есть имя, с помощью которого можно запускать этот блок кода сколько угодно раз
- запуск кода функции называется вызовом функции
- при создании функции, как правило, определяются параметры функции.
- параметры функции определяют, какие аргументы функция может принимать
- функциям можно передавать аргументы
- соответственно, код функции будет выполняться с учетом указанных аргументов

Часто получается, что есть кусок кода, который повторяется. Конечно, его можно копировать из одного скрипта в другой. Но это очень неудобно, так как при внесении изменений в код нужно будет обновить его во всех файлах, в которые он скопирован.

Гораздо проще и правильней вынести этот код в функцию (это может быть и несколько функций).

И тогда будет производиться вызов этой функции - в этом файле или каком-то другом.

Создание функции

- функции создаются с помощью зарезервированного слова def
- за def следуют имя функции и круглые скобки
- внутри скобок могут указываться параметры, которые функция принимает
- после круглых скобок идет двоеточие и с новой строки, с отступом, идет блок кода, который выполняет функция
- первой строкой, опционально, может быть комментарий, так называемая docstring
- в функциях может использоваться оператор return
- он используется для прекращения работы функции и выхода из нее
-чаще всего, оператор return возвращает какое-то значение

Пример функции:

In [1]: def configure_intf(intf_name, ip, mask): ...: print('interface', intf_name) ...: print('ip address', ip, mask) ...:

Функция configure_intf создает конфигурацию интерфейса с указанным именем и IP-адресом. У функции есть три параметра: intf_name, ip, mask. При вызове функции в эти параметры попадут реальные данные.

Вызов функции

Функция configure_intf ожидает при вызове три значения, потому что она была создана с тремя параметрами:

In [2]: configure_intf('F0/0', '10.1.1.1', '255.255.255.0') interface F0/0 ip address 10.1.1.1 255.255.255.0 In [3]: configure_intf('Fa0/1', '94.150.197.1', '255.255.255.248') interface Fa0/1 ip address 94.150.197.1 255.255.255.248

Текущий вариант функции configure_intf выводит команды на стандартный поток вывода, команды можно увидеть, но при этом результат функции нельзя сохранить в переменную.

In [8]: result = configure_intf('Fa0/0', '10.1.1.1', '255.255.255.0') interface Fa0/0 ip address 10.1.1.1 255.255.255.0 In [9]: print(result) None

Чтобы функция могла возвращать какое-то значение, надо использовать оператор return.

Оператор return

Оператор return используется для возврата какого-то значения, и в то же время он завершает работу функции. Функция может возвращать любой объект Python. По умолчанию, функция всегда возвращает None.

Для того, чтобы функция configure_intf возвращала значение, которое потом можно, например, присвоить переменной, надо использовать оператор return:

In [10]: def configure_intf(intf_name, ip, mask): ...: config = f'interface {intf_name}\nip address {ip} {mask}' ...: return config ...: In [11]: result = configure_intf('Fa0/0', '10.1.1.1', '255.255.255.0') In [12]: print(result) interface Fa0/0 ip address 10.1.1.1 255.255.255.0 In [13]: result Out[13]: 'interface Fa0/0\nip address 10.1.1.1 255.255.255.0'

Теперь в переменой result находится строка с командами для настройки интерфейса.

В реальной жизни практически всегда функция будет возвращать какое-то значение. Вместе с тем можно использовать выражение print, чтобы дополнительно выводить какие-то сообщения.

Ещё один важный аспект работы оператора return: после return, функция завершает работу, а значит выражения, которые идут после return, не выполняются.

Функция может возвращать несколько значений. В этом случае, они пишутся через запятую после оператора return. При этом фактически функция возвращает кортеж:

In [16]: def configure_intf(intf_name, ip, mask): ...: config_intf = f'interface {intf_name}\n' ...: config_ip = f'ip address {ip} {mask}' ...: return config_intf, config_ip ...: In [17]: result = configure_intf('Fa0/0', '10.1.1.1', '255.255.255.0') In [18]: result Out[18]: ('interface Fa0/0\n', 'ip address 10.1.1.1 255.255.255.0') In [19]: type(result) Out[19]: tuple In [20]: intf, ip_addr = configure_intf('Fa0/0', '10.1.1.1', '255.255.255.0') In [21]: intf Out[21]: 'interface Fa0/0\n' In [22]: ip_addr Out[22]: 'ip address 10.1.1.1 255.255.255.0'

Документация (docstring)

Первая строка в определении функции - это docstring, строка документации. Это комментарий, который используется как описание функции:

In [23]: def configure_intf(intf_name, ip, mask): ...: ''' ...: Функция генерирует конфигурацию интерфейса ...: ''' ...: config_intf = f'interface {intf_name}\n' ...: config_ip = f'ip address {ip} {mask}' ...: return config_intf, config_ip ...: In [24]: configure_intf? Signature: configure_intf(intf_name, ip, mask) Docstring: Функция генерирует конфигурацию интерфейса File: ~/repos/pyneng-examples-exercises/examples/06_control_structures/ Type: function

Лучше не лениться писать краткие комментарии, которые описывают работу функции. Например, описать, что функция ожидает на вход, какого типа должны быть аргументы и что будет на выходе. Кроме того, лучше написать пару предложений о том, что делает функция. Это очень поможет, когда через месяц-два вы будете пытаться понять, что делает функция, которую вы же написали.

Пространства имен. Области видимости

У переменных в Python есть область видимости. В зависимости от места в коде, где переменная была определена, определяется и область видимости, то есть, где переменная будет доступна.

При использовании имен переменных в программе, Python каждый раз ищет, создает или изменяет эти имена в соответствующем пространстве имен. Пространство имен, которое доступно в каждый момент, зависит от области, в которой находится код.

У Python есть правило LEGB, которым он пользуется при поиске переменных.

Например, если внутри функции выполняется обращение к имени переменной, Python ищет переменную в таком порядке по областям видимости (до первого совпадения):
- L (local) - в локальной (внутри функции)
- E (enclosing) - в локальной области объемлющих функций (это те функции, внутри которых находится наша функция)
- G (global) - в глобальной (в скрипте)
- B (built-in) - во встроенной (зарезервированные значения Python)

Соответственно, есть локальные и глобальные переменные:

- локальные переменные:
переменные, которые определены внутри функции
эти переменные становятся недоступными после выхода из функции

- глобальные переменные:
переменные, которые определены вне функции
эти переменные „глобальны“ только в пределах модуля

например, чтобы они были доступны в другом модуле, их надо импортировать

Пример локальной intf_config:

In [1]: def configure_intf(intf_name, ip, mask): ...: intf_config = f'interface {intf_name}\nip address {ip} {mask}' ...: return intf_config ...: In [2]: intf_config --------------------------------------------------------------------------- NameError Traceback (most recent call last) in ----> 1 intf_config NameError: name 'intf_config' is not defined

Обратите внимание, что переменная intf_config недоступна за пределами функции. Для того чтобы получить результат функции, надо вызвать функцию и присвоить результат в переменную:

In [3]: result = configure_intf('F0/0', '10.1.1.1', '255.255.255.0') In [4]: result Out[4]: 'interface F0/0\nip address 10.1.1.1 255.255.255.0'

Параметры и аргументы функций

Цель создания функции, как правило, заключается в том, чтобы вынести кусок кода, который выполняет определенную задачу, в отдельный объект. Это позволяет использовать этот кусок кода многократно, не создавая его заново в программе.

При работе с функциями важно различать:

- параметры - это переменные, которые используются при создании функции.
- аргументы - это фактические значения (данные), которые передаются функции при вызове.

Параметры бывают обязательные и необязательные.

Обязательные:

def f(a, b): pass

Необязательные (со значением по умолчанию):

def f(a=None): pass

В этом случае a - передавать необязательно.

Аргументы бывают позиционные и ключевые.

def summ(a, b): return a + b

Позиционные:

summ(1, 2)

Ключевые:

summ(a=1, b=2)

Независимо от того как параметры созданы, при вызове функции им можно передавать значения и как ключевые и как позиционные аргументы. При этом обязательные параметры надо передать в любом случае, любым способом (позиционными или ключевыми), а необязательные можно передавать, можно нет. Если передавать, то тоже любым способом.

Для того, чтобы функция могла принимать входящие значения, ее нужно создать с параметрами (файл func_check_passwd.py):
python_37_ciscomaster.ru.jpg
В данном случае, у функции два параметра: username и password.

Функция проверяет пароль и возвращает False, если проверки не прошли и True если пароль прошел проверки:

In [2]: check_passwd('nata', '12345') Пароль слишком короткий Out[2]: False In [3]: check_passwd('nata', '12345lsdkjflskfdjsnata') Пароль содержит имя пользователя Out[3]: False In [4]: check_passwd('nata', '12345lsdkjflskfdjs') Пароль для пользователя nata прошел все проверки Out[4]: True

При таком определении функции надо обязательно передать оба аргумента. Если передать только один аргумент, возникнет ошибка
Аналогично, возникнет ошибка, если передать три и больше аргументов.

Типы параметров функции

При создании функции можно указать, какие аргументы нужно передавать обязательно, а какие нет. Соответственно, функция может быть создана с:

- обязательными параметрами
- необязательными параметрами (опциональными, параметрами со значением по умолчанию)

Обязательные параметры

Обязательные параметры - определяют, какие аргументы нужно передать функции обязательно. При этом, их нужно передать ровно столько, сколько указано параметров функции (нельзя указать большее или меньшее количество)

Функция с обязательными параметрами (файл func_params_types.py):
python_32_ciscomaster.ru.jpg

Функция check_passwd ожидает два аргумента: username и password.
Функция проверяет пароль и возвращает False, если проверки не прошли и True, если пароль прошел проверки:

In [2]: check_passwd('nata', '12345') Пароль слишком короткий Out[2]: False In [3]: check_passwd('nata', '12345lsdkjflskfdjsnata') Пароль содержит имя пользователя Out[3]: False In [4]: check_passwd('nata', '12345lsdkjflskfdjs') Пароль для пользователя nata прошел все проверки Out[4]: True

Необязательные параметры (параметры со значением по умолчанию)

При создании функции можно указывать значение по умолчанию для параметра таким образом: def check_passwd(username, password, min_length=8). В этом случае, параметр min_length указан со значением по умолчанию и может не передаваться при вызове функции.

Пример функции check_passwd с параметром со значением по умолчанию (файл func_check_passwd_optional_param.py):
python_33_ciscomaster.ru.jpg

Так как у параметра min_length есть значение по умолчанию, соответствующий аргумент можно не указывать при вызове функции, если значение по умолчанию подходит:

In [7]: check_passwd('nata', '12345') Пароль слишком короткий Out[7]: False

Если нужно поменять значение по умолчанию:

In [8]: check_passwd('nata', '12345', 3) Пароль для пользователя nata прошел все проверки Out[8]: True

Типы аргументов функции

При вызове функции аргументы можно передавать двумя способами:

- как позиционные - передаются в том же порядке, в котором они определены при создании функции. То есть, порядок передачи аргументов определяет, какое значение получит каждый аргумент
- как ключевые - передаются с указанием имени аргумента и его значения. В таком случае, аргументы могут быть указаны в любом порядке, так как их имя указывается явно.

Позиционные и ключевые аргументы могут быть использоваться одновременно при вызове функции. При этом сначала должны идти позиционные аргументы, а только потом - ключевые.

Позиционные аргументы

Позиционные аргументы при вызове функции надо передать в правильном порядке (поэтому они и называются позиционные).
python_34_ciscomaster.ru.jpg

Ключевые аргументы

- передаются с указанием имени аргумента
- за счет этого они могут передаваться в любом порядке
Если передать оба аргумента как ключевые, можно передавать их в любом порядке:
python_33_ciscomaster.ru.jpg

In [9]: check_passwd(password='12345', username='nata', min_length=4) Пароль для пользователя nata прошел все проверки Out[9]: True

Всегда сначала должны идти позиционные аргументы, а затем ключевые.

In [11]: check_passwd('nata', '12345', min_length=3) Пароль для пользователя nata прошел все проверки Out[11]: True

В реальной жизни зачастую намного понятней и удобней указывать флаги (параметры со значениями True/False) или числовые значения как ключевой аргумент. Если задать хорошее название параметра, то по его имени сразу будет понятно, что именно он делает.

Например, можно добавить флаг, который будет контролировать, выполнять проверку наличия имени пользователя в пароле или нет:
python_35_ciscomaster.ru.jpg

По умолчанию флаг равен True, а значит проверку выполнять надо:

In [14]: check_passwd('nata', '12345nata', min_length=3) Пароль содержит имя пользователя Out[14]: False In [15]: check_passwd('nata', '12345nata', min_length=3, check_username=True) Пароль содержит имя пользователя Out[15]: False

Аргументы переменной длины

Иногда необходимо сделать так, чтобы функция принимала не фиксированное количество аргументов, а любое. Для такого случая в Python можно создавать функцию со специальным параметром, который принимает аргументы переменной длины. Такой параметр может быть как ключевым, так и позиционным.

Позиционные аргументы переменной длины

Параметр, который принимает позиционные аргументы переменной длины, создается добавлением перед именем параметра звездочки. Имя параметра может быть любым, но по договоренности чаще всего используют имя *args

In [1]: def sum_arg(a, *args): ....: print(a, args) ....: return a + sum(args) ....:

Функция sum_arg создана с двумя параметрами:

- параметр a
если передается как позиционный аргумент, должен идти первым
если передается как ключевой аргумент, то порядок не важен

- параметр *args - ожидает аргументы переменной длины
сюда попадут все остальные аргументы в виде кортежа
эти аргументы могут отсутствовать

In [2]: sum_arg(1, 10, 20, 30) 1 (10, 20, 30) Out[2]: 61 In [3]: sum_arg(1, 10) 1 (10,) Out[3]: 11 In [4]: sum_arg(1) 1 () Out[4]: 1

Можно создать и такую функцию:

In [5]: def sum_arg(*args): ....: print(args) ....: return sum(args) ....: In [6]: sum_arg(1, 10, 20, 30) (1, 10, 20, 30) Out[6]: 61 In [7]: sum_arg() () Out[7]: 0

Ключевые аргументы переменной длины

Параметр, который принимает ключевые аргументы переменной длины, создается добавлением перед именем параметра двух звездочек. Имя параметра может быть любым, но, по договоренности, чаще всего, используют имя **kwargs (от keyword arguments).

In [8]: def sum_arg(a, **kwargs): ....: print(a, kwargs) ....: return a + sum(kwargs.values()) ....:

Функция sum_arg создана с двумя параметрами:

- параметр a
если передается как позиционный аргумент, должен идти первым
если передается как ключевой аргумент, то порядок не важен

- параметр **kwargs - ожидает ключевые аргументы переменной длины
сюда попадут все остальные ключевые аргументы в виде словаря
эти аргументы могут отсутствовать

In [9]: sum_arg(a=10, b=10, c=20, d=30) 10 {'c': 20, 'b': 10, 'd': 30} Out[9]: 70 In [10]: sum_arg(b=10, c=20, d=30, a=10) 10 {'c': 20, 'b': 10, 'd': 30} Out[10]: 70

Распаковка аргументов

В Python выражения *args и **kwargs позволяют выполнять ещё одну задачу - распаковку аргументов.

До сих пор мы вызывали все функции вручную. И соответственно передавали все нужные аргументы.

В реальности, как правило, данные необходимо передавать в функцию программно. И часто данные идут в виде какого-то объекта Python.

Распаковка позиционных аргументов

Например, при форматировании строк часто надо передать методу format несколько аргументов. И часто эти аргументы уже находятся в списке или кортеже. Чтобы их передать методу format, приходится использовать индексы таким образом:

In [1]: items = [1,2,3] In [2]: print('One: {}, Two: {}, Three: {}'.format(items[0], items[1], items[2])) One: 1, Two: 2, Three: 3

Вместо этого, можно воспользоваться распаковкой аргументов и сделать так:

In [4]: items = [1,2,3] In [5]: print('One: {}, Two: {}, Three: {}'.format(*items)) One: 1, Two: 2, Three: 3

Еще один пример - функция config_interface (файл func_config_interface_unpacking.py):

In [8]: def config_interface(intf_name, ip_address, mask): ..: interface = f'interface {intf_name}' ..: no_shut = 'no shutdown' ..: ip_addr = f'ip address {ip_address} {mask}' ..: result = [interface, no_shut, ip_addr] ..: return result ..:

Функция ожидает такие аргументы:

- intf_name - имя интерфейса
- ip_address - IP-адрес
- mask - маска

Функция возвращает список строк для настройки интерфейса:

In [9]: config_interface('Fa0/1', '10.0.1.1', '255.255.255.0') Out[9]: ['interface Fa0/1', 'no shutdown', 'ip address 10.0.1.1 255.255.255.0'] In [11]: config_interface('Fa0/10', '10.255.4.1', '255.255.255.0') Out[11]: ['interface Fa0/10', 'no shutdown', 'ip address 10.255.4.1 255.255.255.0']

Допустим, нужно вызвать функцию и передать ей информацию, которая была получена из другого источника, к примеру, из БД.

Например, список interfaces_info, в котором находятся параметры для настройки интерфейсов:

In [14]: interfaces_info = [['Fa0/1', '10.0.1.1', '255.255.255.0'], ...: ['Fa0/2', '10.0.2.1', '255.255.255.0'], ...: ['Fa0/3', '10.0.3.1', '255.255.255.0'], ...: ['Fa0/4', '10.0.4.1', '255.255.255.0'], ...: ['Lo0', '10.0.0.1', '255.255.255.255']] ...:

Если пройтись по списку в цикле и передавать вложенный список как аргумент функции, возникнет ошибка:

In [15]: for info in interfaces_info: ...: print(config_interface(info)) ...: --------------------------------------------------------------------------- TypeError Traceback (most recent call last) in 1 for info in interfaces_info: ----> 2 print(config_interface(info)) 3 TypeError: config_interface() missing 2 required positional arguments: 'ip_address' and 'mask'

Ошибка вполне логичная: функция ожидает три аргумента, а ей передан 1 аргумент - список.

В такой ситуации пригодится распаковка аргументов. Достаточно добавить * перед передачей списка как аргумента, и ошибки уже не будет:

In [16]: for info in interfaces_info: ...: print(config_interface(*info)) ...: ['interface Fa0/1', 'no shutdown', 'ip address 10.0.1.1 255.255.255.0'] ['interface Fa0/2', 'no shutdown', 'ip address 10.0.2.1 255.255.255.0'] ['interface Fa0/3', 'no shutdown', 'ip address 10.0.3.1 255.255.255.0'] ['interface Fa0/4', 'no shutdown', 'ip address 10.0.4.1 255.255.255.0'] ['interface Lo0', 'no shutdown', 'ip address 10.0.0.1 255.255.255.255']

Python сам „распакует“ список info и передаст в функцию элементы списка как аргументы.
Таким же образом можно распаковывать и кортеж.

Распаковка ключевых аргументов

Аналогичным образом можно распаковывать словарь, чтобы передать его как ключевые аргументы.

Функция check_passwd (файл func_check_passwd_optional_param_2.py):
python_36_ciscomaster.ru.jpg

Список словарей username_passwd, в которых указано имя пользователя и пароль:

In [20]: username_passwd = [{'username': 'cisco', 'password': 'cisco'}, ...: {'username': 'nata', 'password': 'natapass'}, ...: {'username': 'user', 'password': '123456789'}]

Если передать словарь функции check_passwd, возникнет ошибка:

In [21]: for data in username_passwd: ...: check_passwd(data) ...: --------------------------------------------------------------------------- TypeError Traceback (most recent call last) in 1 for data in username_passwd: ----> 2 check_passwd(data) 3 TypeError: check_passwd() missing 1 required positional argument: 'password'

Ошибка такая, так как функция восприняла словарь как один аргумент и считает что ей не хватает только аргумента password.

Если добавить ** перед передачей словаря функции, функция нормально отработает:

In [22]: for data in username_passwd: ...: check_passwd(**data) ...: Пароль слишком короткий Пароль содержит имя пользователя Пароль для пользователя user прошел все проверки In [23]: for data in username_passwd: ...: print(data) ...: check_passwd(**data) ...: {'username': 'cisco', 'password': 'cisco'} Пароль слишком короткий {'username': 'nata', 'password': 'natapass'} Пароль содержит имя пользователя {'username': 'user', 'password': '123456789'} Пароль для пользователя user прошел все проверки

Python распаковывает словарь и передает его в функцию как ключевые аргументы. Запись check_passwd(**data) превращается в вызов вида check_passwd(username='cisco', password='cisco').

Пример использования ключевых аргументов переменной длины и распаковки аргументов

С помощью аргументов переменной длины и распаковки аргументов можно передавать аргументы между функциями. Посмотрим на примере.

Функция check_passwd (файл func_add_user_kwargs_example.py):
python_38_ciscomaster.ru.jpg
Функция проверяет пароль и возвращает True, если пароль прошел проверки и False - если нет.

Вызов функции в ipython:

In [3]: check_passwd('nata', '12345', min_length=3) Пароль для пользователя nata прошел все проверки Out[3]: True In [4]: check_passwd('nata', '12345nata', min_length=3) Пароль содержит имя пользователя Out[4]: False In [5]: check_passwd('nata', '12345nata', min_length=3, check_username=False) Пароль для пользователя nata прошел все проверки Out[5]: True In [6]: check_passwd('nata', '12345nata', min_length=3, check_username=True) Пароль содержит имя пользователя Out[6]: False

Сделаем функцию add_user_to_users_file, которая запрашивает пароль для указанного пользователя, проверяет его и запрашивает заново, если пароль не прошел проверки или записывает пользователя и пароль в файл, если пароль прошел проверки.

In [7]: def add_user_to_users_file(user, users_filename='users.txt'): ...: while True: ...: passwd = input(f'Введите пароль для пользователя {user}: ') ...: if check_passwd(user, passwd): ...: break ...: with open(users_filename, 'a') as f: ...: f.write(f'{user},{passwd}\n') ...: In [8]: add_user_to_users_file('nata') Введите пароль для пользователя nata: natasda Пароль слишком короткий Введите пароль для пользователя nata: natasdlajsl;fjd Пароль содержит имя пользователя Введите пароль для пользователя nata: salkfdjsalkdjfsal;dfj Пароль для пользователя nata прошел все проверки In [9]: cat users.txt nata,salkfdjsalkdjfsal;dfj

В таком варианте функции add_user_to_users_file нет возможности регулировать минимальную длину пароля и то надо ли проверять наличие имени пользователя в пароле. В следующем варианте функции add_user_to_users_file эти возможности добавлены:

In [5]: def add_user_to_users_file(user, users_filename='users.txt', min_length=8, check_username=True): ...: while True: ...: passwd = input(f'Введите пароль для пользователя {user}: ') ...: if check_passwd(user, passwd, min_length, check_username): ...: break ...: with open(users_filename, 'a') as f: ...: f.write(f'{user},{passwd}\n') ...: In [6]: add_user_to_users_file('nata', min_length=5) Введите пароль для пользователя nata: natas2342 Пароль содержит имя пользователя Введите пароль для пользователя nata: dlfjgkd Пароль для пользователя nata прошел все проверки

Теперь при вызове функции можно указать параметр min_length или check_username. Однако, пришлось повторить параметры функции check_passwd в определении функции add_user_to_users_file. Это не очень хорошо и, когда параметров много, просто неудобно, особенно если учитывать, что у функции check_passwd могут добавиться другие параметры.

Такая ситуация случается довольно часто и в Python есть распространенное решение этой задачи: все аргументы для внутренней функции (в этом случае это check_passwd) будут приниматься в **kwargs. Затем, при вызове функции check_passwd они будут распаковываться в ключевые аргументы тем же синтаксисом **kwargs.

In [7]: def add_user_to_users_file(user, users_filename='users.txt', **kwargs): ...: while True: ...: passwd = input(f'Введите пароль для пользователя {user}: ') ...: if check_passwd(user, passwd, **kwargs): ...: break ...: with open(users_filename, 'a') as f: ...: f.write(f'{user},{passwd}\n') ...: In [8]: add_user_to_users_file('nata', min_length=5) Введите пароль для пользователя nata: alskfdjlksadjf Пароль для пользователя nata прошел все проверки In [9]: add_user_to_users_file('nata', min_length=5) Введите пароль для пользователя nata: 345 Пароль слишком короткий Введите пароль для пользователя nata: 309487538 Пароль для пользователя nata прошел все проверки

В таком варианте в функцию check_passwd можно добавлять аргументы без необходимости дублировать их в функции add_user_to_users_file.

Аргументы, которые можно передавать только как ключевые

Аргументы, которые указаны после * можно передавать только как ключевые при вызове функции.

Например, в этой функции аргументы min_length и check_username можно передавать только как ключевые.
python_39_ciscomaster.ru.jpg

При передаче их как позиционных, возникнет исключение:

In [2]: check_passwd('nata', '12345', min_length=3) Пароль для пользователя nata прошел все проверки Out[2]: True In [3]: check_passwd('nata', '12345', 3) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) in ----> 1 check_passwd('nata', '12345', 3) TypeError: check_passwd() takes 2 positional arguments but 3 were given

Распространенные проблемы/нюансы работы с функциями
Список/словарь в который собираются данные в функции, создан за пределами функции

Очень часто в решении заданий встречается такой нюанс: функция должна собрать какие-то данные в список/словарь и список создан вне функции. Тогда вроде как функция работает правильно, но при этом тест не проходит. Это происходит потому что в таком варианте функция работает неправильно и каждый вызов добавляет элементы в тот же список:

In [1]: result = [] In [2]: def func(items): ...: for i in items: ...: result.append(i*100) ...: return result ...: In [3]: func([1, 2, 3]) Out[3]: [100, 200, 300] In [4]: func([7, 8]) Out[4]: [100, 200, 300, 700, 800]

Исправить это можно переносом строки создания списка в функцию:

In [20]: def func(items): ...: result = [] ...: for i in items: ...: result.append(i*100) ...: return result ...: In [21]: func([1, 2, 3]) Out[21]: [100, 200, 300] In [22]: func([7, 8]) Out[22]: [700, 800]

Всё, что относится к функции лучше всегда писать внутри функции. Тест тут не проходит потому что внутри файла задания функция вызывается первый раз - всё правильно, а потом тест вызывает её второй раз и там вдруг в два раза больше данных чем нужно.

Значения по умолчанию в параметрах создаются во время создания функции

Пример функции, которая должна выводить текущую дату и время при каждом вызове:

In [1]: from datetime import datetime In [2]: import time In [3]: def print_current_datetime(ptime=datetime.now()): ...: print(f">>> {ptime}") ...: In [4]: for i in range(3): ...: print("Имитируем долгое выполнение...") ...: time.sleep(1) ...: print_current_datetime() ...: Имитируем долгое выполнение... >>> 2021-02-23 09:01:49.845425 Имитируем долгое выполнение... >>> 2021-02-23 09:01:49.845425 Имитируем долгое выполнение... >>> 2021-02-23 09:01:49.845425

Так как datetime.now() указано в значении по умолчанию, это значение создается во время создания функции и в итоге при каждом вызове время одно и то же. Для корректного вывода, надо вызывать datetime.now() в теле функции:

In [5]: def print_current_datetime(): ...: print(f">>> {datetime.now()}") ...:

Второй пример где этот нюанс может привести к неожиданным результатам, если о нем не знать - изменяемые типы данных в значении по умолчанию.

Например, использование списка в значении по умолчанию:

In [15]: def add_item(item, data=[]): ...: data.append(item) ...: return data ...:

В этом случае список data создается один раз - при создании функции и при вызове функции, данные добавляются в один и тот же список. В итоге все повторные вызовы будут добавлять элементы:

In [16]: add_item(1) Out[16]: [1] In [17]: add_item(2) Out[17]: [1, 2] In [18]: add_item(4) Out[18]: [1, 2, 4]

Если нужно сделать так, чтобы параметр data был необязательным и по умолчанию создавался пустой список, надо сделать так:

In [22]: def add_item(item, data=None): ...: if data is None: ...: data = [] ...: data.append(item) ...: return data ...: In [23]: add_item(1) Out[23]: [1] In [24]: add_item(2) Out[24]: [2]

Задание 9.1

Создать функцию, которая генерирует конфигурацию для access-портов.

Функция ожидает такие аргументы:
- словарь с соответствием интерфейс-VLAN такого вида:

{"FastEthernet0/12": 10, "FastEthernet0/14": 11, "FastEthernet0/16": 17}

- шаблон конфигурации access-портов в виде списка команд (список access_mode_template)

Функция должна возвращать список всех портов в режиме access с конфигурацией на основе шаблона access_mode_template. В конце строк в списке не должно быть символа перевода строки.

В этом задании заготовка для функции уже сделана и надо только продолжить писать само тело функции.

Пример итогового списка (перевод строки после каждого элемента сделан для удобства чтения):

[ "interface FastEthernet0/12", "switchport mode access", "switchport access vlan 10", "switchport nonegotiate", "spanning-tree portfast", "spanning-tree bpduguard enable", "interface FastEthernet0/17", "switchport mode access", "switchport access vlan 150", "switchport nonegotiate", "spanning-tree portfast", "spanning-tree bpduguard enable", ...]

Проверить работу функции на примере словаря access_config и списка команд access_mode_template. Если предыдущая проверка прошла успешно, проверить работу функции еще раз на словаре access_config_2 и убедиться, что в итоговом списке правильные номера интерфейсов и вланов.

access_mode_template = [ "switchport mode access", "switchport access vlan", "switchport nonegotiate", "spanning-tree portfast", "spanning-tree bpduguard enable" ] access_config = { "FastEthernet0/12": 10, "FastEthernet0/14": 11, "FastEthernet0/16": 17 } access_config_2 = { "FastEthernet0/03": 100, "FastEthernet0/07": 101, "FastEthernet0/09": 107, } def generate_access_config(intf_vlan_mapping, access_template): """ intf_vlan_mapping - словарь с соответствием интерфейс-VLAN такого вида: {"FastEthernet0/12": 10, "FastEthernet0/14": 11, "FastEthernet0/16": 17} access_template - список команд для порта в режиме access Возвращает список всех портов в режиме access с конфигурацией на основе шаблона """

Решение
task_9_1.py

Задание 9.1а

Дополнить скрипт: ввести дополнительный параметр, который контролирует будет ли настроен port-security:

- имя параметра «psecurity»
- по умолчанию значение None
- для настройки port-security, как значение надо передать список команд port-security (находятся в списке port_security_template)

Функция должна возвращать список всех портов в режиме access с конфигурацией на основе шаблона access_mode_template и шаблона port_security_template, если он был передан. В конце строк в списке не должно быть символа перевода строки.

Проверить работу функции на примере словаря access_config, с генерацией конфигурации port-security и без.

Пример вызова функции:

print(generate_access_config(access_config, access_mode_template)) print(generate_access_config(access_config, access_mode_template, port_security_template))

Ограничение: Все задания надо выполнять используя только пройденные темы.

access_mode_template = [ "switchport mode access", "switchport access vlan", "switchport nonegotiate", "spanning-tree portfast", "spanning-tree bpduguard enable" ] port_security_template = [ "switchport port-security maximum 2", "switchport port-security violation restrict", "switchport port-security" ] access_config = {"FastEthernet0/12": 10, "FastEthernet0/14": 11, "FastEthernet0/16": 17}

Решение
task_9_1a.py

Задание 9.2

Создать функцию generate_trunk_config, которая генерирует конфигурацию для trunk-портов.

У функции должны быть такие параметры:
1) intf_vlan_mapping: ожидает как аргумент словарь с соответствием интерфейс-VLANы такого вида:

{"FastEthernet0/1": [10, 20], "FastEthernet0/2": [11, 30], "FastEthernet0/4": [17]}

2) trunk_template: ожидает как аргумент шаблон конфигурации trunk-портов в виде списка команд (список trunk_mode_template)

Функция должна возвращать список команд с конфигурацией на основе указанных портов и шаблона trunk_mode_template. В конце строк в списке не должно быть символа перевода строки.

Проверить работу функции на примере словаря trunk_config и списка команд trunk_mode_template. Если эта проверка прошла успешно, проверить работу функции еще раз на словаре trunk_config_2 и убедится, что в итоговом списке правильные номера интерфейсов и вланов.

Пример итогового списка (перевод строки после каждого элемента сделан для удобства чтения):

[ "interface FastEthernet0/1", "switchport mode trunk", "switchport trunk native vlan 999", "switchport trunk allowed vlan 10,20,30", "interface FastEthernet0/2", "switchport mode trunk", "switchport trunk native vlan 999", "switchport trunk allowed vlan 11,30", ...]
trunk_mode_template = [ "switchport mode trunk", "switchport trunk native vlan 999", "switchport trunk allowed vlan" ] trunk_config = { "FastEthernet0/1": [10, 20, 30], "FastEthernet0/2": [11, 30], "FastEthernet0/4": [17] }

Решение
python_40_ciscomaster.ru.jpg
task_9_2.py

Задание 9.2a

Сделать копию функции generate_trunk_config из задания 9.2

Изменить функцию таким образом, чтобы она возвращала не список команд, а словарь:

- ключи: имена интерфейсов, вида «FastEthernet0/1»
- значения: список команд, который надо выполнить на этом интерфейсе

Проверить работу функции на примере словаря trunk_config и шаблона trunk_mode_template.

Ограничение: Все задания надо выполнять используя только пройденные темы.

Решение
python_41_ciscomaster.ru.jpg
task_9_2a.py

Добавить комментарий

Filtered HTML

  • Адреса страниц и электронной почты автоматически преобразуются в ссылки.
  • Допустимые HTML-теги: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Строки и абзацы переносятся автоматически.

Plain text

  • HTML-теги не обрабатываются и показываются как обычный текст
  • Адреса страниц и электронной почты автоматически преобразуются в ссылки.
  • Строки и абзацы переносятся автоматически.
CAPTCHA
Этот вопрос задается для того, чтобы выяснить, являетесь ли Вы человеком или представляете из себя автоматическую спам-рассылку.
Target Image