Дополнительно

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

Передача кортежей

Хотелось ли вам когда-нибудь, чтобы функция возвращала не один результат, а два? Это возможно. Всё, что для этого нужно, – использовать кортеж.

>>> def get_error_details():
...     return (2, 'детали')
...
>>> errnum, errstr = get_error_details()
>>> errnum
2
>>> errstr
'детали'

Обратите внимание, что использование выражения a, b = <некоторое выражение> интерпретирует результат как кортеж из двух значений.

Это также подразумевает, что поменять местами два значения в Python быстрее всего можно так:

>>> a = 5; b = 8
>>> a, b
(5, 8)
>>> a, b = b, a
>>> a, b
(8, 5)

Специальные методы

Есть ряд методов, играющих особую роль для классов. Например, __init__ и __del__.

Специальные методы служат для того, чтобы имитировать поведение встроенных типов данных. Например, всё, что потребуется для использования операции индексирования x[ключ] применительно к своему классу (в таком виде, как это делалось для списков и кортежей), это реализовать метод __getitem__(). Кстати, именно этот метод Python использует для самого класса list!

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

  • __init__(self, ...)

    • Этот метод вызывается прямо перед тем, как вновь созданный объект возвращается для использования.
  • __del__(self)

    • Вызывается перед уничтожением объекта (который имеет непредсказуемое время, поэтому избегайте его использования)
  • __str__(self)

    • Вызывается при использовании функции print или str().
  • __lt__(self, other)

    • Вызывается, когда используется оператор меньше (<). Существуют и аналогичные методы для всех операторов (+, >, и т.д.)
  • __getitem__(self, key)

    • Вызывается при использовании оператора индексирования x[ключ]
  • __len__(self)

    • Вызывается при обращении к встроенной функции len() для объекта-последовательности.

Блоки в одно выражение

Мы неоднократно говорили, что каждый блок команд отделяется от других своим собственным уровнем отступа. Однако, существует и исключение. Если блок команд содержит только одно выражение, его можно указывать в одной строке с условным оператором или, скажем, оператором цикла. Рассмотрим это на примере:

>>> flag = True
>>> if flag: print('Да')
...
Да

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

Lambda-формы

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

Пример (сохраните как more_lambda.py):

points = [{'x': 2, 'y': 3},
          {'x': 4, 'y': 1}]
points.sort(key=lambda i: i['y'])
print(points)

Вывод:

$ python more_lambda.py
[{'y': 1, 'x': 4}, {'y': 3, 'x': 2}]

Как это работает

Обратите внимание на то, что метод sort класса list может принимать параметр key, определяющий способ сортировки списка (обычно мы думаем только о сортировке по возрастанию или по убыванию). В данном случае мы хотим провести сортировку по собственному принципу, для чего нам необходимо написать соответствующую функцию. Но вместо того, чтобы создавать отдельный блок def для описания функции, которая будет использоваться только в этом месте, мы применяем лямбда-выражение.

Генераторы списков

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

Пример (сохраните как more_list_comprehension.py):

listone = [2, 3, 4]
listtwo = [2*i for i in listone if i > 2]
print(listtwo)

Вывод:

$ python more_list_comprehension.py
[6, 8]

Как это работает

В этом примере мы создаём новый список, указав операцию, которую необходимо произвести (2*i), когда выполняется некоторое условие (if i > 2). Обратите внимание, что исходный список при этом не изменяется.

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

Передача кортежей и словарей в функции

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

>>> def powersum(power, *args):
...     '''Возвращает сумму аргументов, возведённых в указанную степень.'''
...     total = 0
...     for i in args:
...         total += pow(i, power)
...     return total
...
>>> powersum(2, 3, 4)
25
>>> powersum(2, 10)
100

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

Оператор assert

Оператор assert существует для того, чтобы указать, что нечто является истиной. Например, если требуется гарантировать, что в списке будет хотя бы один элемент, и вызвать ошибку, если это не так, то оператор assert идеально подойдёт для такой задачи. Когда заявленное выражение ложно, вызывается ошибка AssertionError. Метод pop() возвращает последний элемент списка, одновременно удаляя его оттуда.

>>> mylist = ['item']
>>> assert len(mylist) >= 1
>>> mylist.pop()
'item'
>>> assert len(mylist) >= 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

Тем не менее, оператор assert следует использовать благоразумно. В большинстве случаев гораздо лучше "отлавливать" исключения и либо решать соответствующую проблему автоматически, либо выдавать пользователю сообщение об ошибке и завершать работу программы.

Декораторы

Декораторы - это короткий путь к применению функций-оберток. Это полезно для "обертывания" функциональности одним и тем же кодом снова и снова. Например, я создал для себя декоратор retry, который я могу просто применить к любой функции, и если во время выполнения возникнет исключение, она будет повторена, максимум 5 раз и с задержкой между каждым повторением. Это особенно полезно в ситуациях, когда вы пытаетесь выполнить сетевой вызов на удаленный компьютер:

from time import sleep
from functools import wraps
import logging
logging.basicConfig()
log = logging.getLogger("retry")


def retry(f):
    @wraps(f)
    def wrapper_function(*args, **kwargs):
        MAX_ATTEMPTS = 5
        for attempt in range(1, MAX_ATTEMPTS + 1):
            try:
                return f(*args, **kwargs)
            except Exception:
                log.exception("Попытка %s/%s не удалась : %s",
                              attempt,
                              MAX_ATTEMPTS,
                              (args, kwargs))
                sleep(10 * attempt)
        log.critical("Все %s попытки не удались : %s",
                     MAX_ATTEMPTS,
                     (args, kwargs))
    return wrapper_function


counter = 0


@retry
def save_to_database(arg):
    print("Запись в базу данных, сетевой вызов и т.д.")
    print("При возникновении исключения попытка будет автоматически повторена.")
    global counter
    counter += 1
    # Это приведет к исключению при первом вызове.
    # И будет работать нормально во втором вызове (т.е. при повторной попытке)
    if counter < 2:
        raise ValueError(arg)


if __name__ == '__main__':
    save_to_database("Некоторое плохое значение")

Вывод:

$ python more_decorator.py
Запись в базу данных, сетевой вызов и т.д.
При возникновении исключения попытка будет автоматически повторена.
ERROR:retry:Попытка 1/5 не удалась : (('Некоторое плохое значение',), {})
Traceback (most recent call last):
  File "more_decorator.py", line 14, in wrapper_function
    return f(*args, **kwargs)
  File "more_decorator.py", line 39, in save_to_database
    raise ValueError(arg)
ValueError: Некоторое плохое значение
Запись в базу данных, сетевой вызов и т.д.
При возникновении исключения попытка будет автоматически повторена.

Как это работает

Смотри:

Различия между Python 2 и Python 3

Смотри:

Резюме

Итак, в настоящей главе мы рассмотрели некоторые дополнительные возможности Python, хотя по-прежнему, не охватили всего. Тем не менее, к настоящему моменту мы уже прошли почти всё, что вам когда-либо понадобится использовать на практике. Этого вполне достаточно для начала работы над любыми программами.

Далее мы обсудим, как продолжать исследовать Python.

results matching ""

    No results matching ""