FAQ по программированию

Общие вопросы

Есть ли отладчик на уровне исходного кода с точками останова, одиночным шагом и т. д.?

Да.

Ниже описаны несколько отладчиков для Python, а встроенная функция breakpoint() позволяет перейти к любому из них.

Модуль pdb - это простой, но адекватный отладчик в консольном режиме для Python. Он входит в стандартную библиотеку Python и является documented in the Library Reference Manual. Вы также можете написать свой собственный отладчик, используя код для pdb в качестве примера.

Интерактивная среда разработки IDLE, входящая в стандартный дистрибутив Python (обычно доступна в виде Tools/scripts/idle3), включает в себя графический отладчик.

PythonWin - это Python IDE, которая включает в себя графический отладчик, основанный на pdb. Отладчик PythonWin отображает точки останова и имеет множество интересных возможностей, таких как отладка программ, не работающих на PythonWin. PythonWin доступен как часть проекта pywin32 и как часть дистрибутива ActivePython.

Eric - это IDE, построенная на PyQt и компоненте редактирования Scintilla.

trepan3k - это gdb-подобный отладчик.

Visual Studio Code - это IDE с инструментами отладки, которая интегрируется с программами контроля версий.

Существует ряд коммерческих IDE для Python, которые включают в себя графические отладчики. К ним относятся:

Существуют ли инструменты, помогающие найти ошибки или выполнить статический анализ?

Да.

Pylint и Pyflakes выполняют базовую проверку, которая поможет вам быстрее обнаружить ошибки.

Статические программы проверки типов, такие как Mypy, Pyre и Pytype, могут проверять подсказки типов в исходном коде Python.

Как создать автономный двоичный файл из сценария Python?

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

Один из вариантов - использовать инструмент freeze, который включен в дерево исходных текстов Python как Tools/freeze. Он преобразует байт-код Python в массивы C; с помощью компилятора C вы можете встроить все свои модули в новую программу, которая затем будет связана со стандартными модулями Python.

Он работает, рекурсивно сканируя ваш исходный текст на наличие операторов импорта (в обеих формах) и ища модули как в стандартном пути к Python, так и в каталоге с исходным кодом (для встроенных модулей). Затем он превращает байткод модулей, написанных на Python, в Си-код (инициализаторы массивов, которые можно превратить в объекты кода с помощью модуля marshal) и создает собственный файл конфигурации, содержащий только те встроенные модули, которые действительно используются в программе. Затем он компилирует сгенерированный Си-код и связывает его с остальной частью интерпретатора Python, формируя автономный двоичный файл, который действует точно так же, как ваш скрипт.

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

  • Nuitka (Кроссплатформенная)

  • PyInstaller (Кроссплатформенная)

  • PyOxidizer (Кроссплатформенная)

  • cx_Freeze (Кроссплатформенная)

  • py2app (только для macOS)

  • py2exe (только для Windows)

Существуют ли стандарты кодирования или руководство по стилю для программ на Python?

Да. Стиль кодирования, требуемый для стандартных библиотечных модулей, документирован как PEP 8.

Основной язык

Почему я получаю ошибку UnboundLocalError, когда переменная имеет значение?

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

Этот код:

>>> x = 10
>>> def bar():
...     print(x)
...
>>> bar()
10

работает, но этот код:

>>> x = 10
>>> def foo():
...     print(x)
...     x += 1

приводит к UnboundLocalError:

>>> foo()
Traceback (most recent call last):
  ...
UnboundLocalError: local variable 'x' referenced before assignment

Это происходит потому, что при присваивании переменной в области видимости эта переменная становится локальной для этой области и теневой для любой переменной с аналогичным именем во внешней области видимости. Поскольку последний оператор в foo присваивает новое значение x, компилятор распознает его как локальную переменную. Следовательно, когда в предыдущем выражении print(x) делается попытка вывести неинициализированную локальную переменную, возникает ошибка.

В приведенном выше примере вы можете получить доступ к переменной внешней области видимости, объявив ее глобальной:

>>> x = 10
>>> def foobar():
...     global x
...     print(x)
...     x += 1
...
>>> foobar()
10

Это явное объявление необходимо для того, чтобы напомнить вам, что (в отличие от поверхностно аналогичной ситуации с переменными класса и экземпляра) вы действительно изменяете значение переменной во внешней области видимости:

>>> print(x)
11

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

>>> def foo():
...    x = 10
...    def bar():
...        nonlocal x
...        print(x)
...        x += 1
...    bar()
...    print(x)
...
>>> foo()
10
11

Каковы правила для локальных и глобальных переменных в Python?

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

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

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

Предположим, что вы используете цикл for для определения нескольких различных лямбд (или даже простых функций), например:

>>> squares = []
>>> for x in range(5):
...     squares.append(lambda: x**2)

В результате вы получите список, содержащий 5 ламбд, которые вычисляют x**2. Вы можете ожидать, что при вызове они вернут, соответственно, 0, 1, 4, 9 и 16. Однако, попробовав, вы увидите, что все они возвращают 16:

>>> squares[2]()
16
>>> squares[4]()
16

Это происходит потому, что x не локальна для лямбды, а определена во внешней области видимости, и доступ к ней осуществляется при вызове лямбды — а не при ее определении. В конце цикла значение x равно 4, поэтому все функции теперь возвращают 4**2, то есть 16. Вы также можете проверить это, изменив значение x, и посмотреть, как меняются результаты работы лямбд:

>>> x = 8
>>> squares[2]()
64

Чтобы избежать этого, необходимо сохранять значения в переменных, локальных для лямбд, чтобы они не зависели от значения глобальной x:

>>> squares = []
>>> for x in range(5):
...     squares.append(lambda n=x: n**2)

Здесь n=x создает новую переменную n, локальную для лямбды и вычисляемую при ее определении, чтобы она имела то же значение, что и x в этот момент в цикле. Это означает, что значение n будет 0 в первой лямбде, 1 во второй, 2 в третьей и так далее. Поэтому теперь каждая лямбда будет возвращать правильный результат:

>>> squares[2]()
4
>>> squares[4]()
16

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

Как разделить глобальные переменные между модулями?

Канонический способ обмена информацией между модулями в рамках одной программы - это создание специального модуля (часто называемого config или cfg). Просто импортируйте модуль config во все модули вашего приложения; тогда модуль станет доступен как глобальное имя. Поскольку существует только один экземпляр каждого модуля, любые изменения, внесенные в объект модуля, отражаются везде. Например:

config.py:

x = 0   # Default value of the 'x' configuration setting

mod.py:

import config
config.x = 1

main.py:

import config
import mod
print(config.x)

Обратите внимание, что использование модуля также является основой для реализации паттерна проектирования singleton, по той же самой причине.

Каковы «лучшие практики» использования импорта в модуле?

В общем случае не используйте from modulename import *. Это загромождает пространство имен импортера и значительно затрудняет обнаружение неопределенных имен линерами.

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

Хорошей практикой будет, если вы будете импортировать модули в следующем порядке:

  1. модули стандартной библиотеки - например, sys, os, argparse, re

  2. модули сторонних библиотек (все, что установлено в каталоге site-packages Python) - например, dateutil, requests, PIL.Image.

  3. модули местной разработки

Иногда необходимо перенести импорт в функцию или класс, чтобы избежать проблем с циклическим импортом. Гордон Макмиллан говорит:

