Часто задаваемые вопросы по программированию

Содержание

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

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

Да.

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

Модуль pdb является простым, но адекватным отладчиком консольного режима для Python. Является частью стандартной библиотеки Python и задокументирован в Справочном руководстве библиотеки. Можно также написать собственный отладчик, используя в качестве примера код для pdb.

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

PythonWin - это Python IDE, включающая GUI-отладчик на основе pdb. Отладчик Pythonwin окрашивает точки останова и имеет довольно много классных функций, таких как отладка программ, не относящихся к Pythonwin. Pythonwin доступен как часть проекта Python для расширений Windows и как часть дистрибутива ActivePython (см. https://www.activestate.com/activepython).

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

Pydb - версия стандартного Python дебаггера pdb, модифицированного для использования с DDD (Отладчик отображаемых данных, Data Display Debugger), популярным графическим отладочным интерфейсом. Pydb можно найти в http://bashdb.sourceforge.net/pydb/, DDD - в https://www.gnu.org/software/ddd.

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

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

Да.

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

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

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

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

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

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

Очевидно, что для замораживания требуется компилятор C. Есть несколько других утилит, которые этого не делают. Один из них - py2exe Томаса Хеллера (только для Windows)

Другой инструмент - cx_Freeze Энтони Туининга.

Существуют ли стандарты кодирования или руководство по стилю для программ 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.py:

x = 0   # значение по умолчанию параметра конфигурации "x"

mod.py:

import config
config.x = 1

main.py:

import config
import mod
print(config.x)

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

Каковы «рекомендации» по использованию import в модуле?

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

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

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

  1. стандартные библиотечные модули - например, sys, os, getopt, re
  2. сторонние библиотечные модули (какие-либо установленные в каталоге site-packages Python’а) - например, mx.DateTime, ZODB, PIL.Image и т.д.
  3. локально разработанные модули

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

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

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

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

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

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

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

def foo(mydict={}):  # Опасность: общая ссылка на один словарь для всех вызовов
    ... 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 = {}  # создать новый словарь для локального пространства имен

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

# Вызывающие могут предоставить только два параметра и дополнительно передать _cache по ключю
def expensive(arg1, arg2, *, _cache={}):
    if (arg1, arg2) in _cache:
        return _cache[(arg1, arg2)]

    # Рассчитать значение
    result = ... expensive computation ...
    _cache[(arg1, arg2)] = result           # Сохранить результат в кэше
    return result

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

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

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

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

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

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

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. списки являются изменяемыми, что означает, что вы можете изменить их содержимое.

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

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

>>> x = 5  # ints являются неизменяемыми
>>> y = x
>>> x = x + 1  # 5 нельзя изменить, мы создаем здесь новый объект
>>> x
6
>>> y
5

мы видим, что в данном случае x и y уже не равны. Это потому, что целые числа неизменны, и когда мы выполняем x = x + 1 мы не изменяем int 5, увеличивая его значение; вместо этого мы создаем новый объект (6 int) и присваиваем его 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 и b - локальные имена
    ...     b = b + 1              # назначается новым объектам
    ...     return a, b            # возвращает новые значения
    ...
    >>> x, y = 'old-value', 99
    >>> func1(x, y)
    ('new-value', 100)
    

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

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

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

    >>> def func2(a):
    ...     a[0] = 'new-value'     # 'a' ссылается на изменяемый список
    ...     a[1] = a[1] + 1        # изменяет общий объект
    ...
    >>> args = ['old-value', 99]
    >>> func2(args)
    >>> args
    ['new-value', 100]
    
  4. передача в словаре, который изменяется:

    >>> def func3(args):
    ...     args['a'] = 'new-value'     # args - изменяемый словарь
    ...     args['b'] = args['b'] + 1   # изменение на месте
    ...
    >>> 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 - изменяемое Namespace
    ...     args.b = args.b + 1         # изменить объект на месте
    ...
    >>> 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__ унаследован
    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

# Простые < 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)))))

# Первые 10 чисел Фибоначчи
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))))

# множество Мандельброта
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))
#    \___ ___/  \___ ___/  |   |   |__ строки на экране
#        V          V      |   |______ столбцы на экране
#        |          |      |__________ максимум "итераций"
#        |          |_________________ диапазон на оси Y
#        |____________________________ диапазон на оси X

Не пробуйте это дома, дети!

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

Косая черта в списке аргументов функции обозначает, что предшествующие ей параметры являются только позиционными. Только позиционные параметры - это параметры без внешнего имени. При вызове функции, которая принимает только позиционные параметры, аргументы сопоставляются с параметрами, основанными исключительно на их положении. Например, 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(), например 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(). Для необычного форматирования посмотрите разделы Форматированные строковые литералы и Синтаксис format строки, например, "{: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'

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

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

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

    def a():
        pass
    
    def b():
        pass
    
    dispatch = {'go': a, 'stop': b}  # Обратите внимание на отсутствие parens для funcs
    
    dispatch[get_input()]()  # Обратите внимание на завершающие parens для вызова функции
    
  • Используйте встроенную функцию 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() или eval() для разрешения имени функции:

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

    Примечание. Использовать eval() медленно и опасно. Если у вас нет абсолютного контроля над содержимым строки, кто-то может передать строку, что приведет к выполнению произвольной функции.

