Функции – это именованные блоки кода, предназначенные для выполнения одной конкретной работы.
Когда вы хотите выполнить определенную задачу, которую вы определили в функции, вы вызываете функцию, отвечающую за нее. Если вам нужно выполнить эту задачу несколько раз в вашей программе, вам не нужно снова и снова набирать весь код для одной и той же задачи; вы просто вызываете функцию, предназначенную для выполнения этой задачи, и этот вызов указывает Python на выполнение кода внутри функции. Вы увидите, что использование функций облегчает написание, чтение, тестирование и исправление ваших программ.
- Определение функции
- Передача информации в функцию
- Аргументы и параметры
- Передача аргументов
- Позиционные аргументы
- Многократные вызовы функций
- Порядок имеет значение в позиционных аргументах
- Аргументы с ключевыми словами
- Значения по умолчанию
- Эквивалентные вызовы функций
- Избегание ошибок аргумента
- Возвращаемые значения
- Возврат простого значения
- Сделать аргумент необязательным
- Возвращение словаря
- Использование функции с циклом while
- Передача списка
- Изменение списка в функции
- Запрет на изменение списка функцией
- Передача произвольного числа аргументов
- Смешивание позиционных и произвольных аргументов
- Использование произвольных аргументов ключевых слов
- Хранение функций в модулях
- Импорт целого модуля
- Импорт определенных функций
- Использование as для присвоения функции псевдонима
- Использование as для присвоения псевдонима модулю
- Импортирование всех функций в модуле
- Стилизация функций
Определение функции
Вот простая функция greet_user(), которая печатает приветствие:
def greet_user():
"""Display a simple greeting."""
print("Hello!")
greet_user()
Hello!
Этот пример показывает простейшую структуру функции.
-
Ключевое слово def сообщает Python, что вы определяете функцию. Это определение функции, которое сообщает Python имя функции и, если применимо, информацию о том, какая информация нужна функции для выполнения ее работы.
-
В круглых скобках содержится эта информация. В данном случае имя функции – greet_user(), и ей не требуется никакой информации для выполнения своей работы, поэтому ее круглые скобки пусты.
-
Наконец, определение заканчивается двоеточием.
Все строки с отступом, которые следуют за def greet_user():
, составляют тело функции. Текст “””Вывести простое приветствие.””” – это комментарий, называемый docstring, который описывает, что делает функция. Докстроки заключены в тройные кавычки, которые Python ищет при создании документации для функций в ваших программах.
Строка print(“Hello!”) – единственная строка реального кода в теле этой функции, поэтому greet_user() выполняет только одну работу: print("Hello!")
.
Когда вы хотите использовать эту функцию, вы вызываете ее. Вызов функции указывает Python на выполнение кода в функции. Чтобы вызвать функцию, вы пишете имя функции, за которым в круглых скобках следует любая необходимая информация.
Передача информации в функцию
Слегка модифицированная функция greet_user() может не только сказать пользователю “Привет!”, но и поприветствовать его по имени. Для этого в скобках определения функции def greet_user() вводится имя пользователя. Добавляя здесь имя пользователя, вы позволяете функции принимать любое значение имени пользователя, которое вы укажете. Теперь функция ожидает, что вы будете указывать значение имени пользователя каждый раз, когда вызываете ее. Когда вы вызываете greet_user(), вы можете передать ей имя, например, ‘Mohammed’, внутри круглых скобок:
def greet_user(username):
"""Display a simple greeting."""
print(f"Hello, {username.title()}!")
greet_user('Mohammed')
Hello, Mohammed!
Ввод greet_user(‘Mohammed’) вызывает greet_user() и дает функции информацию, необходимую для выполнения вызова print(). Функция принимает имя, которое вы ей передали, и выводит приветствие для этого имени.
Вы можете вызывать greet_user() сколько угодно раз и передавать ей любое имя, чтобы каждый раз получать предсказуемый результат.
Аргументы и параметры
В предыдущей функции greet_user() мы определили, что для greet_user() требуется значение переменной username. Как только мы вызвали функцию и передали ей информацию (имя человека), она вывела правильное приветствие.
Переменная username в определении функции greet_user() является примером параметра, части информации, которую функция выводит для приветствия.
параметра, части информации, необходимой функции для выполнения своей работы. Значение ‘Mohammed’ в greet_user(‘Mohammed’) является примером аргумента. Аргумент – это часть информации, которая передается от вызова функции к функции. Когда мы вызываем функцию, мы помещаем значение, с которым должна работать функция, в круглые скобки.
в круглые скобки. В данном случае аргумент ‘Mohammed’ был передан функции greet_user(), а значение было присвоено параметру username.
Иногда люди говорят об аргументах и параметрах как о взаимозаменяемых понятиях. Не удивляйтесь, если увидите, что переменные в определении функции называются аргументами или переменные в вызове функции называются параметрами.
Передача аргументов
Поскольку определение функции может иметь несколько параметров, вызов функции может нуждаться в нескольких аргументах. Вы можете передавать аргументы в функции несколькими способами. Вы можете использовать позиционные аргументы, которые должны располагаться в том же порядке, в котором были записаны параметры; аргументы с ключевыми словами, где каждый аргумент состоит из имени переменной и значения; списки и словари значений.
Позиционные аргументы
Когда вы вызываете функцию, Python должен сопоставить каждый аргумент в вызове функции с параметром в определении функции. Самый простой способ сделать это – основываться на порядке следования аргументов. Значения, сопоставленные таким образом, называются позиционными аргументами.
Чтобы понять, как это работает, рассмотрим функцию, которая выводит информацию о домашних животных. Функция сообщает нам, к какому виду относится каждый питомец и как его зовут:
def describe_pet(animal_type, pet_name):
"""Display information about a pet."""
print(f"nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
describe_pet('hamster', 'harry')
I have a hamster.
My hamster's name is Harry.
Из определения видно, что этой функции нужен тип животного и его имя. Когда мы вызываем describe_pet(), нам нужно предоставить тип животного и имя, именно в таком порядке.
Многократные вызовы функций
Вы можете вызывать функцию столько раз, сколько необходимо. Для описания второго, другого животного требуется еще один вызов функции describe_pet():
def describe_pet(animal_type, pet_name):
"""Display information about a pet."""
print(f"nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
describe_pet('hamster', 'harry')
describe_pet('dog', 'willie')
I have a hamster.
My hamster's name is Harry.
I have a dog.
My dog's name is Willie.
Вызов функции несколько раз – это очень эффективный способ работы. Код, описывающий питомца, пишется один раз в функции. Затем, когда вы хотите описать нового питомца, вы вызываете функцию с информацией о новом питомце. Даже если код для описания питомца разрастется до десяти строк, вы сможете описать нового питомца всего одной строкой, снова вызвав функцию.
В функциях можно использовать столько позиционных аргументов, сколько вам нужно. Python просматривает аргументы, которые вы предоставляете при вызове функции, и сопоставляет каждый из них с соответствующим параметром в определении функции.
Порядок имеет значение в позиционных аргументах
Вы можете получить неожиданные результаты, если перепутаете порядок аргументов в вызове функции при использовании позиционных аргументов:
def describe_pet(animal_type, pet_name):
"""Display information about a pet."""
print(f"nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
describe_pet('harry', 'hamster')
I have a harry.
My harry's name is Hamster.
В этом вызове функции мы перечисляем сначала имя, а затем тип животного. Поскольку аргумент ‘harry’ в этот раз указан первым, это значение присваивается параметру animal_type. Аналогично, ‘hamster’ присваивается параметру pet_name.
Аргументы с ключевыми словами
Аргумент ключевого слова – это пара имя-значение, которую вы передаете в функцию. Вы напрямую связываете имя и значение в аргументе, поэтому при передаче аргумента в функцию не возникает путаницы (в итоге у вас не получится Гарри по имени Хомяк). Аргументы с ключевыми словами избавляют вас от необходимости беспокоиться о правильном порядке аргументов в вызове функции и уточняют роль каждого значения в вызове функции.
def describe_pet(animal_type, pet_name):
"""Display information about a pet."""
print(f"nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
describe_pet(animal_type='hamster', pet_name='harry')
I have a hamster.
My hamster's name is Harry.
Функция describe_pet() не изменилась. Но когда мы вызываем функцию, мы явно указываем Python, с каким параметром должен быть сопоставлен каждый аргумент. Когда Python считывает вызов функции, он знает, что нужно присвоить аргумент ‘hamster’ параметру animal_type, а аргумент ‘harry’ – параметру pet_name. Вывод правильно показывает, что у нас есть хомяк по имени Гарри.
Порядок аргументов ключевых слов не имеет значения, потому что Python знает, куда следует отнести каждое значение. Следующие два вызова функций эквивалентны:
describe_pet(animal_type='hamster', pet_name='harry')
describe_pet(pet_name='harry', animal_type='hamster')
Когда вы используете аргументы с ключевыми словами, не забудьте использовать точные имена параметров в определении функции.
Значения по умолчанию
При написании функции вы можете определить значение по умолчанию для каждого параметра. Если аргумент для параметра указан в вызове функции, Python использует значение аргумента. Если нет, то используется значение параметра по умолчанию. Таким образом, определяя значение по умолчанию для параметра, вы можете исключить соответствующий аргумент, который обычно указывается в вызове функции. Использование значений по умолчанию может упростить вызов функций и прояснить способы, которыми обычно используются ваши функции.
Например, если вы заметили, что большинство вызовов функции describe_pet() используется для описания собак, вы можете установить значение по умолчанию параметра animal_type равным ‘dog’. Теперь каждый, кто вызывает describe_pet() для собаки, может опустить эту информацию:
def describe_pet(pet_name, animal_type='dog'):
"""Display information about a pet."""
print(f"nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
describe_pet(pet_name='willie')
I have a dog.
My dog's name is Willie.
Мы изменили определение функции describe_pet(), включив в нее значение по умолчанию, ‘dog’, для animal_type. Теперь, когда функция вызывается без указания типа животного, Python знает, что для этого параметра нужно использовать значение ‘dog’.
Обратите внимание, что порядок параметров в определении функции пришлось изменить. Поскольку значение по умолчанию делает ненужным указание типа животного в качестве аргумента, единственным аргументом в вызове функции остается имя питомца. Python по-прежнему интерпретирует его как позиционный аргумент, поэтому если вызвать функцию, указав только имя животного, то этот аргумент будет соответствовать первому параметру, указанному в определении функции. Именно по этой причине первым параметром должно быть имя_питомца.
Самый простой способ использовать эту функцию сейчас – предоставить только имя собаки в вызове функции describe_pet('willie')
.
Этот вызов функции будет иметь тот же результат, что и предыдущий пример. Единственным предоставленным аргументом является ‘willie’, поэтому он сопоставляется с первым параметром в определении, именем_животного. Поскольку для параметра animal_type не указан аргумент, Python использует значение по умолчанию ‘dog’.
Чтобы описать животное, отличное от собаки, можно использовать вызов функции, как показано ниже:
describe_pet(pet_name='harry', animal_type='hamster')
Поскольку для параметра animal_type указан явный аргумент, Python игнорирует значение параметра по умолчанию.
Когда вы используете значения по умолчанию, любой параметр со значением по умолчанию должен быть перечислен после всех параметров, которые не имеют значений по умолчанию. Это позволяет Python продолжать правильно интерпретировать позиционные аргументы.
Эквивалентные вызовы функций
Поскольку позиционные аргументы, аргументы ключевых слов и значения по умолчанию могут использоваться вместе, часто у вас есть несколько эквивалентных способов вызова функции. Рассмотрим следующее определение для describe_pet() с одним значением по умолчанию:
def describe_pet(pet_name, animal_type='dog'):
При таком определении всегда требуется аргумент для имени_животного, и это значение может быть предоставлено с помощью позиционного формата или формата ключевого слова. Если описываемое животное не является собакой, в вызов должен быть включен аргумент для типа животного, который также может быть указан в позиционном формате или в формате ключевого слова.
Для этой функции подойдут все следующие вызовы:
# A dog named Willie.
describe_pet('willie')
describe_pet(pet_name='willie')
# A hamster named Harry.
describe_pet('harry', 'hamster')
describe_pet(pet_name='harry', animal_type='hamster')
describe_pet(animal_type='hamster', pet_name='harry')
Не имеет значения, какой стиль вызова вы используете. До тех пор, пока вызовы функций будут выдавать нужный вам результат, просто используйте тот стиль, который вам легче всего понять.
Избегание ошибок аргумента
Когда вы начнете использовать функции, не удивляйтесь, если столкнетесь с ошибками, связанными с несочетаемыми аргументами. Несовпадающие аргументы возникают, когда вы предоставляете меньше или больше аргументов, чем требуется функции для выполнения ее работы. Например, вот что произойдет, если мы попытаемся вызвать describe_pet() без аргументов:
def describe_pet(animal_type, pet_name):
"""Display information about a pet."""
print(f"nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
describe_pet()
TypeError: describe_pet() missing 2 required positional arguments: 'animal_type' and 'pet_name'
Отслеживание говорит нам, что в вызове не хватает двух аргументов, и сообщает имена недостающих аргументов. Если бы эта функция находилась в отдельном файле, мы, вероятно, смогли бы правильно переписать вызов без необходимости открывать этот файл и читать код функции.
Python полезен тем, что он читает код функции за нас и сообщает нам имена аргументов, которые мы должны предоставить. Это еще одна мотивация для того, чтобы давать переменным и функциям описательные имена. В этом случае сообщения Python об ошибках будут более полезными для вас и всех остальных, кто может использовать ваш код.
Если вы предоставите слишком много аргументов, вы должны получить аналогичное сообщение об ошибке, которое поможет вам правильно сопоставить вызов функции с ее определением.
Возвращаемые значения
Функция не всегда должна отображать свой вывод напрямую. Вместо этого она может обработать некоторые данные, а затем вернуть значение или набор значений. Значение, которое возвращает функция, называется возвращаемым значением. Оператор return берет значение из функции и отправляет его обратно в строку, которая вызвала функцию. Возвращаемые значения позволяют перенести большую часть трудоемкой работы в функции, что может упростить тело вашей программы.
Возврат простого значения
Давайте рассмотрим функцию, которая принимает имя и фамилию и возвращает аккуратно отформатированное полное имя:
def get_formatted_name(first_name, last_name):
"""Return a full name, neatly formatted."""
full_name = f"{first_name} {last_name}"
return full_name.title()
boxer = get_formatted_name('mohammed', 'ali')
print(boxer)
Mohammed Ali
Когда вы вызываете функцию, возвращающую значение, вам необходимо предоставить переменную, которой можно присвоить возвращаемое значение. В данном случае возвращаемое значение присваивается переменной boxer.
Это может показаться большой работой для получения аккуратного форматированного имени, когда мы могли бы просто написать:
print("Mohammed Ali")
Но если учесть работу с большой программой, в которой нужно хранить много имен и фамилий отдельно, функции типа get_formatted_name() становятся очень полезными. Вы храните имена и фамилии отдельно, а затем вызываете эту функцию всякий раз, когда хотите отобразить полное имя.
Сделать аргумент необязательным
Иногда имеет смысл сделать аргумент необязательным, чтобы люди, использующие функцию, могли выбрать предоставление дополнительной информации только в том случае, если они этого хотят. Чтобы сделать аргумент необязательным, можно использовать значения по умолчанию.
Например, мы хотим расширить функцию get_formatted_name() для обработки средних имен. Первая попытка включить средние имена может выглядеть следующим образом:
def get_formatted_name(first_name, middle_name, last_name):
"""Return a full name, neatly formatted."""
full_name = f"{first_name} {middle_name} {last_name}"
return full_name.title()
user = get_formatted_name('ahmed', 'mohammed', 'gouda')
print(user)
Ahmed Mohammed Gouda
Эта функция работает, если заданы имя, отчество и фамилия. Но средние имена нужны не всегда, и эта функция в том виде, в котором она написана, не будет работать, если вы попытаетесь вызвать ее, указав только имя и фамилию.
Чтобы сделать второе имя необязательным, мы можем дать аргументу middle_name пустое значение по умолчанию и игнорировать его, если пользователь не укажет значение. Чтобы функция get_formatted_name() работала без среднего имени, мы установим значение по умолчанию для middle_name в виде пустой строки и переместим его в конец списка параметров:
def get_formatted_name(first_name, last_name, middle_name=''):
"""Return a full name, neatly formatted."""
if middle_name:
full_name = f"{first_name} {middle_name} {last_name}"
else:
full_name = f"{first_name} {last_name}"
return full_name.title()
user = get_formatted_name('ahmed', 'gouda')
print(user)
user = get_formatted_name('ahmed', 'gouda', 'mohammed')
print(user)
Ahmed Gouda
Ahmed Mohammed Gouda
Необязательные значения позволяют функциям обрабатывать широкий спектр случаев использования, при этом вызовы функций остаются максимально простыми.
Возвращение словаря
Функция может вернуть любое значение, которое вам нужно, включая более сложные структуры данных, такие как списки и словари. Например, следующая функция принимает части имени и возвращает словарь, представляющий человека:
def build_person(first_name, last_name):
"""Return a dictionary of information about a person."""
person = {'first': first_name, 'last': last_name}
return person
boxer = build_person('mohammed', 'ali')
print(boxer)
{'first': 'mohammed', 'last': 'ali'}
Функция build_person() принимает имя и фамилию и помещает эти значения в словарь. Значение first_name хранится с ключом ‘first’, а значение last_name – с ключом ‘last’. Возвращается весь словарь, представляющий человека. Возвращаемое значение печатается с исходными двумя частями текстовой информации, которые теперь хранятся в словаре.
Эта функция принимает простую текстовую информацию и помещает ее в более осмысленную структуру данных, которая позволяет работать с информацией, не ограничиваясь простой печатью. Строки ‘mohammed’ и ‘ali’ теперь обозначены как имя и фамилия. Вы можете легко расширить эту функцию, чтобы она принимала необязательные значения, такие как второе имя, возраст, род занятий или любую другую информацию, которую вы хотите хранить о человеке.
Например, следующее изменение позволяет хранить также возраст человека:
def build_person(first_name, last_name, age=None):
"""Return a dictionary of information about a person."""
person = {'first': first_name, 'last': last_name}
if age:
person['age'] = age
return person
boxer = build_person('mohammed', 'ali', age=74)
print(boxer)
{'first': 'mohammed', 'last': 'ali', 'age': 74}
Мы добавляем новый необязательный параметр age в определение функции и присваиваем параметру специальное значение None, которое используется, когда переменная не имеет определенного значения. Можно считать, что None – это значение-заглушка. В условных тестах None оценивается как False. Если вызов функции включает значение для возраста, это значение сохраняется в словаре. Эта функция всегда хранит имя человека, но ее можно модифицировать, чтобы хранить любую другую информацию о человеке.
Использование функции с циклом while
Мы можем использовать функцию get_formatted_name() с циклом while для более формального приветствия пользователей.
def get_formatted_name(first_name, last_name):
"""Return a full name, neatly formatted."""
full_name = f"{first_name} {last_name}"
return full_name.title()
while True:
print("nPlease tell me your name:")
print("(enter 'q' at any time to quit)n")
f_name = input("First name: ")
if f_name == 'q':
break
l_name = input("Last name: ")
if l_name == 'q':
break
formatted_name = get_formatted_name(f_name, l_name)
print(f"nHello, {formatted_name}!")
Please tell me your name:
(enter 'q' at any time to quit)
First name: omar
Last name: ali
Hello, Omar Ali!
Please tell me your name:
(enter 'q' at any time to quit)
First name: ahmed
Last name: gouda
Hello, Ahmed Gouda!
Please tell me your name:
(enter 'q' at any time to quit)
First name: q
Для завершения цикла while нам необходимо определить условие выхода. Поэтому мы добавим сообщение, информирующее пользователя о том, как выйти из программы, а затем выйдем из цикла, если пользователь введет значение quit в любой из подсказок. Программа будет продолжать приветствовать людей, пока кто-нибудь не введет ‘q’ для любого имени.
Передача списка
Часто бывает полезно передать в функцию список, будь то список имен, чисел или более сложных объектов, таких как словари. Когда вы передаете список в функцию, функция получает прямой доступ к содержимому списка. Давайте воспользуемся функциями, чтобы сделать работу со списками более эффективной.
Допустим, у нас есть список пользователей, и мы хотим напечатать каждому из них приветствие. В следующем примере список имен отправляется в функцию greet_users(), которая приветствует каждого человека в списке по отдельности:
def greet_users(names):
"""Print a simple greeting to each user in the list."""
for name in names:
msg = f"Hello, {name.title()}!"
print(msg)
usernames = ['mohammed', 'islam', 'ali']
greet_users(usernames)
Hello, Mohammed!
Hello, Islam!
Hello, Ali!
Это тот результат, который мы хотели получить. Каждый пользователь видит персональное приветствие, и вы можете вызывать эту функцию в любое время, когда захотите поприветствовать определенный набор пользователей.
Изменение списка в функции
Когда вы передаете список в функцию, функция может изменять этот список. Любые изменения, внесенные в список внутри тела функции, являются постоянными, что позволяет эффективно работать даже при работе с большими объемами данных.
Рассмотрим компанию, которая создает 3D-печатные модели дизайнов, представленных пользователями. Модели, которые необходимо напечатать, хранятся в списке, а после печати перемещаются в отдельный список. Следующий код делает это без использования функций:
# Start with some designs that need to be printed.
unprinted_designs = ['phone case', 'robot pendant', 'cube']
completed_models = []
# Simulate printing each design, until none are left.
# Move each design to completed_models after printing.
while unprinted_designs:
current_design = unprinted_designs.pop()
print(f"Printing model: {current_design}")
completed_models.append(current_design)
# Display all completed models.
print("nThe following models have been printed:")
for completed_model in completed_models:
print(completed_model)
Printing model: cube
Printing model: robot pendant
Printing model: phone case
The following models have been printed:
cube
robot pendant
phone case
Мы можем реорганизовать этот код, написав две функции, каждая из которых выполняет одну конкретную работу. Большая часть кода не изменится, мы просто сделаем его более тщательно структурированным. Первая функция будет выполнять печать рисунков, а вторая – подводить итоги сделанных отпечатков:
def print_models(unprinted_designs, completed_models):
"""
Simulate printing each design, until none are left.
Move each design to completed_models after printing.
"""
while unprinted_designs:
current_design = unprinted_designs.pop()
print(f"Printing model: {current_design}")
completed_models.append(current_design)
def show_completed_models(completed_models):
"""Show all the models that were printed."""
print("nThe following models have been printed:")
for completed_model in completed_models:
print(completed_model)
unprinted_designs = ['phone case', 'robot pendant', 'cube']
completed_models = []
print_models(unprinted_designs, completed_models)
show_completed_models(completed_models)
Printing model: cube
Printing model: robot pendant
Printing model: phone case
The following models have been printed:
cube
robot pendant
phone case
Эта программа имеет тот же вывод, что и версия без функций, но код гораздо более организован. Код, выполняющий большую часть работы, был перенесен в две отдельные функции, что делает основную часть программы более понятной. Посмотрите на тело программы, чтобы понять, насколько легче понять, что делает эта программа.
unprinted_designs = ['phone case', 'robot pendant', 'dodecahedron']
completed_models = []
print_models(unprinted_designs, completed_models)
show_completed_models(completed_models)
Мы создаем список ненапечатанных моделей и пустой список, в котором будут храниться готовые модели. Затем, поскольку мы уже определили две наши функции, нам остается только вызвать их и передать им нужные аргументы. Мы вызываем print_models() и передаем ей два необходимых списка; как и ожидалось, print_models() имитирует печать моделей. Затем мы вызываем show_completed_models() и передаем ей список завершенных моделей, чтобы она могла сообщить о моделях, которые были напечатаны. Описательные имена функций позволяют другим читать этот код и понимать его даже без комментариев.
Эту программу легче расширять и поддерживать, чем версию без функций. Если позже нам понадобится напечатать больше моделей, мы можем просто снова вызвать print_models(). Если мы поймем, что код печати нужно изменить, мы можем изменить код один раз, и наши изменения будут происходить везде, где вызывается функция. Эта техника более эффективна, чем обновление кода по отдельности в нескольких местах программы.
Этот пример также демонстрирует идею о том, что у каждой функции должна быть одна конкретная задача. Первая функция распечатывает каждый дизайн, а вторая отображает готовые модели. Это более выгодно, чем использовать одну функцию для выполнения обеих задач. Если вы пишете функцию и замечаете, что она выполняет слишком много разных задач, попробуйте разделить код на две функции. Помните, что вы всегда можете вызвать функцию из другой функции, что может быть полезно при разбиении сложной задачи на ряд шагов.
Запрет на изменение списка функцией
Иногда необходимо запретить функции изменять список. Например, предположим, что вы начинаете со списка ненапечатанных моделей и пишете функцию для перемещения их в список завершенных моделей, как в предыдущем примере. Вы можете решить, что, хотя вы напечатали все модели, вы хотите сохранить исходный список ненапечатанных моделей для своих записей. Но поскольку вы удалили все имена дизайнов из unprinted_designs, список стал пустым, а пустой список – это единственная версия, которая у вас есть; оригинал исчез. В этом случае вы можете решить эту проблему, передав функции копию списка, а не оригинал. Любые изменения, которые функция внесет в список, затронут только копию, а оригинал останется нетронутым.
Копию списка можно передать функции следующим образом:
function_name(list_name[:])
Нотация slice [:]
создает копию списка для отправки в функцию. Если бы мы не хотели очищать список от ненапечатанных моделей, мы могли бы вызвать print_models() следующим образом:
print_models(unprinted_designs[:], completed_models)
Функция print_models() может выполнять свою работу, поскольку она по-прежнему получает имена всех непечатных дизайнов. Но на этот раз она использует копию исходного списка непечатных дизайнов, а не реальный список unprinted_designs. Список completed_models будет заполнен именами напечатанных моделей, как и раньше, но оригинальный список не напечатанных дизайнов не будет затронут функцией.
Даже если вы можете сохранить содержимое списка, передавая его копию в функции, вы должны передавать в функции оригинальный список, если у вас нет особых причин передавать его копию. Для функции эффективнее работать с существующим списком, чтобы не тратить время и память на создание отдельной копии, особенно при работе с большими списками.
Передача произвольного числа аргументов
Иногда вы не можете заранее знать, сколько аргументов должна принять функция. К счастью, Python позволяет функции получать произвольное количество аргументов от вызывающего оператора.
Например, рассмотрим функцию, которая строит пиццу. Она должна принимать определенное количество начинок, но вы не можете заранее знать, сколько начинок захочет человек. Функция в следующем примере имеет один параметр, *toppings, но этот параметр собирает столько аргументов, сколько предоставляет вызывающая строка:
def make_pizza(*toppings):
"""Print the list of toppings that have been requested."""
print(toppings)
make_pizza('pepperoni')
make_pizza('mushrooms', 'green peppers', 'extra cheese')
('pepperoni',)
('mushrooms', 'green peppers', 'extra cheese')
Звездочка в имени параметра *toppings указывает Python создать пустой кортеж под названием toppings и упаковать все полученные значения в этот кортеж. Вызов print() в теле функции выводит результат, показывающий, что Python может обрабатывать вызов функции с одним значением и вызов с тремя значениями. Он обрабатывает разные вызовы одинаково. Обратите внимание, что Python упаковывает аргументы в кортеж, даже если функция получает только одно значение.
def make_pizza(*toppings):
"""Summarize the pizza we are about to make."""
print("nMaking a pizza with the following toppings:")
for topping in toppings:
print(f"- {topping}")
make_pizza('pepperoni')
make_pizza('mushrooms', 'green peppers', 'extra cheese')
Making a pizza with the following toppings:
- pepperoni
Making a pizza with the following toppings:
- mushrooms
- green peppers
- extra cheese
Функция реагирует соответствующим образом, независимо от того, получает ли она одно значение или три. Этот синтаксис работает независимо от того, сколько аргументов получает функция.
Смешивание позиционных и произвольных аргументов
Если вы хотите, чтобы функция принимала несколько различных видов аргументов, параметр, принимающий произвольное количество аргументов, должен быть помещен последним в определении функции. Python сначала принимает позиционные и ключевые аргументы, а затем собирает все оставшиеся аргументы в последний параметр.
Например, если функция должна принимать размер пиццы, этот параметр должен идти перед параметром *toppings:
def make_pizza(size, *toppings):
"""Summarize the pizza we are about to make."""
print(f"nMaking a {size}-inch pizza with the following toppings:")
for topping in toppings:
print(f"- {topping}")
make_pizza(16, 'pepperoni')
make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')
Making a 16-inch pizza with the following toppings:
- pepperoni
Making a 12-inch pizza with the following toppings:
- mushrooms
- green peppers
- extra cheese
В определении функции Python присваивает первое полученное значение параметру size. Все остальные последующие значения хранятся в кортеже toppings. Вызовы функций включают сначала аргумент size, а затем столько toppings, сколько необходимо.
Часто можно встретить общее имя параметра *args, в котором собраны произвольные позиционные аргументы, подобные этому.
Использование произвольных аргументов ключевых слов
Иногда вам нужно принять произвольное количество аргументов, но вы не знаете заранее, какая информация будет передана в функцию. В этом случае вы можете написать функции, которые принимают столько пар ключ-значение, сколько указано в вызывающем операторе. Один из примеров связан с построением профилей пользователей: вы знаете, что получите информацию о пользователе, но не уверены, какого рода информацию вы получите. Функция build_profile() в следующем примере всегда принимает имя и фамилию, но она также принимает произвольное количество аргументов в виде ключевых слов:
def build_profile(first, last, **user_info):
"""Build a dictionary containing everything we know about a user."""
user_info['first_name'] = first
user_info['last_name'] = last
return user_info
user_profile = build_profile('albert', 'einstein',
location='princeton',
field='physics',)
print(user_profile)
Определение build_profile() ожидает имя и фамилию, а затем позволяет пользователю передать столько пар имя-значение, сколько он захочет. Двойные звездочки перед параметром **user_info заставляют Python создать пустой словарь user_info и упаковать все полученные пары имя-значение в этот словарь. Внутри функции вы можете обращаться к парам “ключ-значение” в user_info так же, как и к любому другому словарю.
В теле функции build_profile() мы добавляем имя и фамилию в словарь user_info, потому что мы всегда будем получать эти две части информации от пользователя, а они еще не были помещены в словарь. Затем мы возвращаем словарь user_info в строку вызова функции.
Мы вызываем функцию build_profile(), передавая ей имя ‘albert’, фамилию ‘einstein’ и две пары ключ-значение location=’princeton’ и field=’physics’. Мы присваиваем полученный профиль user_profile и выводим user_profile:
{'location': 'princeton', 'field': 'physics', 'first_name': 'albert', 'last_name': 'einstein'}
Возвращаемый словарь содержит имя и фамилию пользователя, а в данном случае также местоположение и специальность. Функция будет работать независимо от того, сколько дополнительных пар ключ-значение будет предоставлено при
вызове функции.
При написании собственных функций можно по-разному сочетать позиционные, ключевые и произвольные значения. Полезно знать, что все эти типы аргументов существуют, потому что вы будете часто их видеть, когда начнете читать чужой код. Чтобы научиться правильно использовать различные типы и знать, когда нужно использовать каждый тип, потребуется практика. Пока что помните, что нужно использовать самый простой подход, который позволяет выполнить работу. По мере продвижения вы научитесь каждый раз использовать наиболее эффективный подход.
Часто можно встретить имя параметра **kwargs, используемое для сбора неспецифических аргументов ключевых слов.
Хранение функций в модулях
Одним из преимуществ функций является то, что они отделяют блоки кода от основной программы. Если использовать описательные имена для функций, за основной программой будет гораздо легче следить. Вы можете пойти еще дальше, храня свои функции в отдельном файле, называемом модулем, а затем импортируя этот модуль в свою основную программу. Оператор import указывает Python сделать код модуля доступным в текущем файле программы.
Хранение функций в отдельном файле позволяет скрыть детали кода вашей программы и сосредоточиться на ее высокоуровневой логике. Это также позволяет повторно использовать функции в различных программах. Когда вы храните функции в отдельных файлах, вы можете делиться этими файлами с другими программистами без необходимости делиться всей своей программой. Умение импортировать функции также позволяет использовать библиотеки функций, написанные другими программистами.
Существует несколько способов импорта модуля.
Импорт целого модуля
Чтобы начать импортировать функции, сначала нужно создать модуль.
Модуль – это файл, заканчивающийся на .py, который содержит код, который вы хотите импортировать в свою программу.
Мы можем сделать модуль, содержащий функцию make_pizza(), поместив определение функции в отдельный файл (например, с именем pizza), а затем импортировать этот файл туда, где нам нужно использовать.
Файл pizza. py
def make_pizza(size, *toppings):
"""Summarize the pizza we are about to make."""
print(f"nMaking a {size}-inch pizza with the following toppings:")
for topping in toppings:
print(f"- {topping}")
Файл making_pizzas. py (в том же каталоге, что и pizza. py)
import pizza
pizza.make_pizza(16, 'pepperoni')
pizza.make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')
Когда Python читает этот файл, строка import pizza говорит Python открыть файл pizza.py и скопировать все функции из него в эту программу. На самом деле вы не видите, как код копируется между файлами, потому что Python копирует код за кулисами непосредственно перед запуском программы. Все, что вам нужно знать, это то, что любая функция, определенная в pizza.py, теперь будет доступна в making_pizzas.py.
Этот первый подход к импорту, при котором вы просто пишете import, за которым следует имя модуля, делает каждую функцию из модуля доступной в вашей программе. Если вы используете этот тип оператора import для импорта
всего модуля с именем module_name. py, то каждая функция модуля будет доступна с помощью следующего синтаксиса:
module_name.function_name()
Импорт определенных функций
Вы также можете импортировать определенную функцию из модуля. Вот общий синтаксис для этого подхода:
from module_name import function_name
Вы можете импортировать из модуля столько функций, сколько хотите, разделяя имя каждой функции запятой:
from module_name import function_0, function_1, function_2
Пример making_pizzas. py будет выглядеть следующим образом, если мы хотим импортировать только ту функцию, которую собираемся использовать:
from pizza import make_pizza
make_pizza(16, 'pepperoni')
make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')
При таком синтаксисе вам не нужно использовать точечную нотацию при вызове функции. Поскольку мы явно импортировали функцию make_pizza() в операторе import, мы можем вызывать ее по имени, когда используем функцию.
Использование as для присвоения функции псевдонима
Если имя импортируемой функции может конфликтовать с существующим именем в вашей программе или если имя функции длинное, вы можете использовать короткий уникальный псевдоним – альтернативное имя, похожее на прозвище для функции. Вы дадите функции этот специальный псевдоним, когда будете импортировать функцию.
Здесь мы даем функции make_pizza() псевдоним mp(), импортируя make_pizza как mp. Ключевое слово as переименовывает функцию, используя указанный вами псевдоним:
from pizza import make_pizza as mp
mp(16, 'pepperoni')
mp(12, 'mushrooms', 'green peppers', 'extra cheese')
Показанный здесь оператор import переименовывает функцию make_pizza() в mp() в этой программе. В любой момент, когда мы захотим вызвать make_pizza(), мы можем просто написать вместо нее mp(), и Python выполнит код в make_pizza(), избегая путаницы с другой функцией make_pizza(), которую вы могли написать в этом программном файле.
Общий синтаксис для указания псевдонима следующий:
from module_name import function_name as fn
Использование as для присвоения псевдонима модулю
Вы также можете задать псевдоним для имени модуля. Присвоение модулю короткого псевдонима, например, p для pizza, позволяет быстрее вызывать функции модуля. Вызов p.make_pizza()
более краток, чем вызов pizza.make_pizza()
:
import pizza as p
p.make_pizza(16, 'pepperoni')
p.make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')
В операторе импорта модулю pizza присвоен псевдоним p, но все функции модуля сохранили свои оригинальные имена. Вызов функций через p.make_pizza()
не только более краток, чем pizza.make_pizza()
, но и отвлекает внимание от имени модуля и позволяет сосредоточиться на описательных именах его функций. Эти имена функций, которые ясно говорят вам, что делает каждая функция, более важны для удобочитаемости вашего кода, чем использование полного имени модуля.
Общий синтаксис для этого подхода следующий:
import module_name as mn
Импортирование всех функций в модуле
Вы можете указать Python импортировать каждую функцию в модуле с помощью оператора звездочки (*):
from pizza import *
make_pizza(16, 'pepperoni')
make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')
Звездочка в операторе import указывает Python скопировать каждую функцию из модуля pizza в этот программный файл. Поскольку каждая функция импортирована, вы можете вызывать каждую функцию по имени без использования точечной нотации. Однако лучше не использовать этот подход при работе с большими модулями, которые вы не писали: если у модуля есть имя функции, совпадающее с существующим именем в вашем проекте, вы можете получить неожиданные результаты. Python может увидеть несколько функций или переменных с одинаковым именем, и вместо того, чтобы импортировать все функции по отдельности, он перезапишет их.
Лучший подход – импортировать нужную вам функцию или функции, или импортировать весь модуль и использовать точечную нотацию. Это приведет к чистому коду, который легко читать и понимать.
Стилизация функций
Функции должны иметь описательные имена, причем в этих именах должны использоваться строчные буквы и знаки подчеркивания. Описательные имена помогают вам и другим понять, что пытается сделать ваш код. Имена модулей также должны использовать эти соглашения.
Каждая функция должна иметь комментарий, который кратко объясняет, что делает функция. Этот комментарий должен появляться сразу после определения функции и использовать формат docstring. В хорошо документированной функции другие программисты могут использовать функцию, читая только описание в doc-строке. Они должны быть уверены, что код работает так, как описано, и если они знают имя функции, аргументы, которые ей нужны, и вид возвращаемого ею значения, они смогут использовать ее в своих программах.
Если вы указываете значение по умолчанию для параметра, пробелы не должны использоваться по обе стороны от знака равенства:
def function_name(parameter_0, parameter_1='default value')
Такое же соглашение следует использовать для аргументов ключевых слов в вызовах функций:
function_name(value_0, parameter_1='value')
Если из-за набора параметров длина определения функции превышает 79 символов, нажмите enTer после открывающей круглой скобки в строке определения. На следующей строке дважды нажмите TaB, чтобы отделить список аргументов от тела функции, которое будет отступлено только на один уровень.
Большинство редакторов автоматически выстраивают все дополнительные строки параметров в соответствии с отступом, установленным в первой строке:
def function_name(
parameter_0, parameter_1, parameter_2,
parameter_3, parameter_4, parameter_5):
function body...
Если ваша программа или модуль содержит более одной функции, вы можете отделить каждую из них двумя пустыми строками, чтобы было легче понять, где заканчивается одна функция и начинается следующая.
Все операторы импорта должны быть написаны в начале файла. Единственным исключением является использование комментариев в начале файла для описания программы в целом.