FAQ по программированию¶
Общие вопросы¶
Есть ли отладчик на уровне исходного кода с точками останова, одиночным шагом и т. д.?¶
Да.
Ниже описаны несколько отладчиков для Python, а встроенная функция breakpoint()
позволяет перейти к любому из них.
Модуль pdb - это простой, но адекватный отладчик в консольном режиме для Python. Он входит в стандартную библиотеку Python и является documented in the Library Reference Manual
. Вы также можете написать свой собственный отладчик, используя код для pdb в качестве примера.
Интерактивная среда разработки IDLE, входящая в стандартный дистрибутив Python (обычно доступна в виде Tools/scripts/idle3), включает в себя графический отладчик.
PythonWin - это Python IDE, которая включает в себя графический отладчик, основанный на pdb. Отладчик PythonWin отображает точки останова и имеет множество интересных возможностей, например, отладку программ, не работающих на PythonWin. PythonWin доступен как часть проекта pywin32 и как часть дистрибутива ActivePython.
Eric - это IDE, построенная на PyQt и компоненте редактирования Scintilla.
trepan3k - это gdb-подобный отладчик.
Visual Studio Code - это IDE с инструментами отладки, которая интегрируется с программами контроля версий.
Существует ряд коммерческих IDE для Python, которые включают в себя графические отладчики. К ним относятся:
Существуют ли инструменты, помогающие найти ошибки или выполнить статический анализ?¶
Да.
Pylint и Pyflakes выполняют базовую проверку, которая поможет вам быстрее обнаружить ошибки.
Статические программы проверки типов, такие как Mypy, Pyre и Pytype, могут проверять подсказки типов в исходном коде Python.
Как создать автономный двоичный файл из сценария Python?¶
Вам не нужна возможность компиляции Python в C-код, если все, что вам нужно, - это отдельная программа, которую пользователи могут скачать и запустить без необходимости устанавливать дистрибутив Python. Существует ряд инструментов, которые определяют набор модулей, необходимых программе, и связывают эти модули с двоичным файлом Python для создания единого исполняемого файла.
Один из вариантов - использовать инструмент freeze, который включен в дерево исходных текстов Python как Tools/freeze. Он преобразует байт-код Python в массивы C; с помощью компилятора C вы можете встроить все свои модули в новую программу, которая затем будет связана со стандартными модулями Python.
Он работает, рекурсивно сканируя ваш исходный текст на наличие операторов импорта (в обеих формах) и ища модули как в стандартном пути Python, так и в каталоге исходного кода (для встроенных модулей). Затем он превращает байткод модулей, написанных на Python, в Си-код (инициализаторы массивов, которые можно превратить в объекты кода с помощью модуля marshal) и создает собственный файл конфигурации, содержащий только те встроенные модули, которые действительно используются в программе. Затем он компилирует сгенерированный Си-код и связывает его с остальной частью интерпретатора Python, чтобы сформировать самодостаточный двоичный файл, который действует точно так же, как ваш скрипт.
Следующие пакеты могут помочь в создании консольных и графических исполняемых файлов:
Nuitka (Кроссплатформенная)
PyInstaller (Кроссплатформенная)
PyOxidizer (Кроссплатформенная)
cx_Freeze (Кроссплатформенная)
py2app (только для macOS)
py2exe (только для Windows)
Существуют ли стандарты кодирования или руководство по стилю для программ на Python?¶
Да. Стиль кодирования, требуемый для модулей стандартной библиотеки, документирован как PEP 8.
Основной язык¶
Почему я получаю ошибку UnboundLocalError, когда переменная имеет значение?¶
Бывает неожиданно получить UnboundLocalError
в ранее работающем коде, когда он изменяется путем добавления оператора присваивания в тело функции.
Этот код:
>>> x = 10
>>> def bar():
... print(x)
...
>>> bar()
10
работает, но этот код:
>>> x = 10
>>> def foo():
... print(x)
... x += 1
приводит к UnboundLocalError
:
>>> foo()
Traceback (most recent call last):
...
UnboundLocalError: local variable 'x' referenced before assignment
Это происходит потому, что когда вы делаете присваивание переменной в области видимости, эта переменная становится локальной для этой области видимости и затеняет любую переменную с аналогичным именем во внешней области видимости. Поскольку последний оператор в foo присваивает новое значение x
, компилятор распознает его как локальную переменную. Следовательно, когда в предыдущем выражении print(x)
делается попытка вывести неинициализированную локальную переменную, возникает ошибка.
В приведенном выше примере вы можете получить доступ к переменной внешней области видимости, объявив ее глобальной:
>>> x = 10
>>> def foobar():
... global x
... print(x)
... x += 1
...
>>> foobar()
10
Это явное объявление необходимо для того, чтобы напомнить вам, что (в отличие от поверхностно аналогичной ситуации с переменными класса и экземпляра) вы действительно изменяете значение переменной во внешней области видимости:
>>> print(x)
11
Вы можете сделать то же самое во вложенной области видимости, используя ключевое слово nonlocal
:
>>> def foo():
... x = 10
... def bar():
... nonlocal x
... print(x)
... x += 1
... bar()
... print(x)
...
>>> foo()
10
11
Каковы правила для локальных и глобальных переменных в Python?¶
В Python переменные, на которые ссылаются только внутри функции, являются неявными глобальными. Если переменной присваивается значение где-либо в теле функции, она считается локальной, если явно не объявлена как глобальная.
Поначалу это вызывает некоторое удивление, но при ближайшем рассмотрении все объясняется. С одной стороны, требование global
для присваиваемых переменных обеспечивает защиту от непредвиденных побочных эффектов. С другой стороны, если бы global
требовалось для всех глобальных ссылок, вы бы постоянно использовали global
. Вам пришлось бы объявлять глобальной каждую ссылку на встроенную функцию или на компонент импортируемого модуля. Такой беспорядок лишил бы декларацию global
пользы для выявления побочных эффектов.
Почему лямбды, определенные в цикле с разными значениями, возвращают один и тот же результат?¶
Предположим, что вы используете цикл for для определения нескольких различных лямбд (или даже простых функций), например:
>>> squares = []
>>> for x in range(5):
... squares.append(lambda: x**2)
Это даст вам список, содержащий 5 ламбд, которые вычисляют x**2
. Вы можете ожидать, что при вызове они вернут, соответственно, 0
, 1
, 4
, 9
и 16
. Однако, попробовав, вы увидите, что все они возвращают 16
:
>>> squares[2]()
16
>>> squares[4]()
16
Это происходит потому, что x
не является локальным для лямбды, а определен во внешней области видимости, и доступ к нему осуществляется при вызове лямбды — а не при ее определении. В конце цикла значение x
равно 4
, поэтому все функции теперь возвращают 4**2
, то есть 16
. Вы также можете проверить это, изменив значение x
, и посмотреть, как изменяются результаты работы ламбд:
>>> x = 8
>>> squares[2]()
64
Чтобы избежать этого, необходимо сохранять значения в переменных, локальных для лямбд, чтобы они не зависели от значения глобальной x
:
>>> squares = []
>>> for x in range(5):
... squares.append(lambda n=x: n**2)
Здесь n=x
создает новую переменную n
, локальную для лямбды и вычисляемую при ее определении, чтобы она имела то же значение, что и x
в этот момент в цикле. Это означает, что значение n
будет 0
в первой лямбде, 1
во второй, 2
в третьей и так далее. Поэтому теперь каждая лямбда будет возвращать правильный результат:
>>> squares[2]()
4
>>> squares[4]()
16
Обратите внимание, что такое поведение не свойственно лямбдам, а применимо и к обычным функциям.
Каковы «лучшие практики» использования импорта в модуле?¶
Вообще, не используйте from modulename import *
. Это загромождает пространство имен импортера и значительно затрудняет обнаружение неопределенных имен линерами.
Импортируйте модули в верхней части файла. Это позволяет понять, какие еще модули требуются вашему коду, и избежать вопросов о том, находится ли имя модуля в области видимости. Использование одного импорта в строке упрощает добавление и удаление импорта модулей, но использование нескольких импортов в строке занимает меньше места на экране.
Хорошей практикой будет, если вы будете импортировать модули в следующем порядке:
модули стандартной библиотеки - например,
sys
,os
,argparse
,re
модули сторонних библиотек (все, что установлено в каталоге site-packages Python) - например,
dateutil
,requests
,PIL.Image
модули местной разработки
Иногда необходимо перенести импорт в функцию или класс, чтобы избежать проблем с циклическим импортом. Гордон Макмиллан говорит:
Циркулярный импорт работает нормально, когда оба модуля используют форму импорта «import <module>». Они терпят неудачу, когда второй модуль хочет взять имя из первого («from module import name»), а импорт находится на верхнем уровне. Это происходит потому, что имена в 1-м модуле еще недоступны, поскольку первый модуль занят импортом 2-го.
В этом случае, если второй модуль используется только в одной функции, импорт можно легко перенести в эту функцию. К моменту вызова импорта первый модуль закончит инициализацию, и второй модуль сможет выполнить импорт.
Также может потребоваться перенести импорт с верхнего уровня кода, если некоторые модули специфичны для конкретной платформы. В этом случае, возможно, даже не удастся импортировать все модули в верхней части файла. В этом случае хорошим вариантом будет импорт нужных модулей в код, соответствующий конкретной платформе.
Переносите импорт в локальную область видимости, например, внутрь определения функции, только если это необходимо для решения проблемы, например, чтобы избежать кругового импорта, или если вы пытаетесь сократить время инициализации модуля. Этот прием особенно полезен, если многие импорты не нужны в зависимости от того, как выполняется программа. Вы также можете захотеть переместить импорт в функцию, если модули будут использоваться только в этой функции. Обратите внимание, что загрузка модуля в первый раз может быть дорогостоящей из-за однократной инициализации модуля, но загрузка модуля несколько раз практически бесплатна и обходится лишь парой обращений к словарю. Даже если имя модуля вышло из области видимости, модуль, вероятно, доступен в sys.modules
.
Как передать необязательные или ключевые параметры из одной функции в другую?¶
Соберите аргументы с помощью спецификаторов *
и **
в списке параметров функции; при этом позиционные аргументы будут представлены в виде кортежа, а аргументы ключевых слов - в виде словаря. Эти аргументы можно передать при вызове другой функции, используя *
и **
:
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
.
Этот результат обусловлен двумя факторами:
Переменные - это просто имена, которые ссылаются на объекты. Операция
y = x
не создает копию списка - она создает новую переменнуюy
, которая ссылается на тот же объект, на который ссылаетсяx
. Это означает, что существует только один объект (список), и обаx
иy
ссылаются на него.Списки имеют формат 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 аргументы передаются по присваиванию. Поскольку присваивание просто создает ссылки на объекты, между именем аргумента в вызывающей и вызываемой сторонах нет псевдонима, а значит, нет и вызова по ссылке как такового. Добиться желаемого эффекта можно несколькими способами.
Возвращая кортеж результатов:
>>> 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)
Это почти всегда самое правильное решение.
Используя глобальные переменные. Это небезопасно для потоков и не рекомендуется.
Передавая мутабельный (изменяемый на месте) объект:
>>> 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]
Передавая словарь, который будет изменяться:
>>> 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}
Или объедините значения в экземпляре класса:
>>> class Namespace: ... def __init__(self, /, **args): ... for key, value in args.items(): ... setattr(self, key, value) ... >>> def func4(args): ... args.a = 'new-value' # args is a mutable Namespace ... args.b = args.b + 1 # change object in-place ... >>> args = Namespace(a='old-value', b=99) >>> func4(args) >>> vars(args) {'a': 'new-value', 'b': 100}
Почти никогда нет веских причин для таких сложностей.
Лучше всего возвращать кортеж, содержащий несколько результатов.
Как сделать функцию высшего порядка в Python?¶
У вас есть два варианта: вы можете использовать вложенные диапазоны или использовать вызываемые объекты. Например, предположим, что вы хотите определить linear(a,b)
, который возвращает функцию f(x)
, которая вычисляет значение a*x+b
. Использование вложенных диапазонов:
def linear(a, b):
def result(x):
return a * x + b
return result
Или использовать вызываемый объект:
class linear:
def __init__(self, a, b):
self.a, self.b = a, b
def __call__(self, x):
return self.a * x + self.b
В обоих случаях
taxes = linear(0.3, 2)
дает вызываемый объект, где taxes(10e6) == 0.3 * 10e6 + 2
.
Подход с вызываемыми объектами имеет тот недостаток, что он немного медленнее и приводит к немного более длинному коду. Однако обратите внимание, что коллекция вызываемых объектов может иметь общую сигнатуру через наследование:
class exponential(linear):
# __init__ inherited
def __call__(self, x):
return self.a * (x ** self.b)
Объект может инкапсулировать состояние для нескольких методов:
class counter:
value = 0
def set(self, x):
self.value = x
def up(self):
self.value = self.value + 1
def down(self):
self.value = self.value - 1
count = counter()
inc, dec, reset = count.up, count.down, count.set
Здесь inc()
, dec()
и reset()
действуют как функции, имеющие одну и ту же счетную переменную.
Как скопировать объект в Python?¶
В общем случае попробуйте copy.copy()
или copy.deepcopy()
для общего случая. Не все объекты могут быть скопированы, но большинство - да.
Некоторые объекты легче копировать. Словари имеют метод copy()
:
newdict = olddict.copy()
Последовательности можно копировать с помощью нарезки:
new_l = l[:]
Как найти методы или атрибуты объекта?¶
Для экземпляра x
пользовательского класса dir(x)
возвращает алфавитный список имен, содержащих атрибуты экземпляра, а также методы и атрибуты, определенные его классом.
Как мой код может узнать имя объекта?¶
Вообще говоря, не может, потому что у объектов на самом деле нет имен. По сути, присваивание всегда связывает имя со значением; то же самое верно и для операторов def
и class
, но в этом случае значением является вызываемый объект. Рассмотрим следующий код:
>>> class A:
... pass
...
>>> B = A
>>> a = B()
>>> b = a
>>> print(b)
<__main__.A object at 0x16D07CC>
>>> print(a)
<__main__.A object at 0x16D07CC>
Можно утверждать, что у класса есть имя: даже если он связан с двумя именами и вызывается через имя B
, созданный экземпляр все равно сообщается как экземпляр класса A
. Однако невозможно сказать, является ли имя экземпляра a
или b
, поскольку оба имени связаны с одним и тем же значением.
Вообще говоря, вашему коду не должно быть необходимо «знать имена» конкретных значений. Если только вы не пишете интроспективные программы, это обычно является признаком того, что изменение подхода может быть полезным.
В разделе comp.lang.python Фредрик Лундх однажды привел отличную аналогию в ответ на этот вопрос:
Точно так же, как вы узнаете имя той кошки, которую нашли на крыльце: сама кошка (объект) не может сказать вам свое имя, да ей это и не важно, поэтому единственный способ узнать, как ее зовут, - спросить у всех соседей (пространства имен), их ли это кошка (объект)…
…. и не удивляйтесь, если обнаружите, что она известна под разными именами или вообще без названия!
Как обстоят дела с приоритетом оператора запятой?¶
Запятая не является оператором в Python. Рассмотрим этот сеанс:
>>> "a" in "b", "a"
(False, 'a')
Поскольку запятая - это не оператор, а разделитель между выражениями, приведенное выше оценивается так, как если бы вы ввели:
("a" in "b"), "a"
не:
"a" in ("b", "a")
То же самое относится и к различным операторам присваивания (=
, +=
и т. д.). На самом деле это не операторы, а синтаксические разделители в операторах присваивания.
Существует ли эквивалент троичного оператора «?:» в языке C?¶
Да, это так. Синтаксис следующий:
[on_true] if [expression] else [on_false]
x, y = 50, 25
small = x if x < y else y
До появления этого синтаксиса в Python 2.5 распространенной идиомой было использование логических операторов:
[expression] and [on_true] or [on_false]
Однако эта идиома небезопасна, так как может дать неверный результат, если on_true имеет ложное булево значение. Поэтому всегда лучше использовать форму ... if ... else ...
.
Можно ли писать обфусцированные однострочные сообщения на Python?¶
Да. Обычно это делается путем вложения lambda
внутри lambda
. См. следующие три примера, слегка адаптированные Ульфом Бартелтом:
from functools import reduce
# Primes < 1000
print(list(filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0,
map(lambda x,y=y:y%x,range(2,int(pow(y,0.5)+1))),1),range(2,1000)))))
# First 10 Fibonacci numbers
print(list(map(lambda x,f=lambda x,f:(f(x-1,f)+f(x-2,f)) if x>1 else 1:
f(x,f), range(10))))
# Mandelbrot set
print((lambda Ru,Ro,Iu,Io,IM,Sx,Sy:reduce(lambda x,y:x+'\n'+y,map(lambda y,
Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,Sy=Sy,L=lambda yc,Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,i=IM,
Sx=Sx,Sy=Sy:reduce(lambda x,y:x+y,map(lambda x,xc=Ru,yc=yc,Ru=Ru,Ro=Ro,
i=i,Sx=Sx,F=lambda xc,yc,x,y,k,f=lambda xc,yc,x,y,k,f:(k<=0)or (x*x+y*y
>=4.0) or 1+f(xc,yc,x*x-y*y+xc,2.0*x*y+yc,k-1,f):f(xc,yc,x,y,k,f):chr(
64+F(Ru+x*(Ro-Ru)/Sx,yc,0,0,i)),range(Sx))):L(Iu+y*(Io-Iu)/Sy),range(Sy
))))(-2.1, 0.7, -1.2, 1.2, 30, 80, 24))
# \___ ___/ \___ ___/ | | |__ lines on screen
# V V | |______ columns on screen
# | | |__________ maximum of "iterations"
# | |_________________ range on y axis
# |____________________________ range on x axis
Не пытайтесь сделать это дома, дети!
Что означает косая черта (/) в списке параметров функции?¶
Косая черта в списке аргументов функции означает, что параметры перед ней являются только позиционными. Только позиционные параметры - это параметры без внешнего имени. При вызове функции, принимающей только позиционные параметры, аргументы сопоставляются с параметрами исключительно на основе их позиции. Например, divmod()
- это функция, принимающая только позиционные параметры. Ее документация выглядит следующим образом:
>>> help(divmod)
Help on built-in function divmod in module builtins:
divmod(x, y, /)
Return the tuple (x//y, x%y). Invariant: div*y + mod == x.
Косая черта в конце списка параметров означает, что оба параметра являются только позиционными. Таким образом, вызов divmod()
с аргументами в виде ключевых слов приведет к ошибке:
>>> divmod(x=3, y=4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: divmod() takes no keyword arguments
Числа и строки¶
Как указать шестнадцатеричные и восьмеричные целые числа?¶
Чтобы указать восьмеричную цифру, перед восьмеричным значением поставьте ноль, а затем строчную или прописную букву «o». Например, чтобы установить для переменной «a» восьмеричное значение «10» (8 в десятичной системе счисления), введите:
>>> a = 0o10
>>> a
8
Шестнадцатеричная система так же проста. Просто поставьте перед шестнадцатеричным числом ноль, а затем строчную или прописную букву «x». Шестнадцатеричные цифры могут быть указаны как в нижнем, так и в верхнем регистре. Например, в интерпретаторе Python:
>>> a = 0xa5
>>> a
165
>>> b = 0XB2
>>> b
178
Почему -22 // 10 возвращает -3?¶
В первую очередь это вызвано желанием, чтобы i % j
имел тот же знак, что и j
. Если вы хотите этого, а также хотите:
i == (i // j) * j + (i % j)
тогда целочисленное деление должно возвращать пол. C также требует, чтобы тождество выполнялось, и тогда компиляторы, усекающие i // j
, должны сделать так, чтобы i % j
имел тот же знак, что и i
.
Существует мало реальных случаев использования i % j
, когда j
отрицателен. Когда j
положителен, их много, и практически во всех из них полезнее, чтобы i % j
был >= 0
. Если часы показывают 10 сейчас, то что они показывали 200 часов назад? -190 % 12 == 2
полезен; -190 % 12 == -10
- это ошибка, которая только и ждет, чтобы укусить.
Как получить атрибут литерала int вместо ошибки SyntaxError?¶
Попытка найти литеральный атрибут int
обычным способом дает SyntaxError
, поскольку точка воспринимается как десятичная точка:
>>> 1.__class__
File "<stdin>", line 1
1.__class__
^
SyntaxError: invalid decimal literal
Решение - отделить литерал от точки либо пробелом, либо круглыми скобками.
>>> 1 .__class__
<class 'int'>
>>> (1).__class__
<class 'int'>
Как преобразовать строку в число?¶
Для целых чисел используйте встроенный конструктор типов int()
, например int('144') == 144
. Аналогично, float()
преобразуется к плавающей точке, например float('144') == 144.0
.
По умолчанию они интерпретируют число как десятичное, так что int('0144') == 144
будет истинным, а int('0x144')
вызовет ValueError
. int(string, base)
принимает основание для преобразования в качестве второго необязательного аргумента, так что int( '0x144', 16) == 324
. Если основание указано как 0, число интерпретируется по правилам Python: ведущее „0o“ обозначает восьмеричное число, а „0x“ - шестнадцатеричное.
Не используйте встроенную функцию eval()
, если все, что вам нужно, - это преобразовать строки в числа. eval()
будет работать значительно медленнее и представляет собой риск для безопасности: кто-то может передать вам выражение Python, которое может иметь нежелательные побочные эффекты. Например, кто-то может передать __import__('os').system("rm -rf $HOME")
, что приведет к стиранию вашего домашнего каталога.
eval()
также интерпретирует числа как выражения Python, так что, например, eval('09')
выдает синтаксическую ошибку, поскольку Python не допускает ведущего „0“ в десятичном числе (кроме „0“).
Как преобразовать число в строку?¶
Чтобы преобразовать, например, число 144
в строку '144'
, используйте встроенный конструктор типов str()
. Если вам нужно шестнадцатеричное или восьмеричное представление, используйте встроенные функции hex()
или oct()
. Для причудливого форматирования смотрите разделы f-струны и Синтаксис форматной строки, например, "{:04d}".format(144)
дает '0144'
, а "{:.3f}".format(1.0/3.0)
дает '0.333'
.
Как изменить строку на месте?¶
Нельзя, потому что строки неизменяемы. В большинстве ситуаций вам следует просто сконструировать новую строку из различных частей, из которых вы хотите ее собрать. Однако если вам нужен объект, способный изменять юникодные данные на месте, попробуйте использовать объект io.StringIO
или модуль array
:
>>> import io
>>> s = "Hello, world"
>>> sio = io.StringIO(s)
>>> sio.getvalue()
'Hello, world'
>>> sio.seek(7)
7
>>> sio.write("there!")
6
>>> sio.getvalue()
'Hello, there!'
>>> import array
>>> a = array.array('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()
Существует ли эквивалент chomp() в Perl для удаления новых строк из строк?¶
Вы можете использовать S.rstrip("\r\n")
для удаления всех вхождений любого терминатора строки из конца строки S
без удаления других пробельных символов в конце строки. Если строка S
представляет собой более одной строки, с несколькими пустыми строками в конце, то будут удалены концевые символы всех пустых строк:
>>> lines = ("line 1 \r\n"
... "\r\n"
... "\r\n")
>>> lines.rstrip("\n\r")
'line 1 '
Поскольку это обычно требуется только при чтении текста по одной строке за раз, использование S.rstrip()
хорошо работает.
Существует ли эквивалент scanf() или sscanf()?¶
Не как таковой.
Для простого разбора входных данных самым простым подходом обычно является разбиение строки на слова, разделенные пробелами, с помощью метода split()
для строковых объектов, а затем преобразование десятичных строк в числовые значения с помощью int()
или float()
. split()
поддерживает необязательный параметр «sep», который полезен, если в качестве разделителя в строке используется не пробельное пространство, а что-то другое.
Для более сложного разбора ввода регулярные выражения являются более мощными, чем sscanf
в языке Си, и лучше подходят для этой задачи.
Что означает ошибка „UnicodeDecodeError“ или „UnicodeEncodeError“?¶
Смотрите РУКОВОДСТВО по работе с Юникодом.
Можно ли завершить необработанную строку нечетным количеством обратных косых символов?¶
Необработанная строка, заканчивающаяся нечетным количеством обратных косых символов, будет экранирована кавычками:
>>> r'C:\this\will\not\work\'
File "<stdin>", line 1
r'C:\this\will\not\work\'
^
SyntaxError: unterminated string literal (detected at line 1)
Существует несколько способов решения этой проблемы. Один из них - использовать обычные строки и удваивать обратные слеши:
>>> 'C:\\this\\will\\work\\'
'C:\\this\\will\\work\\'
Другой способ - конкатенировать обычную строку, содержащую экранированный обратный слеш, с исходной строкой:
>>> r'C:\this\will\work' '\\'
'C:\\this\\will\\work\\'
Также можно использовать os.path.join()
для добавления обратного слеша в Windows:
>>> os.path.join(r'C:\this\will\work', '')
'C:\\this\\will\\work\\'
Обратите внимание, что хотя обратная косая черта «освобождает» кавычки для определения места окончания исходной строки, при интерпретации значения исходной строки экранирование не происходит. То есть обратная косая черта остается в значении исходной строки:
>>> r'backslash\'preserved'
"backslash\\'preserved"
Также смотрите спецификацию в language reference.
Производительность¶
Моя программа работает слишком медленно. Как мне ускорить ее?¶
Это вообще сложный вопрос. Во-первых, вот список вещей, о которых следует помнить, прежде чем погружаться дальше:
Характеристики производительности в разных реализациях Python различны. Этот FAQ посвящен CPython.
В разных операционных системах поведение может быть разным, особенно если речь идет о вводе-выводе или многопоточности.
Вы всегда должны находить «горячие точки» в вашей программе перед попыткой оптимизировать код (см. модуль
profile
).Написание сценариев бенчмарков позволит вам быстро выполнять итерации при поиске улучшений (см. модуль
timeit
).Настоятельно рекомендуется обеспечить хорошее покрытие кода (с помощью модульного тестирования или любой другой техники), прежде чем потенциально внедрять регрессии, скрытые в сложных оптимизациях.
Тем не менее, существует множество приемов, позволяющих ускорить работу кода на Python. Вот несколько общих принципов, которые помогут достичь приемлемого уровня производительности:
Ускорение работы алгоритмов (или переход на более быстрые алгоритмы) может принести гораздо больше пользы, чем попытка рассыпать по всему коду трюки микрооптимизации.
Используйте правильные структуры данных. Изучите документацию по модулю Встроенные типы и модулю
collections
.Когда стандартная библиотека предоставляет примитив для выполнения какой-либо задачи, он, скорее всего (хотя и не гарантированно), будет быстрее, чем любая альтернатива, которую вы можете придумать. Это вдвойне верно для примитивов, написанных на языке C, таких как встроенные методы и некоторые типы расширения. Например, для сортировки обязательно используйте либо встроенный метод
list.sort()
, либо связанную с ним функциюsorted()
(и смотрите Сортировка, КАК для примеров умеренно продвинутого использования).Абстракции, как правило, создают непрямые связи и заставляют интерпретатор работать больше. Если уровень непрямолинейности перевешивает количество выполняемой полезной работы, ваша программа будет работать медленнее. Следует избегать чрезмерных абстракций, особенно в виде крошечных функций или методов (которые также часто вредят читабельности).
Если вы достигли предела возможностей чистого Python, есть инструменты, которые помогут вам продвинуться дальше. Например, Cython может компилировать слегка измененную версию кода Python в расширение C и может использоваться на многих платформах. Cython может использовать преимущества компиляции (и дополнительные аннотации типов), чтобы сделать ваш код значительно быстрее, чем при интерпретации. Если вы уверены в своих навыках программирования на C, вы также можете write a C extension module самостоятельно.
См.также
Вики-страница, посвященная performance tips.
Каков наиболее эффективный способ конкатенации множества строк?¶
<<<Объекты str
и bytes
неизменяемы, поэтому конкатенация многих строк вместе неэффективна, так как при каждой конкатенации создается новый объект. В общем случае общие затраты времени выполнения квадратичны по отношению к общей длине строки.
Чтобы накопить много объектов str
, рекомендуемая идиома - поместить их в список и вызвать str.join()
в конце:
chunks = []
for s in my_strings:
chunks.append(s)
result = ''.join(chunks)
(другой достаточно эффективной идиомой является использование io.StringIO
)
Чтобы накопить много объектов bytes
, рекомендуется расширять объект bytearray
с помощью конкатенации на месте (оператор +=
):
result = bytearray()
for b in my_bytes_objects:
result += b
Последовательности (кортежи/списки)¶
Как преобразовать кортежи в списки?¶
Конструктор типов tuple(seq)
преобразует любую последовательность (фактически, любую итерируемую) в кортеж с теми же элементами в том же порядке.
Например, tuple([1, 2, 3])
дает (1, 2, 3)
, а tuple('abc')
дает ('a', 'b', 'c')
. Если аргумент является кортежем, то он не создает копию, а возвращает тот же объект, поэтому удобно вызывать tuple()
, когда вы не уверены, что объект уже является кортежем.
Конструктор типа list(seq)
преобразует любую последовательность или итерируемую переменную в список с теми же элементами в том же порядке. Например, list((1, 2, 3))
дает [1, 2, 3]
, а list('abc')
- ['a', 'b', 'c']
. Если аргумент является списком, он создает копию, как это делает seq[:]
.
Что такое отрицательный индекс?¶
Последовательности Python индексируются положительными и отрицательными числами. Для положительных чисел 0 - это первый индекс, 1 - второй индекс и так далее. Для отрицательных чисел -1 - последний индекс, -2 - предпоследний (следующий за последним) индекс и так далее. Считайте, что seq[-n]
- это то же самое, что seq[len(seq)-n]
.
Использование отрицательных индексов может быть очень удобным. Например, S[:-1]
- это вся строка, кроме ее последнего символа, что удобно для удаления из строки начальной строки.
Как выполнить итерацию по последовательности в обратном порядке?¶
Используйте встроенную функцию reversed()
:
for x in reversed(sequence):
... # do something with x ...
Это не затронет вашу исходную последовательность, но создаст новую копию с измененным порядком, которую можно будет повторить.
Как удалить дубликаты из списка?¶
В поваренной книге Python вы найдете подробное описание многих способов сделать это:
Если вы не против изменить порядок списка, отсортируйте его, а затем сканируйте с конца списка, удаляя дубликаты по мере продвижения:
if mylist:
mylist.sort()
last = mylist[-1]
for i in range(len(mylist)-2, -1, -1):
if last == mylist[i]:
del mylist[i]
else:
last = mylist[i]
Если все элементы списка могут быть использованы в качестве ключей набора (то есть все они hashable), это часто быстрее
mylist = list(set(mylist))
Это преобразует список в набор, удаляя дубликаты, а затем снова в список.
Как удалить несколько элементов из списка¶
Как и в случае с удалением дубликатов, одним из вариантов является явный обратный итерационный цикл с условием удаления. Однако проще и быстрее использовать замену фрагментов с неявной или явной прямой итерацией. Вот три варианта.:
mylist[:] = filter(keep_function, mylist)
mylist[:] = (x for x in mylist if keep_condition)
mylist[:] = [x for x in mylist if keep_condition]
Понимание списка может быть самым быстрым.
Как создать массив в Python?¶
Используйте список:
["this", 1, "is", "an", "array"]
По своей временной сложности списки эквивалентны массивам в C или Pascal; основное отличие заключается в том, что список в Python может содержать объекты разных типов.
Модуль array
также предоставляет методы для создания массивов фиксированных типов с компактным представлением, но они индексируются медленнее, чем списки. Также обратите внимание, что NumPy и другие сторонние пакеты также определяют массивоподобные структуры с различными характеристиками.
Чтобы получить связные списки в стиле Lisp, вы можете эмулировать cons cells с помощью tuples:
lisp_list = ("like", ("this", ("example", None) ) )
Если вам нужна мутабельность, вы можете использовать списки вместо кортежей. Здесь аналогом лисповского car является lisp_list[0]
, а аналогом cdr - lisp_list[1]
. Делайте это только в том случае, если вы уверены, что вам это действительно нужно, потому что это обычно намного медленнее, чем использование списков Python.
Как создать многомерный список?¶
Вероятно, вы пытались создать многомерный массив следующим образом:
>>> A = [[None] * 2] * 3
При печати все выглядит правильно:
>>> A
[[None, None], [None, None], [None, None]]
Но когда вы присваиваете значение, оно отображается в нескольких местах:
>>> A[0][0] = 5
>>> A
[[5, None], [5, None], [5, None]]
Причина в том, что репликация списка с помощью *
не создает копий, а только ссылки на существующие объекты. *3
создает список, содержащий 3 ссылки на один и тот же список длины два. Изменения в одной строке будут отображаться во всех строках, а это почти наверняка не то, чего вы хотите.
Предлагаемый подход заключается в том, чтобы сначала создать список нужной длины, а затем заполнить каждый элемент вновь созданным списком:
A = [None] * 3
for i in range(3):
A[i] = [None] * 2
Это генерирует список, содержащий 3 различных списка длины два. Вы также можете использовать осмысление списка:
w, h = 2, 3
A = [[None] * w for i in range(h)]
Или вы можете использовать расширение, предоставляющее матричный тип данных; наиболее известным является NumPy.
Как применить метод или функцию к последовательности объектов?¶
Чтобы вызвать метод или функцию и накапливать возвращаемые значения в виде списка, list comprehension является элегантным решением:
result = [obj.method() for obj in mylist]
result = [function(obj) for obj in mylist]
Чтобы просто запустить метод или функцию без сохранения возвращаемых значений, достаточно обычного цикла for
:
for obj in mylist:
obj.method()
for obj in mylist:
function(obj)
Почему при сложении a_tuple[i] += [„item“] возникает исключение?¶
Это связано с тем, что дополненные операторы присваивания являются операторами присваивания, а также с различием между мутабельными и неизменяемыми объектами в Python.
Это обсуждение применимо в общем случае, когда дополненные операторы присваивания применяются к элементам кортежа, указывающим на изменяемые объекты, но мы будем использовать list
и +=
в качестве примера.
Если вы написали:
>>> a_tuple = (1, 2)
>>> a_tuple[0] += 1
Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment
Причина исключения должна быть понятна сразу: 1
добавляется к объекту a_tuple[0]
, на который указывает 1
, создавая объект результата 2
, но когда мы пытаемся присвоить результат вычисления, 2
, элементу 0
кортежа, мы получаем ошибку, поскольку не можем изменить, на что указывает элемент кортежа.
Под обложкой это расширенное задание выглядит примерно так:
>>> result = a_tuple[0] + 1
>>> a_tuple[0] = result
Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment
Ошибка возникает именно при выполнении операции присваивания, поскольку кортеж неизменяем.
Когда вы пишете что-то вроде:
>>> a_tuple = (['foo'], 'bar')
>>> a_tuple[0] += ['item']
Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment
Исключение немного удивительнее, а еще удивительнее тот факт, что даже несмотря на ошибку, добавление сработало:
>>> a_tuple[0]
['foo', 'item']
Чтобы понять, почему так происходит, нужно знать, что (а) если объект реализует магический метод __iadd__()
, он вызывается при выполнении дополненного присваивания +=
, и его возвращаемое значение используется в операторе присваивания; и (б) для списков __iadd__()
эквивалентно вызову extend()
на списке и возвращению списка. Поэтому мы говорим, что для списков +=
- это «сокращение» для list.extend()
:
>>> a_list = []
>>> a_list += [1]
>>> a_list
[1]
Это эквивалентно:
>>> result = a_list.__iadd__([1])
>>> a_list = result
Объект, на который указывает a_list, был изменен, и указатель на измененный объект присваивается обратно a_list
. Конечный результат присваивания не имеет смысла, поскольку это указатель на тот же объект, на который ранее указывал a_list
, но присваивание все равно происходит.
Таким образом, в нашем примере с кортежем происходящее эквивалентно:
>>> result = a_tuple[0].__iadd__(['item'])
>>> a_tuple[0] = result
Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment
Присвоение __iadd__()
проходит успешно, и таким образом список расширяется, но хотя result
указывает на тот же объект, на который уже указывает a_tuple[0]
, это окончательное присваивание все равно приводит к ошибке, поскольку кортежи неизменяемы.
Я хочу сделать сложную сортировку: можете ли вы сделать преобразование Шварца в Python?¶
Эта техника, приписываемая Рэндалу Шварцу из сообщества Perl, сортирует элементы списка по метрике, которая сопоставляет каждому элементу его «значение сортировки». В Python для метода list.sort()
используйте аргумент key
:
Isorted = L[:]
Isorted.sort(key=lambda s: int(s[10:15]))
Как отсортировать один список по значениям из другого списка?¶
Объедините их в итератор кортежей, отсортируйте полученный список, а затем выберите нужный вам элемент.
>>> list1 = ["what", "I'm", "sorting", "by"]
>>> list2 = ["something", "else", "to", "sort"]
>>> pairs = zip(list1, list2)
>>> pairs = sorted(pairs)
>>> pairs
[("I'm", 'else'), ('by', 'sort'), ('sorting', 'to'), ('what', 'something')]
>>> result = [x[1] for x in pairs]
>>> result
['else', 'sort', 'to', 'something']
Объекты¶
Что такое класс?¶
Класс - это конкретный тип объекта, созданный с помощью оператора class. Объекты класса используются в качестве шаблонов для создания объектов экземпляра, которые воплощают в себе как данные (атрибуты), так и код (методы), характерные для типа данных.
Класс может быть основан на одном или нескольких других классах, называемых его базовыми классами. При этом он наследует атрибуты и методы своих базовых классов. Это позволяет последовательно уточнять объектную модель с помощью наследования. У вас может быть общий класс Mailbox
, который предоставляет основные методы доступа к почтовому ящику, и подклассы MboxMailbox
, MaildirMailbox
, OutlookMailbox
, которые работают с различными специфическими форматами почтовых ящиков.
Что такое метод?¶
Метод - это функция на некотором объекте x
, которую вы обычно вызываете как x.name(arguments...)
. Методы определяются как функции внутри определения класса:
class C:
def meth(self, arg):
return arg * 2 + self.attribute
Что такое самость?¶
Self - это просто условное название первого аргумента метода. Метод, определенный как meth(self, a, b, c)
, должен вызываться как x.meth(a, b, c)
для некоторого экземпляра x
класса, в котором происходит определение; вызываемый метод будет думать, что он вызывается как meth(x, a, b, c)
.
См. также Почему в определениях и вызовах методов необходимо явно использовать „self“?.
Как проверить, является ли объект экземпляром данного класса или его подклассом?¶
Используйте встроенную функцию isinstance(obj, cls)
. Вы можете проверить, является ли объект экземпляром любого из нескольких классов, указав кортеж вместо одного класса, например isinstance(obj, (class1, class2, ...))
, а также проверить, является ли объект одним из встроенных типов Python, например isinstance(obj, str)
или isinstance(obj, (int, float, complex))
.
Обратите внимание, что isinstance()
также проверяет виртуальное наследование от abstract base class. Таким образом, тест вернет True
для зарегистрированного класса, даже если он не наследует от него прямо или косвенно. Чтобы проверить «истинное наследование», просканируйте MRO класса:
from collections.abc import Mapping
class P:
pass
class C(P):
pass
Mapping.register(P)
>>> c = C()
>>> isinstance(c, C) # direct
True
>>> isinstance(c, P) # indirect
True
>>> isinstance(c, Mapping) # virtual
True
# Actual inheritance chain
>>> type(c).__mro__
(<class 'C'>, <class 'P'>, <class 'object'>)
# Test for "true inheritance"
>>> Mapping in type(c).__mro__
False
Обратите внимание, что большинство программ не часто используют isinstance()
в пользовательских классах. Если вы сами разрабатываете классы, то более правильным объектно-ориентированным стилем будет определение методов в классах, которые инкапсулируют определенное поведение, вместо того чтобы проверять класс объекта и делать разные вещи в зависимости от того, к какому классу он относится. Например, если у вас есть функция, которая делает что-то:
def search(obj):
if isinstance(obj, Mailbox):
... # code to search a mailbox
elif isinstance(obj, Document):
... # code to search a document
elif ...
Лучший подход - определить метод search()
для всех классов и просто вызывать его:
class Mailbox:
def search(self):
... # code to search a mailbox
class Document:
def search(self):
... # code to search a document
obj.search()
Что такое делегирование?¶
Делегирование - это объектно-ориентированная техника (также называемая паттерном проектирования). Допустим, у вас есть объект x
и вы хотите изменить поведение только одного из его методов. Вы можете создать новый класс, который обеспечит новую реализацию метода, который вы хотите изменить, и делегирует все остальные методы соответствующему методу x
.
Программисты Python могут легко реализовать делегирование. Например, следующий класс реализует класс, который ведет себя как файл, но преобразует все записанные данные в верхний регистр:
class UpperOut:
def __init__(self, outfile):
self._outfile = outfile
def write(self, s):
self._outfile.write(s.upper())
def __getattr__(self, name):
return getattr(self._outfile, name)
Здесь класс UpperOut
переопределяет метод write()
, чтобы преобразовать строку аргумента в верхний регистр перед вызовом основного метода self._outfile.write()
. Все остальные методы делегируются базовому объекту self._outfile
. Делегирование осуществляется с помощью метода __getattr__()
; дополнительную информацию о контроле доступа к атрибутам см. в разделе the language reference.
Обратите внимание, что для более общих случаев делегирование может оказаться сложнее. Когда атрибуты должны быть установлены, а также получены, класс должен определить метод __setattr__()
, и сделать это нужно тщательно. Базовая реализация __setattr__()
примерно эквивалентна следующей:
class X:
...
def __setattr__(self, name, value):
self.__dict__[name] = value
...
Большинство реализаций __setattr__()
должны модифицировать self.__dict__
, чтобы хранить локальное состояние для себя, не вызывая бесконечной рекурсии.
Как вызвать метод, определенный в базовом классе, из производного класса, который его расширяет?¶
Используйте встроенную функцию super()
:
class Derived(Base):
def meth(self):
super().meth() # calls Base.meth
В примере super()
автоматически определит экземпляр, из которого он был вызван (значение self
), найдет method resolution order (MRO) с помощью type(self).__mro__
, и вернет следующее по порядку после Derived
в MRO: Base
.
Как мне организовать свой код, чтобы было проще изменять базовый класс?¶
Вы можете присвоить базовому классу псевдоним и вывести его из псевдонима. Тогда все, что вам нужно будет изменить, - это значение, присвоенное псевдониму. Кстати, этот трюк также удобен, если вы хотите динамически (например, в зависимости от доступности ресурсов) решать, какой базовый класс использовать. Пример:
class Base:
...
BaseAlias = Base
class Derived(BaseAlias):
...
Как создать статические данные класса и статические методы класса?¶
В Python поддерживаются как статические данные, так и статические методы (в смысле C++ или Java).
Для статических данных достаточно определить атрибут класса. Чтобы присвоить атрибуту новое значение, необходимо явно использовать имя класса в назначении:
class C:
count = 0 # number of times C.__init__ called
def __init__(self):
C.count = C.count + 1
def getcount(self):
return C.count # or return self.count
c.count
также ссылается на C.count
для любого c
такого, что isinstance(c, C)
выполняется, если только он не переопределен самим c
или каким-либо классом на пути поиска базового класса от c.__class__
обратно к C
.
Внимание: внутри метода C присваивание типа self.count = 42
создает новый, не связанный с ним экземпляр с именем «count» в собственном dict self
. Перепривязка статического имени данных класса всегда должна указывать на класс, независимо от того, находится он внутри метода или нет:
C.count = 314
Возможны статические методы:
class C:
@staticmethod
def static(arg1, arg2, arg3):
# No 'self' parameter!
...
Однако гораздо более простой способ получить эффект статического метода - это простая функция на уровне модуля:
def getcount():
return C.count
Если ваш код структурирован таким образом, что в каждом модуле определяется один класс (или тесно связанная иерархия классов), это обеспечивает желаемую инкапсуляцию.
Как перегрузить конструкторы (или методы) в Python?¶
На самом деле этот ответ относится ко всем методам, но обычно этот вопрос возникает в первую очередь в контексте конструкторов.
На C++ вы бы написали
class C {
C() { cout << "No arguments\n"; }
C(int i) { cout << "Argument is " << i << "\n"; }
}
В Python вы должны написать один конструктор, который перехватывает все случаи, используя аргументы по умолчанию. Например:
class C:
def __init__(self, i=None):
if i is None:
print("No arguments")
else:
print("Argument is", i)
Это не совсем эквивалентно, но на практике достаточно близко.
Можно также попробовать использовать список аргументов переменной длины, например
def __init__(self, *args):
...
Этот же подход работает для всех определений метода.
Я пытаюсь использовать __spam и получаю ошибку о _SomeClassName__spam.¶
Имена переменных с двойным ведущим подчеркиванием «искажаются», чтобы обеспечить простой, но эффективный способ определения приватных переменных класса. Любой идентификатор вида __spam
(не менее двух ведущих подчеркиваний, не более одного завершающего подчеркивания) текстуально заменяется на _classname__spam
, где classname
- текущее имя класса с удаленными ведущими подчеркиваниями.
Это не гарантирует конфиденциальности: посторонний пользователь все равно может намеренно получить доступ к атрибуту «_classname__spam», а приватные значения видны в __dict__
объекта. Многие программисты на Python вообще не утруждают себя использованием имен приватных переменных.
Мой класс определяет __del__, но он не вызывается, когда я удаляю объект.¶
Этому есть несколько возможных причин.
Оператор del
не обязательно вызывает __del__()
- он просто уменьшает количество ссылок на объект, и если оно достигает нуля, вызывается __del__()
.
Если ваши структуры данных содержат циклические связи (например, дерево, в котором каждый ребенок имеет ссылку на родителя, а каждый родитель - на список детей), количество ссылок никогда не вернется к нулю. Время от времени Python запускает алгоритм для обнаружения таких циклов, но сборщик мусора может запуститься через некоторое время после исчезновения последней ссылки на вашу структуру данных, поэтому ваш метод __del__()
может быть вызван в неудобное и случайное время. Это неудобно, если вы пытаетесь воспроизвести проблему. Хуже того, порядок выполнения методов __del__()
объекта произволен. Вы можете запустить gc.collect()
, чтобы заставить объект собираться, но существуют патологические случаи, когда объекты никогда не будут собраны.
Несмотря на коллектор циклов, все же полезно определить явный метод close()
для объектов, который будет вызываться всякий раз, когда вы с ними закончите работу. Метод close()
может удалять атрибуты, ссылающиеся на подобъекты. Не вызывайте __del__()
напрямую - __del__()
должен вызывать close()
, а close()
должен быть уверен, что его можно вызвать более одного раза для одного и того же объекта.
Другой способ избежать циклических ссылок - использовать модуль weakref
, который позволяет указывать на объекты, не увеличивая счетчик их ссылок. Например, древовидные структуры данных должны использовать слабые ссылки для своих родительских и сиблинговых ссылок (если они им нужны!).
Наконец, если ваш метод __del__()
вызывает исключение, в sys.stderr
будет выведено предупреждающее сообщение.
Как получить список всех экземпляров данного класса?¶
Python не ведет учет всех экземпляров класса (или встроенного типа). Вы можете запрограммировать конструктор класса так, чтобы он отслеживал все экземпляры, сохраняя список слабых ссылок на каждый экземпляр.
Почему результат id()
оказывается неуникальным?¶
Встроенный модуль id()
возвращает целое число, которое гарантированно будет уникальным в течение всего времени существования объекта. Поскольку в CPython это адрес памяти объекта, часто случается, что после удаления объекта из памяти следующий свежесозданный объект выделяется в той же позиции в памяти. Это можно проиллюстрировать на данном примере:
>>> id(1000)
13901272
>>> id(2000)
13901272
Эти два идентификатора принадлежат разным целочисленным объектам, которые создаются до и удаляются сразу после выполнения вызова id()
. Чтобы убедиться, что объекты, id которых вы хотите проверить, все еще живы, создайте еще одну ссылку на объект:
>>> a = 1000; b = 2000
>>> id(a)
13901272
>>> id(b)
13891296
Когда можно полагаться на тесты идентичности с оператором is?¶
Оператор is
проверяет идентичность объекта. Тест a is b
эквивалентен id(a) == id(b)
.
Важнейшим свойством теста на идентичность является то, что объект всегда идентичен самому себе, a is a
всегда возвращает True
. Тесты идентичности обычно быстрее, чем тесты равенства. И в отличие от тестов равенства, тесты идентичности гарантированно возвращают булево True
или False
.
Однако тесты на идентичность можно только заменить тестами на равенство, когда идентичность объекта гарантирована. Как правило, есть три обстоятельства, при которых идентичность гарантирована:
1) Assignments create new names but do not change object identity. After the
assignment new = old
, it is guaranteed that new is old
.
2) Putting an object in a container that stores object references does not
change object identity. After the list assignment s[0] = x
, it is
guaranteed that s[0] is x
.
3) If an object is a singleton, it means that only one instance of that object
can exist. After the assignments a = None
and b = None
, it is
guaranteed that a is b
because None
is a singleton.
В большинстве других обстоятельств тесты на идентичность нежелательны, предпочтительнее тесты на равенство. В частности, тесты тождества не следует использовать для проверки таких констант, как int
и str
, которые не гарантированно являются синглтонами:
>>> a = 1000
>>> b = 500
>>> c = b + 500
>>> a is c
False
>>> a = 'Python'
>>> b = 'Py'
>>> c = b + 'thon'
>>> a is c
False
Аналогично, новые экземпляры изменяемых контейнеров никогда не бывают идентичными:
>>> a = []
>>> b = []
>>> a is b
False
В коде стандартной библиотеки вы увидите несколько общих шаблонов для правильного использования тестов идентичности:
1) As recommended by PEP 8, an identity test is the preferred way to check
for None
. This reads like plain English in code and avoids confusion with
other objects that may have boolean values that evaluate to false.
2) Detecting optional arguments can be tricky when None
is a valid input
value. In those situations, you can create a singleton sentinel object
guaranteed to be distinct from other objects. For example, here is how
to implement a method that behaves like dict.pop()
:
_sentinel = object()
def pop(self, key, default=_sentinel):
if key in self:
value = self[key]
del self[key]
return value
if default is _sentinel:
raise KeyError(key)
return default
3) Container implementations sometimes need to augment equality tests with
identity tests. This prevents the code from being confused by objects such as
float('NaN')
that are not equal to themselves.
Например, вот реализация collections.abc.Sequence.__contains__()
:
def __contains__(self, value):
for v in self:
if v is value or v == value:
return True
return False
Как подкласс может контролировать, какие данные хранятся в неизменяемом экземпляре?¶
При подклассификации неизменяемого типа переопределите метод __new__()
вместо метода __init__()
. Последний выполняется только после создания экземпляра, а это слишком поздно для изменения данных в неизменяемом экземпляре.
Все эти неизменяемые классы имеют сигнатуру, отличную от сигнатуры их родительского класса:
from datetime import date
class FirstOfMonthDate(date):
"Always choose the first day of the month"
def __new__(cls, year, month, day):
return super().__new__(cls, year, month, 1)
class NamedInt(int):
"Allow text names for some numbers"
xlat = {'zero': 0, 'one': 1, 'ten': 10}
def __new__(cls, value):
value = cls.xlat.get(value, value)
return super().__new__(cls, value)
class TitleStr(str):
"Convert str to name suitable for a URL path"
def __new__(cls, s):
s = s.lower().replace(' ', '-')
s = ''.join([c for c in s if c.isalnum() or c == '-'])
return super().__new__(cls, s)
Классы можно использовать следующим образом:
>>> FirstOfMonthDate(2012, 2, 14)
FirstOfMonthDate(2012, 2, 1)
>>> NamedInt('ten')
10
>>> NamedInt(20)
20
>>> TitleStr('Blog: Why Python Rocks')
'blog-why-python-rocks'
Как кэшировать вызовы методов?¶
Два основных инструмента для кэширования методов - functools.cached_property()
и functools.lru_cache()
. Первый хранит результаты на уровне экземпляра, а второй - на уровне класса.
Подход cached_property работает только с методами, которые не принимают никаких аргументов. При этом не создается ссылка на экземпляр. Кэшированный результат метода будет храниться только до тех пор, пока жив экземпляр.
Преимущество в том, что когда экземпляр больше не используется, кэшированный результат метода будет сразу же освобожден. Недостаток в том, что если экземпляры накапливаются, то накапливаются и результаты методов. Они могут расти неограниченно.
Подход lru_cache работает с методами, имеющими hashable аргументов. Он создает ссылку на экземпляр, если не предпринимать особых усилий для передачи слабых ссылок.
Преимущество алгоритма наименьшего использования заключается в том, что кэш ограничивается заданным maxsize. Недостатком является то, что экземпляры остаются живыми до тех пор, пока они не выйдут из кэша или пока кэш не будет очищен.
В этом примере показаны различные техники:
class Weather:
"Lookup weather information on a government website"
def __init__(self, station_id):
self._station_id = station_id
# The _station_id is private and immutable
def current_temperature(self):
"Latest hourly observation"
# Do not cache this because old results
# can be out of date.
@cached_property
def location(self):
"Return the longitude/latitude coordinates of the station"
# Result only depends on the station_id
@lru_cache(maxsize=20)
def historic_rainfall(self, date, units='mm'):
"Rainfall on a given date"
# Depends on the station_id, date, and units.
В приведенном выше примере предполагается, что station_id никогда не меняется. Если соответствующие атрибуты экземпляра являются изменяемыми, подход cached_property не может работать, поскольку он не может обнаружить изменения атрибутов.
Чтобы подход lru_cache работал, когда station_id мутабелен, класс должен определить методы __eq__()
и __hash__()
, чтобы кэш мог обнаружить соответствующие обновления атрибутов:
class Weather:
"Example with a mutable station identifier"
def __init__(self, station_id):
self.station_id = station_id
def change_station(self, station_id):
self.station_id = station_id
def __eq__(self, other):
return self.station_id == other.station_id
def __hash__(self):
return hash(self.station_id)
@lru_cache(maxsize=20)
def historic_rainfall(self, date, units='cm'):
'Rainfall on a given date'
# Depends on the station_id, date, and units.
Модули¶
Как создать файл .pyc?¶
При первом импорте модуля (или если исходный файл изменился с момента создания текущего скомпилированного файла) в подкаталоге __pycache__
каталога, содержащего файл .py
, должен быть создан файл .pyc
, содержащий скомпилированный код. Файл .pyc
будет иметь имя, начинающееся с того же имени, что и файл .py
, и заканчивающееся .pyc
, со средней составляющей, которая зависит от конкретного бинарного файла python
, который его создал. (Подробности см. в разделе PEP 3147).
Одной из причин, по которой файл .pyc
может не создаваться, является проблема с правами доступа в каталоге, содержащем исходный файл, что означает, что подкаталог __pycache__
не может быть создан. Это может произойти, например, если вы разрабатываете от имени одного пользователя, а запускаете от имени другого, например, если вы тестируете веб-сервер.
Если не установлена переменная окружения PYTHONDONTWRITEBYTECODE
, создание файла .pyc происходит автоматически, если вы импортируете модуль и у Python есть возможность (права, свободное место и т. д.) создать подкаталог __pycache__
и записать скомпилированный модуль в этот подкаталог.
Запуск Python в скрипте верхнего уровня не считается импортом, и никаких .pyc
не будет создано. Например, если у вас есть модуль верхнего уровня foo.py
, который импортирует другой модуль xyz.py
, при запуске foo
(набрав python foo.py
в качестве команды оболочки) для xyz
будет создан файл .pyc
, поскольку xyz
импортируется, но для foo
не будет создан файл .pyc
, поскольку foo.py
не импортируется.
Если вам нужно создать файл .pyc
для foo
- то есть создать файл .pyc
для модуля, который не импортируется - вы можете это сделать, используя модули py_compile
и compileall
.
Модуль py_compile
может вручную скомпилировать любой модуль. Один из способов - интерактивное использование функции compile()
в этом модуле:
>>> import py_compile
>>> py_compile.compile('foo.py')
Это приведет к записи .pyc
в подкаталог __pycache__
в том же месте, что и foo.py
(или вы можете отменить это с помощью необязательного параметра cfile
).
Вы также можете автоматически скомпилировать все файлы в каталоге или каталогах с помощью модуля compileall
. Это можно сделать из интерпретатора 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'