Вопросы и ответы по программированию

Содержание

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

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

Да.

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

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

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

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, в код на языке C (инициализаторы массивов, которые могут быть превращены в объекты кода с помощью модуля marshal) и создает пользовательский файл конфигурации, содержащий только те встроенные модули, которые действительно используются в программе. Затем он компилирует сгенерированный C-код и связывает его с остальной частью интерпретатора 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, getopt, re

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

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

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

Циркулярный импорт нормально работает, когда оба модуля используют форму импорта «import <module>». Они терпят неудачу, когда 2-й модуль хочет получить имя из первого («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+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)

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

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

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

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

>>> 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 имеет значение true, а 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(). Для причудливого форматирования смотрите разделы Форматированные строковые литералы и Синтаксис строки форматирования, например, "{: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('u', s)
>>> print(a)
array('u', 'Hello, world')
>>> a[0] = 'y'
>>> print(a)
array('u', '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()
    

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

Вы можете использовать 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(), и лучше подходят для этой задачи.

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

См. Юникод HOWTO.

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

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

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

  • Характеристики производительности различаются в разных реализациях 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 с помощью конкатенации на месте (оператор +=):

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 Cookbook, где подробно рассматривается множество способов сделать это:

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

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 с помощью кортежей:

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 является наиболее известным.

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

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

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

Почему 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

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

>>> 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» в собственном дикте 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 работает с методами, имеющими хэшируемые аргументы. Он создает ссылку на экземпляр, если не предпринимать особых усилий для передачи слабых ссылок.

Преимущество алгоритма наименьшего последнего использования заключается в том, что кэш ограничен заданным 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 можно заставить работать, но класс должен определить методы __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?

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

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

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

Запуск Python на скрипте верхнего уровня не считается импортом, и никаких .pyc создано не будет. Например, если у вас есть модуль верхнего уровня foo.py, который импортирует другой модуль xyz.py, когда вы запустите foo (набрав python foo.py в качестве команды оболочки), для .pyc будет создан файл xyz, поскольку xyz импортируется, но для .pyc не будет создан файл foo, поскольку 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. Вы можете сделать это из интерпретатора shell, выполнив команду 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'
Вернуться на верх