Циркулярный импорт работает нормально, когда оба модуля используют форму импорта «import <module>». Они терпят неудачу, когда второй модуль хочет взять имя из первого («from module import name»), а импорт находится на верхнем уровне. Это происходит потому, что имена в 1-м модуле еще недоступны, поскольку первый модуль занят импортом 2-го.

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

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

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

Почему значения по умолчанию разделяются между объектами?

Этот тип ошибок обычно поражает программистов-неофитов. Рассмотрим эту функцию:

def foo(mydict={}):  # Danger: shared reference to one dict for all calls
    ... compute something ...
    mydict[key] = value
    return mydict

При первом вызове этой функции mydict содержит один элемент. Во второй раз mydict содержит два элемента, потому что когда foo() начинает выполняться, mydict начинается с уже имеющимся в нем элементом.

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

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

Из-за этой особенности хорошей практикой программирования является неиспользование мутабельных объектов в качестве значений по умолчанию. Вместо этого используйте None в качестве значения по умолчанию, а внутри функции проверяйте, является ли параметр None, и создавайте новый список/словарь/что угодно, если это так. Например, не пишите:

def foo(mydict={}):
    ...

но:

def foo(mydict=None):
    if mydict is None:
        mydict = {}  # create a new dict for local namespace

Эта функция может быть полезной. Если у вас есть функция, вычисление которой занимает много времени, распространенным приемом является кэширование параметров и результирующего значения при каждом вызове функции и возвращение кэшированного значения при повторном запросе того же значения. Это называется «мемоизацией» и может быть реализовано следующим образом:

# Callers can only provide two parameters and optionally pass _cache by keyword
def expensive(arg1, arg2, *, _cache={}):
    if (arg1, arg2) in _cache:
        return _cache[(arg1, arg2)]

    # Calculate the value
    result = ... expensive computation ...
    _cache[(arg1, arg2)] = result           # Store result in the cache
    return result

Вместо значения по умолчанию можно использовать глобальную переменную, содержащую словарь; это дело вкуса.

Как передать необязательные или ключевые параметры из одной функции в другую?

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

def f(x, *args, **kwargs):
    ...
    kwargs['width'] = '14.3c'
    ...
    g(x, *args, **kwargs)

В чем разница между аргументами и параметрами?

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

def func(foo, bar=None, **kwargs):
    pass

foo, bar и kwargs являются параметрами func. Однако при вызове func, например:

func(42, bar=314, extra=somevar)

значения 42, 314 и somevar являются аргументами.

Почему при изменении списка „y“ изменился и список „x“?

Если бы вы написали код типа:

>>> x = []
>>> y = x
>>> y.append(10)
>>> y
[10]
>>> x
[10]

Вам может быть интересно, почему добавление элемента к y изменило и x.

Этот результат обусловлен двумя факторами:

  1. Переменные - это просто имена, которые ссылаются на объекты. Переменная y = x не создает копию списка - она создает новую переменную y, которая ссылается на тот же объект, на который ссылается x. Это означает, что существует только один объект (список), и оба x и y ссылаются на него.

  2. Списки являются mutable, что означает, что вы можете изменять их содержимое.

После обращения к append() содержимое мутабельного объекта изменилось с [] на [10]. Поскольку обе переменные ссылаются на один и тот же объект, использование любого из имен открывает доступ к измененному значению [10].

Если вместо этого мы присвоим неизменяемый объект x:

>>> x = 5  # ints are immutable
>>> y = x
>>> x = x + 1  # 5 can't be mutated, we are creating a new object here
>>> x
6
>>> y
5

мы видим, что в этом случае x и y уже не равны. Это потому, что целые числа - это immutable, и когда мы выполняем x = x + 1, мы не мутируем int 5, увеличивая его значение; вместо этого мы создаем новый объект (int 6) и присваиваем его x (то есть меняем, на какой объект ссылается x). После этого присвоения у нас есть два объекта (ints 6 и 5) и две переменные, которые ссылаются на них (x теперь ссылается на 6, но y по-прежнему ссылается на 5).

Некоторые операции (например, y.append(10) и y.sort()) мутируют объект, в то время как внешне похожие операции (например, y = y + [10] и sorted(y)) создают новый объект. Обычно в Python (и во всех случаях в стандартной библиотеке) метод, который мутирует объект, возвращает None, чтобы избежать путаницы между двумя типами операций. Поэтому если вы ошибочно напишете y.sort(), думая, что это даст вам отсортированную копию y, то вместо этого вы получите None, что, скорее всего, приведет к легко диагностируемой ошибке в вашей программе.

Однако есть один класс операций, где одна и та же операция иногда ведет себя по-разному с разными типами: это дополненные операторы присваивания. Например, += мутирует списки, но не кортежи или инты (a_list += [1, 2, 3] эквивалентен a_list.extend([1, 2, 3]) и мутирует a_list, тогда как some_tuple += (1, 2, 3) и some_int += 1 создают новые объекты).

Другими словами:

  • Если у нас есть мутируемый объект (list, dict, set и т. д.), мы можем использовать некоторые специфические операции для его мутации, и все переменные, ссылающиеся на него, увидят изменения.

  • Если у нас есть неизменяемый объект (str, int, tuple и т. д.), то все переменные, ссылающиеся на него, всегда будут иметь одно и то же значение, но операции, преобразующие это значение в новое, всегда возвращают новый объект.

Если вы хотите узнать, относятся ли две переменные к одному и тому же объекту или нет, вы можете использовать оператор is или встроенную функцию id().

Как написать функцию с выходными параметрами (вызов по ссылке)?

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

  1. Возвращая кортеж результатов:

    >>> def func1(a, b):
    ...     a = 'new-value'        # a and b are local names
    ...     b = b + 1              # assigned to new objects
    ...     return a, b            # return new values
    ...
    >>> x, y = 'old-value', 99
    >>> func1(x, y)
    ('new-value', 100)
    

    Это почти всегда самое правильное решение.

  2. С помощью глобальных переменных. Это небезопасно для потоков и не рекомендуется.

  3. Передавая мутабельный (изменяемый на месте) объект:

    >>> def func2(a):
    ...     a[0] = 'new-value'     # 'a' references a mutable list
    ...     a[1] = a[1] + 1        # changes a shared object
    ...
    >>> args = ['old-value', 99]
    >>> func2(args)
    >>> args
    ['new-value', 100]
    
  4. Передавая словарь, который будет изменяться:

    >>> def func3(args):
    ...     args['a'] = 'new-value'     # args is a mutable dictionary
    ...     args['b'] = args['b'] + 1   # change it in-place
    ...
    >>> args = {'a': 'old-value', 'b': 99}
    >>> func3(args)
    >>> args
    {'a': 'new-value', 'b': 100}
    
  5. Или объедините значения в экземпляре класса:

    >>> class Namespace:
    ...     def __init__(self, /, **args):
    ...         for key, value in args.items():
    ...             setattr(self, key, value)
    ...
    >>> def func4(args):
    ...     args.a = 'new-value'        # args is a mutable Namespace
    ...     args.b = args.b + 1         # change object in-place
    ...
    >>> args = Namespace(a='old-value', b=99)
    >>> func4(args)
    >>> vars(args)
    {'a': 'new-value', 'b': 100}
    

    Почти никогда нет веских причин для таких сложностей.

Лучше всего возвращать кортеж, содержащий несколько результатов.

Как сделать функцию высшего порядка в Python?

У вас есть два варианта: вы можете использовать вложенные диапазоны или использовать вызываемые объекты. Например, предположим, что вы хотите определить linear(a,b), которая возвращает функцию f(x), вычисляющую значение a*x+b. Использование вложенных диапазонов:

