ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 03.12.2023
Просмотров: 386
Скачиваний: 1
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
Итоги
193
Антигравитация в Python
Введите следующую команду в интерактивной оболочке:
>>> import antigravity
Эта строка — забавная пасхалка, которая открывает в браузере классический комикс
XKCD о Python (https://xkcd.com/353/). Вас может удивить, что Python открывает ваш браузер, но это встроенная возможность, предоставляемая модулем webbrowser
Модуль Python webbrowser содержит функцию open()
, которая находит браузер по умолчанию вашей операционной системы и открывает его окно с заданным URL- адресом. Введите следующую команду в интерактивной оболочке:
>>>
1 ... 15 16 17 18 19 20 21 22 ... 40
import webbrowser
>>> webbrowser.open('https://xkcd.com/353/')
Возможности модуля webbrowser ограничены, но он позволяет легко перенаправить пользователя на дополнительную информацию в интернете.
Итоги
Мы часто забываем, что компьютеры и языки программирования создавались людьми и у них есть свои ограничения. Очень многие программы зависят от предпо- чтений и вкусов проектировщиков языков и разработчиков оборудования. Эти люди прикладывают неимоверные усилия к тому, чтобы все ошибки в ваших программах возникали из-за дефектов самой программы, а не интерпретатора или процессора, на котором программа выполняется. В конечном итоге мы начинаем воспринимать эти инструменты как нечто данное свыше. Но именно в этом случае становится ясной ценность изучения потаенных «уголков» и «закоулков» компьютеров и про- граммного обеспечения. Когда в вашем коде возникают ошибки или фатальные сбои (или он просто странно ведет себя, и вы начинаете думать: «Здесь что-то не так»), необходимо знать стандартные ловушки, чтобы устранить эти проблемы.
Вы почти наверняка не столкнетесь ни с одной из странностей работы кода, упо- мянутых в этой главе, но именно их знание сделает вас настоящим программистом на языке Python.
10
Написание эффективных функций
Функции — своего рода мини-программы, которые позво- ляют разбить код на меньшие части. Они избавляют нас от необходимости писать повторяющийся код, который может стать причиной ошибок. Но для написания эффективных функций приходится принимать много решений — относительно имени, размера, параметров и сложности.
В этой главе я покажу различные способы написания функций, а также достоинства и недостатки различных компромиссных решений. Мы разберемся, когда стоит от- давать предпочтение большим или малым функциям, как количество параметров влияет на сложность функции и как писать функции с переменным количеством аргументов при помощи операторов
*
и
**
. Также в этой главе я расскажу о пара- дигме функционального программирования и преимуществах написания функций в соответствии с этой парадигмой.
Имена функций
Имена функций подчиняются тем же правилам, что и имена идентификаторов во- обще (см. главу 4). Но обычно имена функций должны включать глагол, потому что функции выполняют некоторое действие. Также можно включить существи- тельное для описания объекта, с которым это действие выполняется. Например, имена refreshConnection()
, setPassword()
и extract_version()
хорошо поясняют, что делает функция и к чему применяется действие.
Существительное может оказаться излишним для методов, которые являются частью класса или модуля. Метод reset()
, принадлежащий классу
SatelliteConnection
, или функция open()
модуля webbrowser уже предоставляют необходимый контекст.
Плюсы и минусы размера функций
195
Из названий и так ясно, что satellite connection — это то, что перезагружается (reset), а веб-браузер — это то, что открывается (open).
Лучше использовать длинные, содержательные имена вместо сокращений или слишком коротких имен. Возможно, математик сразу поймет, что функция с именем gcd()
возвращает наибольший общий делитель двух чисел, но для всех остальных имя getGreatestCommonDenominator()
станет более понятным.
Не используйте для своих переменных или функций имена встроенных функций или модулей Python — такие как all
, any
, date
, file
, format
, hash
, id
, input
, list
, min
, max
, object
, open
, random
, set
, str
, sum
, test и type
Плюсы и минусы размера функций
Некоторые программисты считают, что функции должны быть по возможности короткими, а весь код — помещаться на одном экране. Функцию, которая занимает всего десяток строк, легко понять — по крайней мере в отличие от той, что состоит из сотни строк. Однако разбиение кода функции на несколько меньших функций так- же имеет свои недостатки. Рассмотрим некоторые преимущества малых функций.
Код функции проще понять.
Функции, скорее всего, требуется меньше параметров.
Снижается вероятность побочных эффектов (см. «Функциональное про- граммирование», с. 206).
Упрощается тестирование и отладка функции.
Вероятно, функция будет выдавать меньше разных видов исключений.
Однако у коротких функций имеются свои недостатки.
Написание коротких функций часто означает увеличение их числа в про- грамме.
Чем больше функций, тем сложнее становится программа.
Увеличение количества функций также означает, что вам придется приду- мывать больше содержательных и точных имен, а это не так просто.
Вам придется писать больше документации.
Чем больше функций, тем сложнее связи между ними.
Некоторые программисты доводят принцип «чем короче, тем лучше» до крайности и заявляют, что все функции должны содержать не более трех-четырех строк кода.
Это настоящее безумие. Для примера ниже приведена функция getPlayerMove()
196
Глава 10.Написание эффективных функций из игры «Ханойские башни» (см. главу 14). Конкретные подробности работы этого кода сейчас не важны. Просто взгляните на общую структуру функции:
def getPlayerMove(towers):
"""Запрашивает ход у пользователя. Возвращает (fromTower, toTower)."""
while True: # Пока пользователь не введет допустимый ход.
print('Enter the letters of "from" and "to" towers, or QUIT.')
print("(e.g. AB to moves a disk from tower A to tower B.)")
print()
response = input("> ").upper().strip()
if response == "QUIT":
print("Thanks for playing!")
sys.exit()
# Проверить, что пользователь ввел допустимые буквы:
if response not in ("AB", "AC", "BA", "BC", "CA", "CB"):
print("Enter one of AB, AC, BA, BC, CA, or CB.")
continue # Снова запросить ход у пользователя.
# Использовать более содержательные имена переменных:
fromTower, toTower = response[0], response[1]
if len(towers[fromTower]) == 0:
# Башня "from" не может быть пустой:
print("You selected a tower with no disks.")
continue # Снова запросить ход у пользователя.
elif len(towers[toTower]) == 0:
# Любой диск можно переместить на пустую башню "to":
return fromTower, toTower elif towers[toTower][-1] < towers[fromTower][-1]:
print("Can't put larger disks on top of smaller ones.")
continue # Снова запросить ход у пользователя.
else:
# Ход проверен, вернуть выбранные башни:
return fromTower, toTower
Функция состоит из 34 строк. И хотя она решает несколько задач — ввод хода пользователем, проверка допустимости этого хода, повторный запрос хода в том случае, если ввод недопустим, — эти задачи относятся к категории получения хода пользователя. С другой стороны, если бы мы фанатично стремились к написанию коротких функций, код getPlayerMove()
можно было бы разбить на меньшие функции:
def getPlayerMove(towers):
"""Запрашивает ход у пользователя. Возвращает (fromTower, toTower)."""
while True: # Пока пользователь не введет допустимый ход.
response = askForPlayerMove()
Плюсы и минусы размера функций
197
terminateIfResponseIsQuit(response)
if not isValidTowerLetters(response):
continue # Снова запросить ход у пользователя.
# Использовать более содержательные имена переменных:
fromTower, toTower = response[0], response[1]
if towerWithNoDisksSelected(towers, fromTower):
continue # Снова запросить ход у пользователя.
elif len(towers[toTower]) == 0:
# Любой диск можно переместить на пустую башню "to":
return fromTower, toTower elif largerDiskIsOnSmallerDisk(towers, fromTower, toTower):
continue # Снова запросить ход у пользователя.
else:
# Ход проверен, вернуть выбранные башни:
return fromTower, toTower def askForPlayerMove():
"""Выдает запрос и возвращает выбранные башни."""
print('Enter the letters of "from" and "to" towers, or QUIT.')
print("(e.g. AB to moves a disk from tower A to tower B.)")
print()
return input("> ").upper().strip()
def terminateIfResponseIsQuit(response):
"""Завершить программу, если введен ответ 'QUIT'"""
if response == "QUIT":
print("Thanks for playing!")
sys.exit()
def isValidTowerLetters(towerLetters):
"""Возвращает True, если значение `towerLetters` допустимо."""
if towerLetters not in ("AB", "AC", "BA", "BC", "CA", "CB"):
print("Enter one of AB, AC, BA, BC, CA, or CB.")
return False return True def towerWithNoDisksSelected(towers, selectedTower):
"""Возвращает True, если `selectedTower` не содержит дисков."""
if len(towers[selectedTower]) == 0:
print("You selected a tower with no disks.")
return True return False def largerDiskIsOnSmallerDisk(towers, fromTower, toTower):
"""Возвращает True, если больший диск перекладывается на меньший."""
if towers[toTower][-1] < towers[fromTower][-1]:
print("Can't put larger disks on top of smaller ones.")
return True return False
198
Глава 10.Написание эффективных функций
Эти шесть функций занимают 56 строк, почти вдвое больше, чем у исходной версии, но делают они то же самое. Хотя каждую функцию проще понять, чем ис- ходную функцию getPlayerMove()
, группировка этих функций приводит к возрас- танию сложности. Читателям вашего кода трудно понять, как взаимодействуют все эти функции. Функция getPlayerMove()
— единственная, которая вызывается из других частей программы; остальные пять функций вызываются только один раз из getPlayerMove()
. Однако большое количество функций не поясняет этот факт.
Мне также пришлось изобретать новые имена и doc-строки (строки в тройных кавычках под каждой командой def
, более подробно они описаны в главе 11) для каждой новой функции. Это приводит к появлению функций с похожими име- нами — например, getPlayerMove()
и askForPlayerMove()
. Кроме того, функция getPlayerMove()
все равно длиннее трех-четырех строк, и если бы я следовал принципу «чем короче, тем лучше», ее пришлось бы еще «измельчить»!
В данном случае политика использования самых коротких функций привела к опре- делению более простых функций, но общая сложность программы заметно возросла.
На мой взгляд, в идеале функции должны быть короче 30 строк и ни в коем случае не длиннее 200 строк. Делайте свои функции короткими в пределах разумного.
Параметры и аргументы функций
Параметры функции представляют собой имена переменных, заключенные в круг- лые скобки в команде def этой функции, тогда как аргументами называются значе- ния, указанные в круглых скобках при вызове функции. Чем больше параметров у функции, тем больше возможностей для настройки и обобщения ее кода. Но увеличение количества параметров также означает повышение сложности.
Предложу хорошее правило: от нуля до трех параметров — это нормально, но больше пяти или шести, пожалуй, слишком много. Если функции становятся очень сложными, лучше рассмотреть возможность их разбиения на меньшие функции с меньшим количеством параметров.
Аргументы по умолчанию
Один из способов сокращения сложности параметров функций заключается в опре- делении аргументов по умолчанию для параметров. Аргумент по умолчанию пред- ставляет собой значение, которое будет использоваться как аргумент, если он не указан явно при вызове функции. Если при большинстве вызовов функций исполь- зуется конкретное значение параметра, то это значение можно сделать аргументом по умолчанию, чтобы его не приходилось многократно указывать при вызове. Ар- гумент по умолчанию задается в команде def
, за ним следует имя параметра и знак
Параметры и аргументы функций
199
равенства. Например, в функции introduction()
параметру с именем greeting присваивается значение 'Hello'
, если оно не задается при вызове функции:
>>> def introduction(name, greeting='Hello'):
... print(greeting + ', ' + name)
>>> introduction('Alice')
Hello, Alice
>>> introduction('Hiro', 'Ohiyo gozaimasu')
Ohiyo gozaimasu, Hiro
Когда функция introduction()
вызывается без второго аргумента, по умолчанию используется строка 'Hello'
. Учтите, что параметры с аргументами по умолчанию всегда должны следовать после параметров без аргументов по умолчанию.
Вспомните, о чем мы говорили в главе 8: изменяемые объекты (такие как пустой список
[]
или пустой словарь
{}
) никогда не следует использовать в качестве зна- чения по умолчанию. В подразделе «Не используйте изменяемые значения для аргументов по умолчанию», с. 174, объясняется проблема, которая может при этом возникнуть, и способы ее решения.
Использование * и ** для передачи аргументов функции
Вы можете использовать синтаксисы
*
и
**
для раздельной передачи групп ар- гументов функциям. Синтаксис
*
позволяет передать элементы итерируемого объекта (такого как список или кортеж). Синтаксис
**
позволяет передавать пары
«ключ — значение» из объекта отображения (например, словаря) как отдельные аргументы.
Например, функция print()
может получать несколько аргументов. По умолчанию при выводе аргументы разделяются пробелами:
>>> print('cat', 'dog', 'moose')
cat dog moose
Эти аргументы называются позиционными (positional arguments), потому что их позиция в вызове функции определяет, какой аргумент должен быть присвоен тому или иному параметру. Но если сохранить эти строки в списке и попытаться передать список, функция print()
решит, что вы хотите передать список как одно значение:
>>> args = ['cat', 'dog', 'moose']
>>> print(args)
['cat', 'dog', 'moose']
При передаче списка функция print()
выводит список, включая квадратные скобки, кавычки и запятые.
200
Глава 10.Написание эффективных функций
В одном из способов вывода отдельных элементов при вызове указывается индекс каждого элемента. Такой код хуже читается:
>>> # Пример плохо читаемого кода:
>>> args = ['cat', 'dog', 'moose']
>>> print(args[0], args[1], args[2])
cat dog moose
Но существует более простой способ передачи значений print()
. Синтаксис
*
можно использовать для интерпретации элементов списка (или любого другого итерируемого типа данных) как отдельных позиционных аргументов. Введите следующий пример в интерактивной оболочке:
>>> args = ['cat', 'dog', 'moose']
>>> print(*args)
cat dog moose
Синтаксис
*
позволяет передать функции элементы списка по отдельности, сколько бы элементов ни содержал список.
Синтаксис
**
используется для передачи типов данных отображений (например, словарей) как набора ключевых аргументов. При передаче ключевого аргумента ука- зывается имя параметра, за которым следует знак равенства. Например, у функции print()
имеется ключевой аргумент sep
, который определяет строку, разделяющую аргументы при выводе. По умолчанию используется строка из одного пробела '
'
Ключевому аргументу можно присвоить другое значение командой присваивания или синтаксисом
**
. Чтобы понять, как работает этот синтаксис, введите следующий фрагмент в интерактивной оболочке:
>>> print('cat', 'dog', 'moose', sep='-')
cat-dog-moose
>>> kwargsForPrint = {'sep': '-'}
>>> print('cat', 'dog', 'moose', **kwargsForPrint)
cat-dog-moose
Обратите внимание: эти инструкции генерируют одинаковый вывод. В примере для создания словаря kwargsForPrint используется только одна строка кода. Но в более сложных сценариях создание словаря ключевых аргументов потребует большего объема кода. Синтаксис
**
позволяет создать специализированный словарь с на- стройками конфигурации, которые должны передаваться при вызове функции.
Данная возможность особенно полезна для функций и методов с большим коли- чеством ключевых аргументов.
Изменяя список или словарь во время выполнения, можно передавать при вызове функции разное количество аргументов с использованием синтаксисов
*
и
**