Есть ли эквивалент 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() C, и лучше подходят для задачи.

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

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

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

  • Характеристики производительности варьируются в зависимости от Python реализаций. Этот FAQ посвящен CPython.
  • Поведение может различаться в разных операционных системах, особенно когда речь идет о I/O или многопоточности.
  • Необходимо всегда находить горячие точки в программе перед попыткой оптимизировать любой код (см. модуль profile).
  • Написание сценариев эталонных тестов позволит быстро выполнять итерацию при поиске улучшений (см. модуль timeit).
  • Настоятельно рекомендуется иметь хороший охват кода (посредством модульного тестирования или любого другого метода), прежде чем потенциально вводить регрессии, скрытые в сложных оптимизациях.

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

  • Создание более быстрых алгоритмов (или переход на более быстрые) может yield гораздо больше приемлем, чем попытка разбросать микрооптимизационные трюки по всему коду.
  • Используйте правильные структуры данных. Документация по изучению Встроенные типы и модуля collections.
  • Когда стандартная библиотека предоставляет примитив для чего-то, скорее всего (хотя и не гарантировано) будет быстрее, чем любая альтернатива, которую вы можете придумать. Это дважды верно для примитивов, написанных на языке C, таких как builtins и некоторые типы расширений. Например, обязательно используйте встроенный метод list.sort() или связанную функцию sorted() для сортировки (примеры умеренно расширенного использования см. в HOWTO по сортировке).
  • Абстракции, как правило, создают косвенные указания и заставляют интерпретатора работать больше. Если уровни абстракции перевешивают количество полезной работы, ваша программа будет работать медленнее. Следует избегать излишней абстракции, особенно в виде крошечных функций или методов (которые также часто ухудшают читаемость).

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

См.также

Вики-страница, посвященная советам по производительности.

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

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):
    ...  # сделать что-нибудь с 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]

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

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 также предоставляет методы создания массивов фиксированных типов с компактными представлениями, но они медленнее индексируются, чем списки. Также следует отметить, что расширения Numeric и другие определяют структуры, похожие на массивы, с различными характеристиками.

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

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

Если желательна изменчивость, вы можете использовать списки вместо кортежей. Здесь аналог lisp машины lisp_list[0] и lisp_list[1] аналог cdr. Делайте это только в том случае, если уверены, что вам это действительно нужно, потому что это обычно намного медленнее, чем при использовании списков 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 содержащий различные списки второй длины. Также можно использовать list comprehension:

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

Можно также использовать расширение, предоставляющее тип данных матрицы. NumPy - самый известный.

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

Используйте list comprehension:

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

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

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

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

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

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

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

Объект, на который указывает a_list, был мутирован, а указатель на мутировавший объект назначен обратно a_list. Конечным результатом назначения является no-op, так как это указатель на тот же объект, на который 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']

Альтернативой последнему шагу является:

>>> result = []
>>> for p in pairs: result.append(p[1])

Если вы находите это более понятным, вы можете использовать это вместо окончательного list comprehension. Однако она почти вдвое медленнее для длинных списков. Почему? Во-первых, операция append() должна перераспределить память, и хотя она использует некоторые трюки, чтобы избежать этого каждый раз, она все равно должна делать это время от времени, и это стоит совсем немного. Во-вторых, выражение «result.append» требует дополнительного поиска атрибута, в-третьих, снижение скорости от выполнения всех этих вызовов функции.

Объекты

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

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

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

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

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

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

Что такое self?

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() в определяемых пользователем классах. Если вы разрабатываете классы самостоятельно, более правильным объектно-ориентированным стилем является определение методов в классах, которые инкапсулируют определенное поведение, вместо проверки класса объекта и выполнения другой операции на основе того, какой это класс. Например, если у вас есть функция, которая что-то делает:

def search(obj):
    if isinstance(obj, Mailbox):
        ...  # код для поиска в почтовом ящике
    elif isinstance(obj, Document):
        ...  # код для поиска документа
    elif ...

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

class Mailbox:
    def search(self):
        ...  # код для поиска в почтовом ящике

class Document:
    def search(self):
        ...  # код для поиска документа

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__; дополнительные сведения об управлении доступом к атрибуту см. Справочник по языку.

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

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

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

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

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

class Derived(Base):
    def meth(self):
        super(Derived, self).meth()

Для версии до 3.0 можно использовать классические классы: для определения класса, такого как class Derived(Base): ..., можно вызвать метод meth(), определенный в Base (или один из базовых классов Base), как Base.meth(self, arguments...). Здесь Base.meth является несвязанным методом, поэтому необходимо указать аргумент self.

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

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

BaseAlias = <real base class>

class Derived(BaseAlias):
    def meth(self):
        BaseAlias.meth(self)
        ...

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

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

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

class C:
    count = 0   # количество вызовов C .__init__

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

    def getcount(self):
        return C.count  # или возвращает 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):
        # НЕТ 'self' параметра!
        ...

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

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) # doctest: +SKIP
13901272
>>> id(2000) # doctest: +SKIP
13901272

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

>>> a = 1000; b = 2000
>>> id(a) # doctest: +SKIP
13901272
>>> id(b) # doctest: +SKIP
13891296

Модули

Как создать файл .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. Это можно сделать из командной строки, запустив 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

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

  • main импортирует foo
  • создаются пустые глобалы для foo
  • foo компилируется и начинается выполняться
  • foo импортирует bar
  • создаются пустые глобалы для bar
  • bar компилируется и начинает выполняться
  • bar импортирует foo (что является no-op, так как уже существует модуль с именем foo)
  • bar.foo_var = foo.foo_var

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

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

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

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

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

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

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

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

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

__import__(„x.y.z“) возвращает <module „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()                # Создать экземпляр из C
>>> importlib.reload(cls)
<module 'cls' from 'cls.py'>
>>> isinstance(c, cls.C)       # сущность false?!?
False

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

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