def linear(a, b):
    def result(x):
        return a * x + b
    return result

Или использовать вызываемый объект:

class linear:

    def __init__(self, a, b):
        self.a, self.b = a, b

    def __call__(self, x):
        return self.a * x + self.b

В обоих случаях

taxes = linear(0.3, 2)

дает вызываемый объект, где taxes(10e6) == 0.3 * 10e6 + 2.

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

class exponential(linear):
    # __init__ inherited
    def __call__(self, x):
        return self.a * (x ** self.b)

Объект может инкапсулировать состояние для нескольких методов:

class counter:

    value = 0

    def set(self, x):
        self.value = x

    def up(self):
        self.value = self.value + 1

    def down(self):
        self.value = self.value - 1

count = counter()
inc, dec, reset = count.up, count.down, count.set

Здесь inc(), dec() и reset() действуют как функции, имеющие одну и ту же счетную переменную.

Как скопировать объект в Python?

Для общего случая попробуйте использовать copy.copy() или copy.deepcopy(). Не все объекты можно скопировать, но большинство - можно.

Некоторые объекты могут быть скопированы более легко. Словари имеют метод copy():

newdict = olddict.copy()

Последовательности можно копировать с помощью нарезки:

new_l = l[:]

Как найти методы или атрибуты объекта?

Для экземпляра x пользовательского класса dir(x) возвращает алфавитный список имен, содержащих атрибуты экземпляра, методы и атрибуты, определенные его классом.

Как мой код может узнать имя объекта?

Вообще говоря, не может, потому что у объектов на самом деле нет имен. По сути, присваивание всегда связывает имя со значением; то же самое верно и для операторов def и class, но в этом случае значение является вызываемым. Рассмотрим следующий код:

>>> class A:
...     pass
...
>>> B = A
>>> a = B()
>>> b = a
>>> print(b)
<__main__.A object at 0x16D07CC>
>>> print(a)
<__main__.A object at 0x16D07CC>

Можно утверждать, что у класса есть имя: даже если он связан с двумя именами и вызывается через имя B, созданный экземпляр все равно сообщается как экземпляр класса A. Однако невозможно сказать, является ли имя экземпляра a или b, поскольку оба имени связаны с одним и тем же значением.

Вообще говоря, вашему коду не должно быть необходимо «знать имена» конкретных значений. Если только вы не пишете интроспективные программы, это обычно является признаком того, что изменение подхода может быть полезным.

В разделе comp.lang.python Фредрик Лундх однажды привел отличную аналогию в ответ на этот вопрос:

Точно так же, как вы узнаете имя той кошки, которую нашли на крыльце: сама кошка (объект) не может сказать вам свое имя, да ей это и не важно, поэтому единственный способ узнать, как ее зовут, - спросить у всех соседей (пространства имен), их ли это кошка (объект)…

…. и не удивляйтесь, если обнаружите, что она известна под разными именами или вообще без названия!

Как обстоят дела с приоритетом оператора запятой?

Запятая не является оператором в Python. Рассмотрим этот сеанс:

>>> "a" in "b", "a"
(False, 'a')

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

("a" in "b"), "a"

не:

"a" in ("b", "a")

То же самое можно сказать и о различных операторах присваивания (=, += и т. д.). На самом деле это не операторы, а синтаксические разделители в операторах присваивания.

Существует ли эквивалент троичного оператора «?:» в языке C?

Да, это так. Синтаксис следующий:

[on_true] if [expression] else [on_false]

x, y = 50, 25
small = x if x < y else y

До появления этого синтаксиса в Python 2.5 распространенной идиомой было использование логических операторов:

[expression] and [on_true] or [on_false]

Однако эта идиома небезопасна, так как может дать неверный результат, если on_true имеет ложное булево значение. Поэтому всегда лучше использовать форму ... if ... else ....

Можно ли писать обфусцированные однострочные сообщения на Python?

Да. Обычно это делается путем вложения lambda внутри lambda. См. следующие три примера, слегка адаптированные Ульфом Бартелтом:

from functools import reduce

# Primes < 1000
print(list(filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0,
map(lambda x,y=y:y%x,range(2,int(pow(y,0.5)+1))),1),range(2,1000)))))

# First 10 Fibonacci numbers
print(list(map(lambda x,f=lambda x,f:(f(x-1,f)+f(x-2,f)) if x>1 else 1:
f(x,f), range(10))))

# Mandelbrot set
print((lambda Ru,Ro,Iu,Io,IM,Sx,Sy:reduce(lambda x,y:x+'\n'+y,map(lambda y,
Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,Sy=Sy,L=lambda yc,Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,i=IM,
Sx=Sx,Sy=Sy:reduce(lambda x,y:x+y,map(lambda x,xc=Ru,yc=yc,Ru=Ru,Ro=Ro,
i=i,Sx=Sx,F=lambda xc,yc,x,y,k,f=lambda xc,yc,x,y,k,f:(k<=0)or (x*x+y*y
>=4.0) or 1+f(xc,yc,x*x-y*y+xc,2.0*x*y+yc,k-1,f):f(xc,yc,x,y,k,f):chr(
64+F(Ru+x*(Ro-Ru)/Sx,yc,0,0,i)),range(Sx))):L(Iu+y*(Io-Iu)/Sy),range(Sy
))))(-2.1, 0.7, -1.2, 1.2, 30, 80, 24))
#    \___ ___/  \___ ___/  |   |   |__ lines on screen
#        V          V      |   |______ columns on screen
#        |          |      |__________ maximum of "iterations"
#        |          |_________________ range on y axis
#        |____________________________ range on x axis

Не пытайтесь сделать это дома, дети!

Что означает косая черта (/) в списке параметров функции?

Косая черта в списке аргументов функции означает, что предшествующие ей параметры являются только позиционными. Только позиционные параметры - это параметры без внешнего имени. При вызове функции, принимающей только позиционные параметры, аргументы сопоставляются с параметрами исключительно на основе их позиции. Например, divmod() - это функция, принимающая только позиционные параметры. Ее документация выглядит следующим образом:

>>> help(divmod)
Help on built-in function divmod in module builtins:

