Как устроен Python. Как устроен Python. Харрисон. Харрисон Мэтт
Скачать 5.41 Mb.
|
17.1. Вызов функций В Python функции вызываются по имени функции, за которым следуют круглые скобки. В следующем фрагменте вызывается только что опре- деленная функция add_2 : >>> add_2(3) 5 Чтобы вызвать функцию, укажите ее имя, за которым следует открываю- щая круглая скобка, входные параметры и закрывающая круглая скобка. 156 Глава 17. Функции Количество параметров должно соответствовать количеству параметров в объявлении функции. Обратите внимание: REPL выводит результат вызова — целое число 5 (то, что возвращает команда return ). Функции add_2 можно передать произвольный объект. Но если этот объект не поддерживает сложение с числами, будет выдано исключение. При передаче строки выдается исключение TypeError : >>> add_2('hello') Traceback (most recent call last): TypeError: must be str, not int 17.2. Область видимости Python ищет переменные в разных местах. Эти места называются обла- стями видимости или пространствами имен. При поиске переменной (не забывайте, что функции в Python также являются переменными — как и классы, модули и т. д.), Python выполняет поиск в следующих местах и в следующем порядке: Локальная область видимости — переменные, определенные внутри функций. Глобальная область видимости — переменные, определяемые на гло- бальном уровне. Встроенная область видимости — переменные, заранее определен- ные в Python. В следующем коде поиск переменных осуществляется по всем трем об- ластям видимости: >>> x = 2 # Глобальная >>> def scope_demo(): ... y = 4 # Локальная для scope_demo ... print("Local: {}".format(y)) ... print("Global: {}".format(x)) ... print("Built-in: {}".format(dir)) >>> scope_demo() Local: 4 Global: 2 Built-in: 17.2. Область видимости 157 После вызова scope_demo локальная переменная y уничтожается в ходе уборки мусора и становится недоступной в глобальной области види- мости: >>> y Traceback (most recent call last): NameError: name 'y' is not defined Переменные, определяемые внутри функции или метода, являются локальными. В общем случае стоит избегать глобальных переменных, потому что они усложняют понимание кода. Глобальные переменные часто встречаются в учебниках, блогах и документации, потому что их использование сокращает объем кода и помогает сосредоточиться на кон- цепциях, не отвлекаясь на упаковку переменных в функциях. Функции и классы помогают избавиться от глобальных переменных, улучшают модульность кода и упрощают его понимание. ПРИМЕЧАНИЕ Python позволяет замещать (переопределять) переменные в глобальной и встроенной области видимости. На глобальном уровне вы можете опре- делить собственную переменную с именем dir . В этот момент встроенная функция dir замещается глобальной переменной. То же самое можно сде- лать внутри функции и создать локальную переменную, которая замещает глобальную или встроенную переменную: >>> def dir(x): ... print("Dir called") >>> dir('') Dir called Команда del может использоваться для удаления переменных в локальной или глобальной области видимости. Однако на практике лучше с самого на- чала избегать замещения встроенных имен: >>> del dir >>> dir('') ['__add__', '__class__', '__contains__', ... ] 158 Глава 17. Функции ПОДСКАЗКА Функции locals и globals используются для вывода содержимого этих областей видимости. Они возвращают словари с текущим содержимым об- ласти видимости: >>> def foo(): ... x = 1 ... print(locals()) >>> foo() {'x': 1} Переменная __builtins__ выводит имена из встроенной области види- мости. Ее атрибут __dict__ выдает такой же словарь, как для глобальных и локальных имен. 17.3. Множественные параметры Функции могут получать несколько параметров. Следующая функция получает два параметра и возвращает их сумму: >>> def add_two_nums(a, b): ... return a + b Так как Python является динамическим языком, указывать типы пара- метров не нужно. Эта функция может суммировать два целых числа: >>> add_two_nums(4, 6) 10 А может суммировать числа с плавающей точкой: >>> add_two_nums(4.0, 6.0) 10.0 И строки тоже: >>> add_two_nums('4', '6') '46' Обратите внимание: для строк используется операция + для выполнения конкатенации (сцепления двух строк). 17.4. Параметры по умолчанию 159 Но если вы попробуете сложить строку с числом, Python сообщит об ошибке: >>> add_two_nums('4', 6) Traceback (most recent call last): TypeError: Can't convert 'int' object to str implicitly Это одна из тех ситуаций, когда Python требует более точно описать нужную операцию и не пытается угадывать за вас. Если вы хотите сло- жить строковый тип с числом, возможно, сначала нужно преобразовать их к числовому формату (при помощи float или int ). С другой стороны, если нужно выполнить операцию конкатенации, следует преобразовать числа в строки. Python не выбирает выполняемую операцию автомати- чески. Вместо этого выдается ошибка, которая заставляет программиста разрешить неоднозначность. 17.4. Параметры по умолчанию Одна из удобных особенностей функций Python — параметры по умолчанию. Как следует из названия, они позволяют задать значения по умолчанию для параметров функций. Параметры по умолчанию не являются обязательными, хотя при необходимости их можно пере- определить. Следующая функция похожа на add_two_nums , но если при вызове второе число не указано, по умолчанию прибавляется 3: >>> def add_n(num, n=3): ... """default to ... adding 3""" ... return num + n >>> add_n(2) 5 >>> add_n(15, -5) 10 Чтобы создать для параметра значение по умолчанию, поставьте после параметра знак равенства ( = ) и нужное значение. 160 Глава 17. Функции ПРИМЕЧАНИЕ Параметры по умолчанию должны объявляться после обычных параметров, в противном случае Python выдаст ошибку SyntaxError : >>> def add_n(num=3, n): ... return num + n Traceback (most recent call last): SyntaxError: non-default argument follows default argument Python требует, чтобы обязательные параметры были объявлены ранее не- обязательных. Приведенный выше код не будет работать для вызова вида add_n(4) , потому что отсутствует обязательный параметр. СОВЕТ Не используйте изменяемые типы (списки, словари) в качестве параметров по умолчанию — разве что вы очень хорошо понимаете, что делаете. Из-за особенностей работы Python параметры по умолчанию создаются только один раз — во время определения функции, а не во время ее выполнения. Если вы используете изменяемое значение по умолчанию, то при каждом вызове функ- ции будет заново использован тот же экземпляр параметра по умолчанию: >>> def to_list(value, default=[]): ... default.append(value) ... return default >>> to_list(4) [4] >>> to_list('hello') [4, 'hello'] Тот факт, что параметры по умолчанию создаются в момент генерирования функции, многие программисты считают дефектом. Это связано с тем, что такое поведение чревато разными неожиданностями. Обходное решение заключается в том, чтобы вынести создание значений по умолчанию из фазы определения функции (которая выполняется всего один раз) в фазу выполнения функции (чтобы новое значение создавалось при каждом выполнении функции). Модифицируйте изменяемые параметры по умолчанию, чтобы им присваи- валось значение None . Затем создайте экземпляр нужного изменяемого типа в теле функции, если значение по умолчанию равно None : 17.5. Правила выбора имен для функций 161 >>> def to_list2(value, default=None): ... if default is None: ... default = [] ... default.append(value) ... return default >>> to_list2(4) [4] >>> to_list2('hello') ['hello'] Следующий код: ... if default is None: ... default = [] можно записать в одну строку с использованием условного выражения: ... default = default if default is not None else [] 17.5. Правила выбора имен для функций Правила выбора имен функций имеют много общего с правилами выбора имен переменных (и они также находятся в документе PEP 8). В именах используется так называемый змеиный регистр, который проще читается. Имена функций: должны записываться в нижнем регистре; слова_должны_разделяться_подчеркиваниями; не должны начинаться с цифр; не должны переопределять встроенные имена; не должны совпадать с ключевыми словами. В таких языках, как Java, используется так называемый «верблюжий регистр». По этой схеме создаются имена переменных вида sectionList или hasTimeOverlap . В Python переменным были бы присвоены имена section_list и has_time_overlap соответственно. Хотя код Python должен следовать соглашениям PEP 8, в PEP 8 также принимается в расчет един- ство стиля. Если в коде, над котором вы работаете, используются разные 162 Глава 17. Функции схемы назначения имен, следуйте примеру и используйте схему, применя- емую в существующем коде. Собственно, в модуле unittest из стандартной библиотеки до сих пор применяется схема в стиле Java (потому что изна- чально этот модуль был импортирован из библиотеки Java junit ). 17.6. Итоги Функции позволяют инкапсулировать изменения и побочные эффекты в своем теле. В этой главе вы узнали, что функции могут получать ввод и возвращать результат. Входных параметров может быть несколько, и им можно назначать значения по умолчанию. Вспомните, что в Python нет ничего, кроме объектов, а при создании функции вы также создаете переменную с именем функции, которая указывает на эту функцию. Функции также могут включать строку документации, которая записы- вается непосредственно после объявления. Эти строки образуют доку- ментацию, которая выводится при вызове help для функции. 17.7. Упражнения 1. Напишите функцию is_odd , которая получает целое число и воз- вращает True для нечетных чисел или False для четных. 2. Напишите функцию is_prime , которая получает целое число и воз- вращает True для простых чисел или False для чисел, не являющих- ся простыми. 3. Напишите функцию бинарного поиска. Функция должна получать отсортированную последовательность и искомый элемент и воз- вращать индекс найденного элемента. Если элемент не найден, функция должна возвращать –1. 4. Напишите функцию, которая получает строки в «верблюжьем ре- гистре» ( ThisIsCamelCased ) и преобразует их в «змеиный регистр» ( this_is_camel_cased ). Измените функцию, добавив в нее аргумент separator , чтобы функция также могла выполнять преобразование к «кебаб-регистру» ( this-is-camel-case ). 18 Индексирование и срезы Python предоставляет две конструкции для извлечения данных из после- довательностей (списки, кортежи и даже строки). Речь идет о конструк- циях индексирования и срезах. Индексирование позволяет извлекать отдельные элементы из последовательности, а срезы предназначены для извлечения подпоследовательностей. 18.1. Индексирование Индексирование уже было продемонстрировано ранее для списков. На- пример, если у вас имеется список с названиями животных, вы сможете выбирать элементы по индексу: >>> my_pets = ["dog", "cat", "bird"] >>> my_pets[0] 'dog' СОВЕТ Напомним, что в Python индексирование начинается с 0. Чтобы извлечь первый элемент, используйте индекс 0, а не 1. В Python предусмотрена удобная возможность обращения к элементам по отрицательным индексам. Индекс –1 обозначает последний элемент, –2 — предпоследний и т. д. Эта запись чаще всего используется для полу- чения последнего элемента списка: >>> my_pets[-1] 'bird' 164 Глава 18. Индексирование и срезы Гвидо ван Россум, создатель Python, в своем твите объяснил, как следует понимать отрицательные значения индексов: «…Правильный подход [к отрицательному индексированию] — ин- терпретировать a[-X] как a[len(a)-X] » @gvanrossum Операции индексирования также можно выполнять с кортежами и стро- ками: >>> ('Fred', 23, 'Senior')[1] 23 >>> 'Fred'[0] 'F' Некоторые типы, например множества, не поддерживают операции индексирования. Если вы хотите определить собственный класс, под- держивающий операции индексирования, реализуйте метод .__getitem__ -3 Примеры индексов Последовательность Индекс 0 1 2 3 4 5 6 7 d a t a . c s v " " -8-7-6-5-4 -2 -3 -1 Отрицательный индекс data.csv[0] data.csv[-3] Рис. 18.1. Положительные и отрицательные значения индексов 18.2. Срезы Кроме извлечения одного элемента по целочисленному индексу вы можете воспользоваться срезом (slice) для извлечения подпоследова- тельности. Срез может содержать начальный индекс, необязательный 18.2. Срезы 165 конечный индекс и необязательное приращение (все значения разделя- ются двоеточиями). Срез для извлечения первых двух элементов списка: >>> my_pets = ["dog", "cat", "bird"] # список >>> print(my_pets[0:2]) ['dog', 'cat'] Напомним, что в Python используются полуоткрытые интервалы. Спи- сок доходит до конечного индекса, но не включает его. Как упоминалось ранее, функция range также аналогично ведет себя со вторым параметром. Примеры срезов Последовательность Индекс 0 1 2 3 4 5 6 7 d a t a . c s v " " -8-7-6-5-4-3-2-1 Отрицательный индекс "data.csv"[0:4] "data.csv"[5:8] "data.csv"[:4] "data.csv"[-8:-4] "data" "data.csv"[5:] "data.csv"[-3:] "csv" Рис. 18.2. Выделение первых четырех символов строки. Показаны три варианта; предпочтительным считается последний. Нулевой индекс не указан, так как он используется по умолчанию. Решение с отрицательными индексами выглядит просто глупо. Также продемонстрирован срез трех последних символов. И снова последний вариант считается идиоматическим решением. Первые два варианта предполагают, что длина строки равна 8 символам, а последний будет работать с любой строкой, содержащей не менее 3 символов При определении среза с двоеточием ( : ) первый индекс не является обязательным. Если первый индекс не указан, то срез по умолчанию на- чинается с первого элемента списка (нулевой элемент): 166 Глава 18. Индексирование и срезы >>> print(my_pets[:2]) ['dog', 'cat'] В срезах также могут использоваться отрицательные индексы. Отри- цательной может быть как начальная, так и конечная позиция. Индекс –1 представляет последний элемент. Если срез распространяется до по- следнего элемента, вы получите все, кроме этого элемента: >>> my_pets[0:-1] ['dog', 'cat'] >>> my_pets[:-1] # defaults to 0 ['dog', 'cat'] >>> my_pets[0:-2] ['dog'] Последний индекс также не является обязательным. Если последний ин- декс отсутствует, срез по умолчанию распространяется до конца списка: >>> my_pets[1:] ['cat', 'bird'] >>> my_pets[-2:] ['cat', 'bird'] Наконец, для начального и конечного индекса могут использоваться значения по умолчанию. Если оба индекса отсутствуют, то возвращае- мый срез проходит от начала до конца (и содержит копию списка). Эта конструкция может использоваться для быстрого копирования списков в Python: >>> print(my_pets[:]) ['dog', 'cat', 'bird'] 18.3. Приращения в срезах После начального и конечного индекса срез также может получать при- ращение. Если приращение не задано, по умолчанию используется зна- чение 1. Приращение 1 означает, что из последовательности извлекается каждый элемент между индексами. С приращением 2 берется каждый второй элемент, с приращением 3 — каждый третий и т. д.: 18.3. Приращения в срезах 167 >>> my_pets = ["dog", "cat", "bird"] >>> dog_and_bird = my_pets[0:3:2] >>> print(dog_and_bird) ['dog', 'bird'] >>> zero_three_six = [0, 1, 2, 3, 4, 5, 6][::3] >>> print(zero_three_six) [0, 3, 6] ПРИМЕЧАНИЕ Функция range также поддерживает третий параметр, задающий прираще- ние: >>> list(range(0, 7, 3)) [0, 3, 6] Приращение может быть отрицательным. Приращение –1 означает, что вы двигаетесь в обратном направлении справа налево. Чтобы использо- вать отрицательное приращение, укажите значение начального индекса больше конечного. Исключением является ситуация, в которой опущен как начальный, так и конечный индекс: приращение –1 переставляет элементы последовательности в обратном порядке: >>> my_pets[0:2:-1] [] >>> my_pets[2:0:-1] ['bird', 'cat'] >>> print([1, 2, 3, 4][::-1]) [4, 3, 2, 1] Когда в следующий раз на собеседовании вам предложат переставить символы строки в обратном порядке, это можно сделать в одной строке: >>> 'emerih'[::-1] 'hireme' Конечно, от вас, скорее всего, потребуют сделать это на C. Просто ска- жите, что вы хотите программировать на Python! 168 Глава 18. Индексирование и срезы 18.4. Итоги Операции индексирования используются для извлечения отдельных значений из последовательностей. Например, они позволяют легко полу- чить символ из строки или элемент из списка либо кортежа. Если вам нужна подпоследовательность, используйте синтаксическую конструкцию среза. Срезы соблюдают принцип полуоткрытых интерва- лов и дают последовательность до конечного индекса (не включая его). Если вы передадите необязательное приращение, то сможете пропускать элементы при формировании среза. В Python предусмотрена удобная возможность использования отри- цательных значений для индексирования или создания среза относи- тельно конца последовательности. Это позволяет выполнять операции относительно длины последовательности, так что вам не приходится беспокоиться о вычислении длины и вычитании смещений из получен- ного результата. 18.5. Упражнения 1. Создайте переменную с вашим именем, хранящимся в формате стро- ки. Используйте операции индексирования для получения первого символа. Извлеките последний символ. Будет ли ваш код для извле- чения последнего символа работать с именем произвольной длины? 2. Создайте переменную filename . Предполагая, что за именем файла следует трехбуквенное расширение, найдите расширение с исполь- зованием операции среза. Так, для файла README.txt должно быть получено расширение txt . Будет ли ваш код работать с именами файлов произвольной длины? 3. Создайте функцию is_palindrome для проверки того, что передан- ное слово одинаково читается в обоих направлениях. |