divmod(x, y, /)
    Return the tuple (x//y, x%y).  Invariant: div*y + mod == x.

Косая черта в конце списка параметров означает, что оба параметра являются только позиционными. Таким образом, вызов divmod() с аргументами в виде ключевых слов приведет к ошибке:

>>> divmod(x=3, y=4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: divmod() takes no keyword arguments

Числа и строки

Как указать шестнадцатеричные и восьмеричные целые числа?

Чтобы указать восьмеричную цифру, перед восьмеричным значением поставьте ноль, а затем строчную или прописную букву «o». Например, чтобы установить для переменной «a» восьмеричное значение «10» (8 в десятичной системе), введите:

>>> a = 0o10
>>> a
8

С шестнадцатеричными числами все так же просто. Просто поставьте перед шестнадцатеричным числом ноль, а затем строчную или прописную букву «x». Шестнадцатеричные цифры могут быть указаны как в нижнем, так и в верхнем регистре. Например, в интерпретаторе Python:

>>> a = 0xa5
>>> a
165
>>> b = 0XB2
>>> b
178

Почему -22 // 10 возвращает -3?

В первую очередь это обусловлено желанием, чтобы i % j имел тот же знак, что и j. Если вы хотите этого, а также хотите:

i == (i // j) * j + (i % j)

то целочисленное деление должно возвращать пол числа. C также требует, чтобы это тождество выполнялось, и тогда компиляторы, которые усекают i // j, должны заставить i % j иметь тот же знак, что и i.

Реальных случаев использования i % j при отрицательном значении j немного. Когда j положителен, их много, и практически во всех из них полезнее, чтобы i % j был >= 0. Если часы показывают 10 сейчас, то что они говорили 200 часов назад? -190 % 12 == 2 полезен; -190 % 12 == -10 - это ошибка, которая только и ждет, чтобы укусить.

Как получить атрибут литерала int вместо ошибки SyntaxError?

Попытка найти литеральный атрибут int обычным способом дает результат SyntaxError, поскольку точка воспринимается как десятичная точка:

>>> 1.__class__
  File "<stdin>", line 1
  1.__class__
   ^
SyntaxError: invalid decimal literal

Решение - отделить литерал от точки либо пробелом, либо круглыми скобками.

>>> 1 .__class__
<class 'int'>
>>> (1).__class__
<class 'int'>

Как преобразовать строку в число?

Для целых чисел используйте встроенный конструктор типа int(), например int('144') == 144. Аналогично, float() преобразуется в тип с плавающей запятой, например float('144') == 144.0.

По умолчанию они интерпретируют число как десятичное, так что int('0144') == 144 будет истинным, а int('0x144') приведет к ValueError. int(string, base) принимает основание для преобразования в качестве второго необязательного аргумента, поэтому int( '0x144', 16) == 324. Если основание указано как 0, число интерпретируется по правилам Python: ведущее „0o“ указывает на восьмеричную систему счисления, а „0x“ - на шестнадцатеричную.

Не используйте встроенную функцию eval(), если все, что вам нужно, - это преобразование строк в числа. eval() будет работать значительно медленнее и представляет собой риск для безопасности: кто-то может передать вам выражение Python, которое может иметь нежелательные побочные эффекты. Например, кто-то может передать __import__('os').system("rm -rf $HOME"), которое сотрет ваш домашний каталог.

eval() также интерпретирует числа как выражения Python, так что, например, eval('09') дает синтаксическую ошибку, поскольку Python не допускает ведущего „0“ в десятичном числе (кроме „0“).

Как преобразовать число в строку?

Чтобы преобразовать, например, число 144 в строку '144', используйте встроенный конструктор типов str(). Если вам нужно шестнадцатеричное или восьмеричное представление, воспользуйтесь встроенными функциями hex() или oct(). Для причудливого форматирования смотрите разделы f-струны и Синтаксис строки форматирования, например, "{:04d}".format(144) дает '0144', а "{:.3f}".format(1.0/3.0) - '0.333'.

Как изменить строку на месте?

Нельзя, потому что строки неизменяемы. В большинстве ситуаций вам следует просто сконструировать новую строку из различных частей, из которых вы хотите ее собрать. Однако если вам нужен объект, способный изменять юникодные данные на месте, попробуйте использовать объект io.StringIO или модуль array:

>>> import io
>>> s = "Hello, world"
>>> sio = io.StringIO(s)
>>> sio.getvalue()
'Hello, world'
>>> sio.seek(7)
7
>>> sio.write("there!")
6
>>> sio.getvalue()
'Hello, there!'

>>> import array
>>> a = array.array('w', s)
>>> print(a)
array('w', 'Hello, world')
>>> a[0] = 'y'
>>> print(a)
array('w', 'yello, world')
>>> a.tounicode()
'yello, world'

Как использовать строки для вызова функций/методов?

Существуют различные техники.

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

    def a():
        pass
    
    def b():
        pass
    
    dispatch = {'go': a, 'stop': b}  # Note lack of parens for funcs
    
    dispatch[get_input()]()  # Note trailing parens to call function
    
  • Используйте встроенную функцию getattr():

    import foo
    getattr(foo, 'bar')()
    

    Обратите внимание, что getattr() работает с любым объектом, включая классы, экземпляры классов, модули и так далее.

    Это используется в нескольких местах стандартной библиотеки, например:

    class Foo:
        def do_foo(self):
            ...
    
        def do_bar(self):
            ...
    
    f = getattr(foo_instance, 'do_' + opname)
    f()
    
  • Используйте locals() для разрешения имени функции:

    def myFunc():
        print("hello")
    
    fname = "myFunc"
    
    f = locals()[fname]
    f()
    

Существует ли эквивалент chomp() в Perl для удаления новых строк из строк?

Вы можете использовать S.rstrip("\r\n"), чтобы удалить все вхождения любого терминатора строки из конца строки S без удаления других пробельных символов. Если строка S представляет собой более одной строки с несколькими пустыми строками в конце, то будут удалены терминаторы всех пустых строк:

>>> lines = ("line 1 \r\n"
...          "\r\n"
...          "\r\n")
>>> lines.rstrip("\n\r")
'line 1 '

Поскольку это обычно требуется только при чтении текста по одной строке за раз, использование S.rstrip() таким образом работает хорошо.

Существует ли эквивалент scanf() или sscanf()?

Не как таковой.

Для простого разбора ввода проще всего разбить строку на слова, разделенные пробелами, используя метод split() для строковых объектов, а затем преобразовать десятичные строки в числовые значения с помощью int() или float(). split() поддерживает необязательный параметр «sep», который полезен, если в качестве разделителя в строке используется что-то другое, кроме пробельных символов.

Для более сложного разбора ввода регулярные выражения являются более мощными, чем sscanf языка C, и лучше подходят для этой задачи.

Что означает ошибка „UnicodeDecodeError“ или „UnicodeEncodeError“?

Смотрите Unicode HOWTO.

Можно ли завершить необработанную строку нечетным количеством обратных косых символов?

Необработанная строка, заканчивающаяся нечетным количеством обратных косых символов, будет заключена в кавычки:

>>> r'C:\this\will\not\work\'
  File "<stdin>", line 1
    r'C:\this\will\not\work\'
         ^
SyntaxError: unterminated string literal (detected at line 1)

Для этого есть несколько обходных путей. Один из них - использовать обычные строки и удваивать обратные слеши:

>>> 'C:\\this\\will\\work\\'
'C:\\this\\will\\work\\'

Другой способ - конкатенировать обычную строку, содержащую экранированный обратный слеш, с исходной строкой:

>>> r'C:\this\will\work' '\\'
'C:\\this\\will\\work\\'

Также можно использовать os.path.join() для добавления обратного слеша в Windows:

>>> os.path.join(r'C:\this\will\work', '')
'C:\\this\\will\\work\\'

Обратите внимание, что хотя обратная косая черта «освобождает» кавычки для определения места окончания исходной строки, при интерпретации значения исходной строки экранирование не происходит. То есть обратная косая черта остается в значении исходной строки:

>>> r'backslash\'preserved'
"backslash\\'preserved"

Также смотрите спецификацию в language reference.

Производительность

Моя программа работает слишком медленно. Как мне ускорить ее?

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

  • Характеристики производительности в разных реализациях Python различны. Этот FAQ посвящен CPython.

  • В разных операционных системах поведение может быть разным, особенно если речь идет о вводе-выводе или многопоточности.

  • Вы всегда должны находить «горячие точки» в своей программе перед тем, как пытаться оптимизировать какой-либо код (см. модуль profile).

  • Написание скриптов бенчмарков позволит вам быстро выполнять итерации при поиске улучшений (см. модуль timeit).

  • Настоятельно рекомендуется обеспечить хорошее покрытие кода (с помощью модульного тестирования или любой другой техники), прежде чем потенциально внедрять регрессии, скрытые в сложных оптимизациях.

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

  • Ускорение работы алгоритмов (или переход на более быстрые алгоритмы) может принести гораздо больше пользы, чем попытка рассыпать по всему коду трюки микрооптимизации.

  • Используйте правильные структуры данных. Изучите документацию по модулю Встроенные типы и collections.

  • Когда стандартная библиотека предоставляет примитив для выполнения какого-либо действия, он, скорее всего (хотя и не гарантированно), будет быстрее, чем любая альтернатива, которую вы можете придумать. Это вдвойне верно для примитивов, написанных на языке C, таких как встроенные методы и некоторые типы расширения. Например, для сортировки обязательно используйте либо встроенный метод list.sort(), либо связанную с ним функцию sorted() (а примеры умеренно продвинутого использования смотрите в разделе Техника сортировки).

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

Если вы достигли предела возможностей чистого Python, есть инструменты, которые помогут вам продвинуться дальше. Например, Cython может компилировать слегка измененную версию кода Python в расширение C, и его можно использовать на разных платформах. Cython может воспользоваться преимуществами компиляции (и дополнительными аннотациями типов), чтобы сделать ваш код значительно быстрее, чем при интерпретации. Если вы уверены в своих навыках программирования на C, вы также можете write a C extension module самостоятельно.

См.также

Вики-страница, посвященная performance tips.

Каков наиболее эффективный способ конкатенации множества строк?

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

Чтобы накопить много объектов str, рекомендуется поместить их в список и вызывать str.join() в конце:

chunks = []
for s in my_strings:
    chunks.append(s)
result = ''.join(chunks)

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

Чтобы накопить много объектов bytes, рекомендуется расширять объект bytearray с помощью in-place конкатенации (оператор +=):

result = bytearray()
for b in my_bytes_objects:
    result += b

Последовательности (кортежи/списки)

Как преобразовать кортежи в списки?

Конструктор типов tuple(seq) преобразует любую последовательность (фактически, любую итерируемую) в кортеж с теми же элементами в том же порядке.

Например, tuple([1, 2, 3]) возвращает (1, 2, 3), а tuple('abc') - ('a', 'b', 'c'). Если аргумент является кортежем, то он не создает копию, а возвращает тот же объект, поэтому удобно вызывать tuple(), когда вы не уверены, что объект уже является кортежем.

Конструктор типа list(seq) преобразует любую последовательность или итерируемую переменную в список с теми же элементами в том же порядке. Например, list((1, 2, 3)) дает [1, 2, 3], а list('abc') - ['a', 'b', 'c']. Если аргумент является списком, он создает копию, как и seq[:].

Что такое отрицательный индекс?

Последовательности Python индексируются положительными и отрицательными числами. Для положительных чисел 0 - это первый индекс, 1 - второй индекс и так далее. Для отрицательных чисел -1 - последний индекс, -2 - предпоследний (следующий за последним) индекс и так далее. Считайте, что seq[-n] - это то же самое, что и seq[len(seq)-n].

Использование отрицательных индексов может быть очень удобным. Например, S[:-1] - это вся строка, кроме ее последнего символа, что удобно для удаления из строки концевой новой строки.

Как выполнить итерацию по последовательности в обратном порядке?

Используйте встроенную функцию reversed():

for x in reversed(sequence):
    ...  # do something with x ...

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

Как удалить дубликаты из списка?

В поваренной книге Python вы найдете подробное описание многих способов сделать это:

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

if mylist:
    mylist.sort()
    last = mylist[-1]
    for i in range(len(mylist)-2, -1, -1):
        if last == mylist[i]:
            del mylist[i]
        else:
            last = mylist[i]

Если все элементы списка можно использовать в качестве ключей набора (т. е. все они hashable), это часто быстрее

mylist = list(set(mylist))

Это преобразует список в набор, удаляя дубликаты, а затем снова в список.

Как удалить несколько элементов из списка

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

mylist[:] = filter(keep_function, mylist)
mylist[:] = (x for x in mylist if keep_condition)
mylist[:] = [x for x in mylist if keep_condition]

Понимание списка может быть самым быстрым.

Как создать массив в Python?

Используйте список:

["this", 1, "is", "an", "array"]

По своей временной сложности списки эквивалентны массивам в C или Pascal; основное отличие заключается в том, что список в Python может содержать объекты разных типов.

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

Чтобы получить связные списки в стиле Lisp, вы можете эмулировать cons cells с помощью tuples:

lisp_list = ("like",  ("this",  ("example", None) ) )

Если требуется мутабельность, можно использовать списки вместо кортежей. Здесь аналогом Lisp car является lisp_list[0], а аналогом cdr - lisp_list[1]. Делайте это только в том случае, если вы уверены, что вам это действительно нужно, потому что это обычно намного медленнее, чем использование списков Python.

Как создать многомерный список?

Вероятно, вы пытались создать многомерный массив следующим образом:

>>> A = [[None] * 2] * 3

При печати все выглядит правильно:

>>> A
[[None, None], [None, None], [None, None]]

Но когда вы присваиваете значение, оно отображается в нескольких местах:

>>> A[0][0] = 5
>>> A
[[5, None], [5, None], [5, None]]

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

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

A = [None] * 3
for i in range(3):
    A[i] = [None] * 2

Это создаст список, содержащий 3 различных списка длины два. Вы также можете использовать понимание списка:

w, h = 2, 3
A = [[None] * w for i in range(h)]

Или вы можете использовать расширение, предоставляющее матричный тип данных; наиболее известным является NumPy.

Как применить метод или функцию к последовательности объектов?

Чтобы вызвать метод или функцию и накапливать возвращаемые значения в виде списка, элегантным решением является list comprehension:

result = [obj.method() for obj in mylist]

result = [function(obj) for obj in mylist]

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

for obj in mylist:
    obj.method()

for obj in mylist:
    function(obj)

Почему при сложении a_tuple[i] += [„item“] возникает исключение?

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

Это обсуждение применимо в общем случае, когда дополненные операторы присваивания применяются к элементам кортежа, которые указывают на изменяемые объекты, но мы будем использовать list и += в качестве примера.

Если вы написали:

>>> a_tuple = (1, 2)
>>> a_tuple[0] += 1
Traceback (most recent call last):
   ...
TypeError: 'tuple' object does not support item assignment

Причина исключения должна быть понятна сразу: 1 добавляется к объекту, на который указывает a_tuple[0] (1), создавая объект-результат 2, но когда мы пытаемся присвоить результат вычислений, 2, элементу 0 кортежа, мы получаем ошибку, поскольку мы не можем изменить, на что указывает элемент кортежа.

Под обложкой это расширенное задание выглядит примерно так:

>>> result = a_tuple[0] + 1
>>> a_tuple[0] = result
Traceback (most recent call last):
  ...
TypeError: 'tuple' object does not support item assignment

Ошибка возникает именно при выполнении операции присваивания, поскольку кортеж неизменяем.

Когда вы пишете что-то вроде:

>>> a_tuple = (['foo'], 'bar')
>>> a_tuple[0] += ['item']
Traceback (most recent call last):
  ...
TypeError: 'tuple' object does not support item assignment

Исключение немного удивительнее, а еще удивительнее тот факт, что даже несмотря на ошибку, добавление сработало:

>>> a_tuple[0]
['foo', 'item']

Чтобы понять, почему так происходит, нужно знать, что (а) если объект реализует магический метод __iadd__(), он вызывается при выполнении дополненного присваивания +=, и его возвращаемое значение используется в операторе присваивания; и (б) для списков __iadd__() эквивалентно вызову extend() на списке и возвращению списка. Поэтому мы говорим, что для списков += - это «сокращение» для list.extend():

>>> a_list = []
>>> a_list += [1]
>>> a_list
[1]

Это эквивалентно:

>>> result = a_list.__iadd__([1])
>>> a_list = result

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

Таким образом, в нашем примере с кортежем происходящее эквивалентно:

>>> result = a_tuple[0].__iadd__(['item'])
>>> a_tuple[0] = result
Traceback (most recent call last):
  ...
TypeError: 'tuple' object does not support item assignment

Присвоение __iadd__() проходит успешно, и таким образом список расширяется, но даже если result указывает на тот же объект, на который уже указывает a_tuple[0], это окончательное присваивание все равно приводит к ошибке, поскольку кортежи неизменяемы.

Я хочу сделать сложную сортировку: можете ли вы сделать преобразование Шварца в Python?

Этот метод, приписываемый Рэндалу Шварцу из сообщества Perl, сортирует элементы списка по метрике, которая сопоставляет каждому элементу его «значение сортировки». В Python используйте аргумент key для метода list.sort():

Isorted = L[:]
Isorted.sort(key=lambda s: int(s[10:15]))

Как отсортировать один список по значениям из другого списка?

Объедините их в итератор кортежей, отсортируйте полученный список, а затем выберите нужный вам элемент.

>>> list1 = ["what", "I'm", "sorting", "by"]
>>> list2 = ["something", "else", "to", "sort"]
>>> pairs = zip(list1, list2)
>>> pairs = sorted(pairs)
>>> pairs
[("I'm", 'else'), ('by', 'sort'), ('sorting', 'to'), ('what', 'something')]
>>> result = [x[1] for x in pairs]
>>> result
['else', 'sort', 'to', 'something']

Объекты

Что такое класс?

Класс - это конкретный тип объекта, созданный при выполнении оператора class. Объекты класса используются в качестве шаблонов для создания объектов экземпляра, которые воплощают в себе как данные (атрибуты), так и код (методы), характерные для типа данных.

Класс может быть основан на одном или нескольких других классах, называемых его базовым классом (классами). При этом он наследует атрибуты и методы своих базовых классов. Это позволяет последовательно совершенствовать объектную модель с помощью наследования. У вас может быть общий класс Mailbox, который предоставляет основные методы доступа к почтовому ящику, и подклассы, такие как MboxMailbox, MaildirMailbox, OutlookMailbox, которые работают с различными специфическими форматами почтовых ящиков.

Что такое метод?

Метод - это функция на некотором объекте x, которую вы обычно вызываете как x.name(arguments...). Методы определяются как функции внутри определения класса:

class C:
    def meth(self, arg):
        return arg * 2 + self.attribute

Что такое самость?

Self - это просто условное имя для первого аргумента метода. Метод, определенный как meth(self, a, b, c), должен вызываться как x.meth(a, b, c) для некоторого экземпляра x класса, в котором происходит определение; вызываемый метод будет думать, что он вызывается как meth(x, a, b, c).

См. также Почему в определениях и вызовах методов необходимо явно использовать „self“?.

Как проверить, является ли объект экземпляром данного класса или его подклассом?

Используйте встроенную функцию isinstance(obj, cls). Вы можете проверить, является ли объект экземпляром любого из нескольких классов, указав кортеж вместо одного класса, например isinstance(obj, (class1, class2, ...)), а также проверить, является ли объект одним из встроенных типов Python, например isinstance(obj, str) или isinstance(obj, (int, float, complex)).

Обратите внимание, что isinstance() также проверяет виртуальное наследование от abstract base class. Таким образом, тест вернет True для зарегистрированного класса, даже если он не наследует от него прямо или косвенно. Чтобы проверить «истинное наследование», просканируйте MRO класса:

from collections.abc import Mapping

class P:
     pass

class C(P):
    pass

Mapping.register(P)
>>> c = C()
>>> isinstance(c, C)        # direct
True
>>> isinstance(c, P)        # indirect
True
>>> isinstance(c, Mapping)  # virtual
True

# Actual inheritance chain
>>> type(c).__mro__
(<class 'C'>, <class 'P'>, <class 'object'>)

# Test for "true inheritance"
>>> Mapping in type(c).__mro__
False

Обратите внимание, что в большинстве программ isinstance() для пользовательских классов используется нечасто. Если вы сами разрабатываете классы, то более правильным объектно-ориентированным стилем будет определение методов в классах, которые инкапсулируют определенное поведение, вместо того чтобы проверять класс объекта и делать разные вещи в зависимости от того, к какому классу он относится. Например, если у вас есть функция, которая делает что-то:

def search(obj):
    if isinstance(obj, Mailbox):
        ...  # code to search a mailbox
    elif isinstance(obj, Document):
        ...  # code to search a document
    elif ...

Лучший подход - определить метод search() для всех классов и просто вызывать его:

class Mailbox:
    def search(self):
        ...  # code to search a mailbox

class Document:
    def search(self):
        ...  # code to search a document

obj.search()

Что такое делегирование?

Делегирование - это объектно-ориентированная техника (также называемая паттерном проектирования). Допустим, у вас есть объект x и вы хотите изменить поведение только одного из его методов. Вы можете создать новый класс, который обеспечит новую реализацию метода, который вы хотите изменить, и делегирует все остальные методы соответствующему методу x.

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

class UpperOut:

    def __init__(self, outfile):
        self._outfile = outfile

    def write(self, s):
        self._outfile.write(s.upper())

    def __getattr__(self, name):
        return getattr(self._outfile, name)

Здесь класс UpperOut переопределяет метод write(), чтобы преобразовать строку аргумента в верхний регистр перед вызовом основного метода self._outfile.write(). Все остальные методы делегируются базовому объекту self._outfile. Делегирование осуществляется с помощью метода __getattr__(); дополнительную информацию о контроле доступа к атрибутам см. в разделе the language reference.

Обратите внимание, что в более общих случаях делегирование может оказаться сложнее. Когда атрибуты должны быть установлены, а также получены, класс должен определить метод __setattr__(), и сделать это нужно тщательно. Базовая реализация __setattr__() примерно эквивалентна следующему:

class X:
    ...
    def __setattr__(self, name, value):
        self.__dict__[name] = value
    ...

Большинство реализаций __setattr__() должны модифицировать self.__dict__, чтобы хранить локальное состояние для себя, не вызывая бесконечной рекурсии.

Как вызвать метод, определенный в базовом классе, из производного класса, который его расширяет?

Используйте встроенную функцию super():

class Derived(Base):
    def meth(self):
        super().meth()  # calls Base.meth

В примере super() автоматически определит экземпляр, из которого он был вызван (значение self), найдет method resolution order (MRO) с помощью type(self).__mro__ и вернет следующий по порядку после Derived в MRO: Base.

Как мне организовать свой код, чтобы было проще изменять базовый класс?

Вы можете присвоить базовому классу псевдоним и вывести его из псевдонима. Тогда все, что вам нужно будет изменить, - это значение, присвоенное псевдониму. Кстати, этот трюк также удобен, если вы хотите динамически (например, в зависимости от доступности ресурсов) решать, какой базовый класс использовать. Пример:

class Base:
    ...

BaseAlias = Base

class Derived(BaseAlias):
    ...

Как создать статические данные класса и статические методы класса?

В Python поддерживаются как статические данные, так и статические методы (в смысле C++ или Java).

Для статических данных достаточно определить атрибут класса. Чтобы присвоить атрибуту новое значение, необходимо явно использовать имя класса в назначении:

class C:
    count = 0   # number of times C.__init__ called

    def __init__(self):
        C.count = C.count + 1

    def getcount(self):
        return C.count  # or return self.count

c.count также ссылается на C.count для любого c, такого, что isinstance(c, C) выполняется, если это не отменено самим c или каким-либо классом на пути поиска базового класса от c.__class__ обратно к C.

Внимание: внутри метода C присваивание типа self.count = 42 создает новый, не связанный с ним экземпляр с именем «count» в собственном dict self. Перепривязка статического имени данных класса всегда должна указывать на класс, независимо от того, находится он внутри метода или нет:

C.count = 314

Возможны статические методы:

class C:
    @staticmethod
    def static(arg1, arg2, arg3):
        # No 'self' parameter!
        ...

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

def getcount():
    return C.count

Если ваш код структурирован таким образом, что в каждом модуле определяется один класс (или тесно связанная иерархия классов), это обеспечивает желаемую инкапсуляцию.

Как перегрузить конструкторы (или методы) в Python?

На самом деле этот ответ относится ко всем методам, но обычно этот вопрос возникает в первую очередь в контексте конструкторов.

На C++ вы бы написали

class C {
    C() { cout << "No arguments\n"; }
    C(int i) { cout << "Argument is " << i << "\n"; }
}

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

class C:
    def __init__(self, i=None):
        if i is None:
            print("No arguments")
        else:
            print("Argument is", i)

Это не совсем эквивалентно, но на практике достаточно близко.

Можно также попробовать использовать список аргументов переменной длины, например

def __init__(self, *args):
    ...

Этот же подход работает для всех определений метода.

Я пытаюсь использовать __spam и получаю ошибку о _SomeClassName__spam.

Имена переменных с двойным ведущим подчеркиванием «искажаются», чтобы обеспечить простой, но эффективный способ определения приватных переменных класса. Любой идентификатор вида __spam (не менее двух ведущих подчеркиваний, не более одного завершающего подчеркивания) текстуально заменяется на _classname__spam, где classname - текущее имя класса с удаленными ведущими подчеркиваниями.

Это не гарантирует конфиденциальности: посторонний пользователь все равно может намеренно получить доступ к атрибуту «_classname__spam», а приватные значения видны в __dict__ объекта. Многие программисты на Python вообще не утруждают себя использованием имен приватных переменных.

Мой класс определяет __del__, но он не вызывается, когда я удаляю объект.

Этому есть несколько возможных причин.

Оператор del не обязательно вызывает __del__() – он просто уменьшает количество ссылок на объект, и если оно достигает нуля, вызывается __del__().

Если ваши структуры данных содержат циклические связи (например, дерево, в котором каждый ребенок имеет ссылку на родителя, а каждый родитель - на список детей), количество ссылок никогда не вернется к нулю. Время от времени Python запускает алгоритм для обнаружения таких циклов, но сборщик мусора может запуститься через некоторое время после исчезновения последней ссылки на вашу структуру данных, поэтому ваш метод __del__() может быть вызван в неудобное и случайное время. Это неудобно, если вы пытаетесь воспроизвести проблему. Хуже того, порядок выполнения методов __del__() объекта произволен. Вы можете запустить gc.collect(), чтобы принудительно собрать объект, но существуют патологические случаи, когда объекты никогда не будут собраны.

Несмотря на коллектор циклов, все же полезно определить для объектов явный метод close(), который будет вызываться всякий раз, когда вы закончите с ними работать. Метод close() может удалять атрибуты, ссылающиеся на подобъекты. Не вызывайте __del__() напрямую - __del__() должен вызывать close(), а close() должен гарантировать, что он может быть вызван более одного раза для одного и того же объекта.

Еще один способ избежать циклических ссылок - использовать модуль weakref, который позволяет указывать на объекты, не увеличивая счетчик их ссылок. Например, древовидные структуры данных должны использовать слабые ссылки для своих родительских и сиблинговых ссылок (если они им нужны!).

Наконец, если ваш метод __del__() вызывает исключение, в sys.stderr выводится предупреждающее сообщение.

Как получить список всех экземпляров данного класса?

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

Почему результат id() не является уникальным?

Встроенный модуль id() возвращает целое число, которое гарантированно будет уникальным в течение всего времени существования объекта. Поскольку в CPython это адрес памяти объекта, часто случается, что после удаления объекта из памяти следующий свежесозданный объект выделяется в той же позиции в памяти. Это можно проиллюстрировать на данном примере:

>>> id(1000) 
13901272
>>> id(2000) 
13901272

Эти два идентификатора принадлежат разным целочисленным объектам, которые создаются до и удаляются сразу после выполнения вызова id(). Чтобы убедиться, что объекты, id которых вы хотите проверить, все еще живы, создайте еще одну ссылку на объект:

>>> a = 1000; b = 2000
>>> id(a) 
13901272
>>> id(b) 
13891296

Когда можно полагаться на тесты идентичности с оператором is?

Оператор is проверяет идентичность объекта. Тест a is b эквивалентен id(a) == id(b).

Самое важное свойство теста на идентичность заключается в том, что объект всегда идентичен самому себе, a is a всегда возвращает True. Тесты идентичности обычно быстрее, чем тесты равенства. И в отличие от тестов равенства, тесты идентичности гарантированно возвращают булеву True или False.

Однако тесты на идентичность можно только заменить тестами на равенство, когда идентичность объекта гарантирована. Как правило, есть три обстоятельства, при которых идентичность гарантирована:

1) Assignments create new names but do not change object identity. After the assignment new = old, it is guaranteed that new is old.

2) Putting an object in a container that stores object references does not change object identity. After the list assignment s[0] = x, it is guaranteed that s[0] is x.

3) If an object is a singleton, it means that only one instance of that object can exist. After the assignments a = None and b = None, it is guaranteed that a is b because None is a singleton.

В большинстве других обстоятельств тесты тождества нежелательны, и предпочтительнее использовать тесты равенства. В частности, тесты тождества не следует использовать для проверки таких констант, как int и str, которые не гарантированно являются синглтонами:

>>> a = 1000
>>> b = 500
>>> c = b + 500
>>> a is c
False

>>> a = 'Python'
>>> b = 'Py'
>>> c = b + 'thon'
>>> a is c
False

Аналогично, новые экземпляры изменяемых контейнеров никогда не бывают идентичными:

>>> a = []
>>> b = []
>>> a is b
False

В коде стандартной библиотеки вы увидите несколько общих шаблонов для правильного использования тестов идентичности:

1) As recommended by PEP 8, an identity test is the preferred way to check for None. This reads like plain English in code and avoids confusion with other objects that may have boolean values that evaluate to false.

2) Detecting optional arguments can be tricky when None is a valid input value. In those situations, you can create a singleton sentinel object guaranteed to be distinct from other objects. For example, here is how to implement a method that behaves like dict.pop():

_sentinel = object()

def pop(self, key, default=_sentinel):
    if key in self:
        value = self[key]
        del self[key]
        return value
    if default is _sentinel:
        raise KeyError(key)
    return default

3) Container implementations sometimes need to augment equality tests with identity tests. This prevents the code from being confused by objects such as float('NaN') that are not equal to themselves.

Например, вот реализация collections.abc.Sequence.__contains__():

def __contains__(self, value):
    for v in self:
        if v is value or v == value:
            return True
    return False

Как подкласс может контролировать, какие данные хранятся в неизменяемом экземпляре?

При создании подкласса неизменяемого типа переопределите метод __new__() вместо метода __init__(). Последний выполняется только после создания экземпляра, а это слишком поздно для изменения данных в неизменяемом экземпляре.

Все эти неизменяемые классы имеют сигнатуру, отличную от сигнатуры их родительского класса:

from datetime import date

class FirstOfMonthDate(date):
    "Always choose the first day of the month"
    def __new__(cls, year, month, day):
        return super().__new__(cls, year, month, 1)

class NamedInt(int):
    "Allow text names for some numbers"
    xlat = {'zero': 0, 'one': 1, 'ten': 10}
    def __new__(cls, value):
        value = cls.xlat.get(value, value)
        return super().__new__(cls, value)

class TitleStr(str):
    "Convert str to name suitable for a URL path"
    def __new__(cls, s):
        s = s.lower().replace(' ', '-')
        s = ''.join([c for c in s if c.isalnum() or c == '-'])
        return super().__new__(cls, s)

Классы можно использовать следующим образом:

>>> FirstOfMonthDate(2012, 2, 14)
FirstOfMonthDate(2012, 2, 1)
>>> NamedInt('ten')
10
>>> NamedInt(20)
20
>>> TitleStr('Blog: Why Python Rocks')
'blog-why-python-rocks'

Как кэшировать вызовы методов?

Два основных инструмента для кэширования методов - это functools.cached_property() и functools.lru_cache(). Первый хранит результаты на уровне экземпляра, а второй - на уровне класса.

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

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

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

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

В этом примере показаны различные техники:

class Weather:
    "Lookup weather information on a government website"

    def __init__(self, station_id):
        self._station_id = station_id
        # The _station_id is private and immutable

    def current_temperature(self):
        "Latest hourly observation"
        # Do not cache this because old results
        # can be out of date.

    @cached_property
    def location(self):
        "Return the longitude/latitude coordinates of the station"
        # Result only depends on the station_id

    @lru_cache(maxsize=20)
    def historic_rainfall(self, date, units='mm'):
        "Rainfall on a given date"
        # Depends on the station_id, date, and units.

В приведенном выше примере предполагается, что station_id никогда не меняется. Если соответствующие атрибуты экземпляра являются изменяемыми, подход cached_property не может работать, поскольку он не может обнаружить изменения атрибутов.

Чтобы подход lru_cache работал, когда station_id мутабелен, класс должен определить методы __eq__() и __hash__(), чтобы кэш мог обнаружить соответствующие обновления атрибутов:

class Weather:
    "Example with a mutable station identifier"

    def __init__(self, station_id):
        self.station_id = station_id

    def change_station(self, station_id):
        self.station_id = station_id

    def __eq__(self, other):
        return self.station_id == other.station_id

    def __hash__(self):
        return hash(self.station_id)

    @lru_cache(maxsize=20)
    def historic_rainfall(self, date, units='cm'):
        'Rainfall on a given date'
        # Depends on the station_id, date, and units.

Модули

Как создать файл .pyc?

При первом импорте модуля (или если исходный файл изменился с момента создания текущего скомпилированного файла) в подкаталоге __pycache__ каталога, содержащего файл .py, должен быть создан файл .pyc, содержащий скомпилированный код. Файл .pyc будет иметь имя, начинающееся с того же имени, что и файл .py, и заканчивающееся .pyc, со средним компонентом, зависящим от конкретного python бинарного файла, который его создал. (Подробности см. в разделе PEP 3147).

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

Если не установлена переменная окружения PYTHONDONTWRITEBYTECODE, создание файла .pyc происходит автоматически, если вы импортируете модуль и у Python есть возможность (права, свободное место и т. д.) создать подкаталог __pycache__ и записать скомпилированный модуль в этот подкаталог.

Запуск Python на скрипте верхнего уровня не считается импортом, и никаких .pyc создано не будет. Например, если у вас есть модуль верхнего уровня foo.py, который импортирует другой модуль xyz.py, при запуске foo (набрав python foo.py в качестве команды оболочки) для xyz будет создан файл .pyc, поскольку xyz импортируется, но для foo не будет создано файла .pyc, поскольку foo.py не импортируется.

Если вам нужно создать .pyc файл для foo – то есть создать файл .pyc для модуля, который не импортирован, можно с помощью модулей py_compile и compileall.

Модуль py_compile может вручную скомпилировать любой модуль. Один из способов - использовать функцию compile() в этом модуле в интерактивном режиме:

>>> import py_compile
>>> py_compile.compile('foo.py')                 

В результате .pyc будет записан в подкаталог __pycache__ в том же месте, что и foo.py (или вы можете отменить это с помощью необязательного параметра cfile).

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

python -m compileall .

Как найти имя текущего модуля?

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

def main():
    print('Running test...')
    ...

if __name__ == '__main__':
    main()

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

Предположим, у вас есть следующие модули:

foo.py:

from bar import bar_var
foo_var = 1

bar.py:

from foo import foo_var
bar_var = 2

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

  • Основной импорт foo

  • Создаются пустые глобалы для foo

  • foo компилируется и начинает выполняться

  • foo импортирует bar

  • Создаются пустые глобалы для bar

  • bar компилируется и начинает выполняться

  • bar импортирует foo (что не имеет смысла, поскольку уже существует модуль с именем foo)

  • Механизм импорта пытается прочитать foo_var из foo глобальных файлов, чтобы установить bar.foo_var = foo.foo_var

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

То же самое происходит, когда вы используете import foo, а затем пытаетесь получить доступ к foo.foo_var в глобальном коде.

Существует (по крайней мере) три возможных способа решения этой проблемы.

Гвидо ван Россум рекомендует избегать любого использования from <module> import ... и помещать весь код внутрь функций. При инициализации глобальных переменных и переменных класса следует использовать только константы или встроенные функции. Это означает, что на все из импортированного модуля можно ссылаться как на <module>.<name>.

Джим Роскинд предлагает выполнять шаги в следующем порядке в каждом модуле:

  • exports (глобальные классы, функции и классы, которые не нуждаются в импорте базовых классов)

  • Заявления import

  • активный код (включая глобалы, которые инициализируются из импортированных значений).

Ван Россуму не очень нравится такой подход, потому что импорт появляется в странном месте, но он работает.

Маттиас Урлихс рекомендует перестроить код таким образом, чтобы рекурсивный импорт не был необходим в первую очередь.

Эти решения не являются взаимоисключающими.

__import__(„x.y.z“) возвращает <модуль „x“>; как мне получить z?

Вместо этого используйте удобную функцию import_module() от importlib:

z = importlib.import_module('x.y.z')

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

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

import importlib
import modname
importlib.reload(modname)

Предупреждение: эта техника не является на 100% безопасной. В частности, модули, содержащие утверждения типа

from modname import some_objects

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

>>> import importlib
>>> import cls
>>> c = cls.C()                # Create an instance of C
>>> importlib.reload(cls)
<module 'cls' from 'cls.py'>
>>> isinstance(c, cls.C)       # isinstance is false?!?
False

Природа проблемы становится понятной, если вывести «идентичность» объектов класса:

>>> hex(id(c.__class__))
'0x7352a0'
>>> hex(id(cls.C))
'0x4198d0'