Часто задаваемые вопросы по программированию
-
- Есть ли отладчик на уровне исходного кода с точками останова, пошаговым выполнением и т. д.?
- Есть ли инструменты для поиска ошибок или выполнения статического анализа?
- Как создать автономный исполняемый файл из скрипта Python?
- Существуют ли стандарты кодирования или руководство по стилю для программ Python?
-
- Почему возникает ошибка UnboundLocalError, когда переменная имеет значение?
- Какие правила для локальных и глобальных переменных в Python?
- Почему лямбда-функции, определённые в цикле с разными значениями, возвращают одинаковый результат?
- Как разделить глобальные переменные между модулями?
- Какие лучшие практики для использования import в модуле?
- Почему значения по умолчанию разделяются между объектами?
- Как передать необязательные или ключевые параметры из одной функции в другую?
- В чём разница между аргументами и параметрами?
- Почему изменение списка ‘y’ также изменило список ‘x’?
- Как написать функцию с выходными параметрами (вызов по ссылке)?
- Как создать функцию высшего порядка в Python?
- Как скопировать объект в Python?
- Как найти методы или атрибуты объекта?
- Как код может определить имя объекта?
- Что с приоритетом оператора запятой?
- Есть ли эквивалент условного оператора C «?:»?
- Можно ли написать зашифрованные однострочные выражения в Python?
- Что означает символ «/» в списке параметров функции?
-
- Как задать шестнадцатеричные и восьмеричные целые числа?
- Почему -22 // 10 возвращает -3?
- Как преобразовать строку в число?
- Как преобразовать число в строку?
- Как изменить строку на месте?
- Как использовать строки для вызова функций/методов?
- Есть ли эквивалент Perl’s chomp() для удаления завершающих символов новой строки из строк?
- Есть ли эквивалент scanf() или sscanf()?
- Что означает ошибка ‘UnicodeDecodeError’ или ‘UnicodeEncodeError’?
-
Последовательности (Кортежи/Списки)
- Как преобразовывать между кортежами и списками?
- Что такое отрицательный индекс?
- Как перебрать последовательность в обратном порядке?
- Как удалить дубликаты из списка?
- Как удалить несколько элементов из списка
- Как создать массив в Python?
- Как создать многомерный список?
- Как применить метод к последовательности объектов?
- Почему a_tuple[i] += [‘item’] вызывает исключение, когда сложение работает?
- Хочу выполнить сложное сортирование: можно ли сделать преобразование Шварцмана в Python?
- Как отсортировать один список по значениям из другого списка?
-
- Что такое класс?
- Что такое метод?
- Что такое self?
- Как проверить, является ли объект экземпляром данного класса или его подкласса?
- Что такое делегирование?
- Как вызвать метод, определённый в базовом классе из производного класса, который его переопределяет?
- Как организовать код для более лёгкого изменения базового класса?
- Как создать статические данные и статические методы класса?
- Как перегрузить конструкторы (или методы) в Python?
- При попытке использовать __spam возникает ошибка _SomeClassName__spam.
- Мой класс определяет __del__, но он не вызывается при удалении объекта.
- Как получить список всех экземпляров данного класса?
- Почему результат
id()кажется не уникальным?
Общие вопросы
Есть ли отладчик на уровне исходного кода с точками останова, пошаговым выполнением и т.д.?
Да.
Ниже описаны несколько отладчиков для Python, а встроенная функция breakpoint() позволяет вам перейти к любому из них.
Модуль pdb — это простой, но адекватный отладчик командной строки для Python. Он входит в стандартную библиотеку Python и documented in the Library
Reference Manual. Вы также можете написать свой собственный отладчик, используя код pdb в качестве примера.
Интерактивная среда разработки IDLE, которая входит в стандартную дистрибуцию Python (обычно доступна как Tools/scripts/idle), включает графический отладчик.
PythonWin — это IDE для Python, которая включает графический отладчик, основанный на 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.
Он работает, рекурсивно сканируя ваш исходный код на наличие операторов import (в обеих формах) и ищет модули в стандартном пути Python, а также в каталоге исходных файлов (для встроенных модулей). Затем он преобразует байткод для модулей, написанных на Python, в код C (инициализаторы массивов, которые можно преобразовать в объекты кода с помощью модуля marshal) и создаёт настраиваемый файл конфигурации, содержащий только те встроенные модули, которые фактически используются в программе. Затем он компилирует сгенерированный код C и связывает его с остальной частью интерпретатора Python, формируя автономный двоичный файл, который действует точно так же, как ваш скрипт.
Очевидно, для работы freeze требуется компилятор C. Существуют и другие утилиты, для которых это не требуется:
Есть ли стандарты кодирования или руководство по стилю для программ 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
Обратите внимание, что это поведение не является уникальным для лямбда-функций, но применимо и к обычным функциям.
Какие «лучшие практики» для использования import в модуле?
В общем, не используйте from modulename import *. Это затрудняет работу интерпретатору и затрудняет для линтеров обнаружение неопределённых имён.
Импортируйте модули в верхней части файла. Это делает понятным, какие другие модули необходимы вашему коду и избегает вопросов о том, находится ли имя модуля в области видимости. Использование одного импорта на строке упрощает добавление и удаление импортов модулей, но использование нескольких импортов на строке экономит место на экране.
Хорошей практикой является импортирование модулей в следующем порядке:
- модули стандартной библиотеки — например,
sys,os,getopt,re - модули сторонних библиотек (любые, установленные в каталоге site-packages Python) — например, mx.DateTime, ZODB, PIL.Image и т. д.
- локально разработанные модули
Иногда необходимо перемещать импорты в функцию или класс, чтобы избежать проблем с циклическими импортами. Гордон МакМиллан говорит:
Циклические импорты допустимы, когда оба модуля используют форму импорта «import <module>». Они терпят неудачу, когда второй модуль хочет получить имя из первого («from module import name»), а импорт находится на верхнем уровне. Это происходит потому, что имена в первом модуле ещё недоступны, потому что первый модуль занят импортом второго.
В этом случае, если второй модуль используется только в одной функции, импорт можно легко перенести в эту функцию. К моменту вызова импорта первый модуль будет завершён с инициализацией, и второй модуль может выполнить свой импорт.
Может потребоваться перемещение импортов из верхнего уровня кода, если некоторые модули зависят от платформы. В этом случае может быть невозможно импортировать все модули в начале файла. В этом случае хорошим вариантом является импортирование соответствующих модулей в соответствующем платформозависимом коде.
Перемещайте импорты только в локальную область видимости, например, внутри определения функции, если это необходимо для решения проблемы, такой как циклический импорт, или для сокращения времени инициализации модуля. Этот приём особенно полезен, если многие импорты не нужны в зависимости от того, как выполняется программа. Вы также можете переместить импорты в функцию, если модули используются только в этой функции. Обратите внимание, что загрузка модуля в первый раз может быть дорогостоящей из-за однократной инициализации модуля, но загрузка модуля несколько раз практически бесплатна, затрачивая только несколько поисков в словаре. Даже если имя модуля вышло из области видимости, модуль, вероятно, доступен в sys.modules.
Как передать необязательные или именованные параметры из одной функции в другую?
Соберите аргументы, используя спецификаторы * и ** в списке параметров функции; это даёт вам позиционные аргументы как кортеж и именованные аргументы как словарь. Затем вы можете передать эти аргументы при вызове другой функции, используя * и **:
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.
Существуют два фактора, обуславливающие этот результат:
- Переменные — это просто имена, ссылающиеся на объекты. Выполнение
y = xне создаёт копию списка — оно создаёт новую переменнуюy, которая ссылается на тот же объект, на который ссылаетсяx. Это означает, что существует только один объект (список), и какx, так иyссылаются на него. - Списки являются изменяемыми, что означает, что вы можете изменить их содержимое.
После вызова 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 больше не равны. Это потому, что целые числа являются неизменяемыми, и когда мы делаем x = x + 1 мы не изменяем целое число 5 путём увеличения его значения; вместо этого мы создаём новый объект (целое число 6) и присваиваем его x (то есть, изменяем, на какой объект ссылается x). После этого присвоения у нас есть два объекта (целые числа 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+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(), например 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(). Для форматирования см. разделы Литералы форматированных строк и Синтаксис строк форматирования, например, "{:04d}".format(144) приводит к '0144' и "{:.3f}".format(1.0/3.0) приводит к '0.333'.
Как изменить строку на месте?
Вы не можете, потому что строки неизменяемы. В большинстве ситуаций вы должны просто создать новую строку из различных частей, из которых вы хотите ее собрать. Однако, если вам нужен объект, способный изменять данные Unicode на месте, попробуйте использовать объект 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()илиeval()для разрешения имени функции:def myFunc(): print("hello") fname = "myFunc" f = locals()[fname] f() f = eval(fname) f()Примечание: Использование
eval()медленное и небезопасно. Если у вас нет абсолютного контроля над содержимым строки, кто-то может передать строку, которая приведет к выполнению произвольной функции.
Существует ли аналог Perl’s 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», который полезен, если строка использует для разделения что-то другое, чем пробелы.
Для более сложного разбора ввода регулярные выражения более мощные, чем C’s sscanf() , и лучше подходят для этой задачи.
Что означает ошибка «UnicodeDecodeError» или «UnicodeEncodeError»?
Производительность
Моя программа слишком медленная. Как её ускорить?
Это сложный вопрос в общем случае. Сначала вот несколько пунктов, которые нужно помнить, прежде чем углубляться в тему:
- Характеристики производительности различаются в разных реализациях Python. Этот раздел FAQ посвящен CPython.
- Поведение может отличаться в зависимости от операционной системы, особенно при работе с вводом-выводом или многопоточностью.
- Вы всегда должны находить узкие места в своей программе перед попыткой оптимизировать код (см. модуль
profile). - Написание скриптов для бенчмаркинга позволит быстро находить улучшения (см. модуль
timeit). - Крайне рекомендуется обеспечить хорошее покрытие кода (через юнит-тестирование или любой другой метод) перед возможным внесением регрессий, скрытых в сложных оптимизациях.
При этом существует много трюков для ускорения кода Python. Вот несколько общих принципов, которые значительно повышают производительность:
- Ускорение алгоритмов (или смена на более быстрые) может принести гораздо большую пользу, чем попытки внедрить микрооптимизации по всему вашему коду.
- Используйте подходящие структуры данных. Изучите документацию по Встроенным типам и модулю
collections. - Если стандартная библиотека предоставляет примитив для выполнения чего-либо, скорее всего (хотя и не гарантированно), он будет быстрее любого альтернативного решения, которое вы можете придумать. Это вдвойне верно для примитивов, написанных на C, таких как встроенные функции и некоторые типы расширений. Например, обязательно используйте встроенный метод
list.sort()или связанную функциюsorted()для сортировки (и см. Руководство по сортировке для примеров умеренно продвинутого использования). - Абстракции, как правило, создают косвенные обращения и заставляют интерпретатор работать больше. Если уровень косвенности превышает объём полезной работы, ваша программа будет медленнее. Следует избегать чрезмерной абстракции, особенно в виде мелких функций или методов (которые также часто негативно сказываются на удобочитаемости).
Если вы достигли предела возможностей чистого 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):
... # do something with x ...
Это не повлияет на вашу исходную последовательность, но создаст новую копию с обратным порядком, для итерирования по которой.
Как удалить дубликаты из списка?
В Python Cookbook есть подробное обсуждение различных способов сделать это:
https://code.activestate.com/recipes/52560/
Если вас не смущает изменение порядка элементов списка, отсортируйте его, а затем просмотрите список с конца, удаляя дубликаты по ходу:
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 car — lisp_list[0], а аналог cdr — lisp_list[1]. Делайте это только если вы точно знаете, что вам это нужно, так как это обычно гораздо медленнее, чем использование списков Python.
Как создать многомерный список?
Вероятно, вы пытались создать многомерный массив так:
>>> A = [[None] * 2] * 3
Это выглядит правильно, если вы его распечатаете:
>>> A [[None, None], [None, None], [None, None]]
Но при присвоении значения оно появляется во многих местах:
>>> A[0][0] = 5 >>> A [[5, None], [5, None], [5, None]]
Причина в том, что создание списка с помощью * не создаёт копии, а только создаёт ссылки на существующие объекты. *3 создаёт список, содержащий 3 ссылки на один и тот же список длины два. Изменения в одной строке будут отображаться во всех строках, что почти наверняка не то, что вы хотите.
Предлагаемый подход заключается в создании списка нужной длины, а затем заполнении каждого элемента вновь созданным списком:
A = [None] * 3
for i in range(3):
A[i] = [None] * 2
Это создаёт список, содержащий 3 различных списка длины два. Вы также можете использовать генератор списка:
w, h = 2, 3 A = [[None] * w for i in range(h)]
Или вы можете использовать расширение, предоставляющее тип данных матрицы; NumPy — самый известный.
Как применить метод к последовательности объектов?
Используйте генератор списка:
result = [obj.method() for obj in mylist]
Почему a_tuple[i] += [‘item’] вызывает исключение, когда добавление работает?
Это связано с тем, что операторы расширенного присваивания являются операторами присваивания и с различием между изменяемыми и неизменяемыми объектами в Python.
Это обсуждение относится к случаям, когда операторы расширенного присваивания применяются к элементам кортежа, указывающим на изменяемые объекты, но мы будем использовать list и += в качестве примера.
Если вы написали:
>>> a_tuple = (1, 2) >>> a_tuple[0] += 1 Traceback (most recent call last): ... TypeError: 'tuple' object does not support item assignment
Причина исключения должна быть очевидна: 1 добавляется к объекту, на который указывает a_tuple[0] (1), создавая результирующий объект 2. Однако, когда мы пытаемся присвоить результат вычисления элементу 0 кортежа, мы получаем ошибку, так как нельзя изменить на что указывает элемент кортежа.
Внутренне, оператор расширенного присваивания делает примерно следующее:
>>> result = a_tuple[0] + 1 >>> a_tuple[0] = result Traceback (most recent call last): ... TypeError: 'tuple' object does not support item assignment
Ошибка возникает именно в части присваивания, поскольку кортеж неизменяемый.
Когда вы пишете что-то вроде:
>>> a_tuple = (['foo'], 'bar') >>> a_tuple[0] += ['item'] Traceback (most recent call last): ... TypeError: 'tuple' object does not support item assignment
Исключение немного более неожиданно, и ещё более неожиданно то, что, несмотря на ошибку, добавление в список произошло:
>>> a_tuple[0] ['foo', 'item']
Чтобы понять, почему это происходит, вам нужно знать, что (а) если объект реализует магический метод __iadd__, он вызывается при выполнении расширенного оператора присваивания +=, и его возвращаемое значение используется в операторе присваивания; и (б) для списков __iadd__ эквивалентно вызову extend для списка и возвращению этого списка. Поэтому мы говорим, что для списков += является «сокращением» для list.extend:
>>> a_list = [] >>> a_list += [1] >>> a_list [1]
Это эквивалентно:
>>> result = a_list.__iadd__([1]) >>> a_list = result
Объект, на который указывает a_list, был изменён, и указатель на изменённый объект присваивается обратно a_list. В итоге, присваивание — ничто, так как это указатель на тот же объект, на который a_list указывал ранее, но присваивание всё равно происходит.
Таким образом, в нашем примере с кортежем происходит следующее:
>>> result = a_tuple[0].__iadd__(['item']) >>> a_tuple[0] = result Traceback (most recent call last): ... TypeError: 'tuple' object does not support item assignment
__iadd__ выполняется успешно, и, следовательно, список расширяется, но, несмотря на то, что result указывает на тот же объект, на который a_tuple[0] уже указывает, это конечное присваивание всё равно приведёт к ошибке, так как кортежи неизменяемые.
Хочу выполнить сложное сортирование: можно ли выполнить преобразование Шварца в Python?
Этот приём, приписываемый Рэндалу Шварцу из сообщества Perl, сортирует элементы списка по метрике, которая отображает каждый элемент в его «значение сортировки». В Python используйте аргумент key для метода list.sort():
Isorted = L[:] Isorted.sort(key=lambda s: int(s[10:15]))
Как отсортировать один список по значениям из другого списка?
Объедините их в итератор кортежей, отсортируйте полученный список, а затем выберите нужный элемент.
>>> list1 = ["what", "I'm", "sorting", "by"]
>>> list2 = ["something", "else", "to", "sort"]
>>> pairs = zip(list1, list2)
>>> pairs = sorted(pairs)
>>> pairs
[("I'm", 'else'), ('by', 'sort'), ('sorting', 'to'), ('what', 'something')]
>>> result = [x[1] for x in pairs]
>>> result
['else', 'sort', 'to', 'something']
Альтернативный вариант для последнего шага:
>>> result = [] >>> for p in pairs: result.append(p[1])
Если вы считаете это более читабельным, вы можете предпочесть использовать это вместо финального генератора списка. Однако, это почти вдвое медленнее для длинных списков. Почему? Во-первых, операция append() должна перевыделить память, и хотя она использует некоторые хитрости, чтобы избежать этого каждый раз, она всё равно делает это время от времени, что стоит немало. Во-вторых, выражение «result.append» требует дополнительного поиска атрибута, и, в-третьих, есть снижение скорости из-за необходимости совершения всех этих вызовов функций.
Объекты
Что такое класс?
Класс — это особый тип объекта, создаваемый при выполнении оператора класса. Объекты класса используются как шаблоны для создания экземпляров, которые воплощают как данные (атрибуты), так и код (методы), специфичные для типа данных.
Класс может основываться на одном или нескольких других классах, называемых базовыми классами. Затем он наследует атрибуты и методы своих базовых классов. Это позволяет поэтапно уточнять модель объекта с помощью наследования. У вас может быть общий 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):
... # 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__; обратитесь к справочнику языка для получения дополнительной информации о контроле доступа к атрибутам.
Обратите внимание, что в более общих случаях делегирование может стать сложнее. Когда необходимо как устанавливать, так и извлекать атрибуты, класс должен также определить метод __setattr__(), и сделать это тщательно. Базовая реализация __setattr__() примерно эквивалентна следующему:
class X:
...
def __setattr__(self, name, value):
self.__dict__[name] = value
...
Большинство реализаций __setattr__() должны изменять self.__dict__ для хранения локального состояния self без создания бесконечной рекурсии.
Как вызвать метод, определённый в базовом классе, из производного класса, который его переопределяет?
Используйте встроенную функцию 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)
...
Как создать статические данные и методы класса?
В Python поддерживаются как статические данные, так и статические методы (в смысле C++ или Java).
Для статических данных просто определите атрибут класса. Чтобы присвоить новое значение атрибуту, вы должны явно использовать имя класса в присваивании:
class C:
count = 0 # number of times C.__init__ called
def __init__(self):
C.count = C.count + 1
def getcount(self):
return C.count # or return self.count
c.count также относится к C.count для любого c , для которого isinstance(c,
C) справедливо, если это не переопределено самим c или каким-либо классом в пути поиска базовых классов от c.__class__ назад к C.
Предупреждение: внутри метода C присваивание, такое как self.count = 42 , создаёт новый и не связанный экземпляр с именем «count» в собственном словаре self. Переназначение имени статических данных класса всегда должно указывать класс, как внутри метода, так и вне его:
C.count = 314
Статические методы возможны:
class C:
@staticmethod
def static(arg1, arg2, arg3):
# No 'self' parameter!
...
Однако гораздо более простой способ получить эффект статического метода — использовать простую функцию на уровне модуля:
def getcount():
return C.count
Если ваш код структурирован таким образом, что один класс (или тесно связанные иерархии классов) определяется на модуль, это обеспечит желаемую инкапсуляцию.
Как перегрузить конструкторы (или методы) в Python?
Этот ответ, фактически, применим ко всем методам, но вопрос обычно возникает в первую очередь в контексте конструкторов.
В C++ вы бы написали
class C {
C() { cout << "No arguments\n"; }
C(int i) { cout << "Argument is " << i << "\n"; }
}
В Python вам нужно написать один конструктор, который обрабатывает все случаи с использованием аргументов по умолчанию. Например:
class C:
def __init__(self, i=None):
if i is None:
print("No arguments")
else:
print("Argument is", i)
Это не совсем эквивалентно, но достаточно близко на практике.
Вы также можете попробовать использовать список аргументов переменной длины, например:
def __init__(self, *args):
...
Тот же подход работает для всех определений методов.
Я пытаюсь использовать __spam, и получаю ошибку о _SomeClassName__spam.
Имена переменных с двумя ведущими подчёркиваниями «заменяются» для обеспечения простого, но эффективного способа определения закрытых переменных класса. Любой идентификатор формы __spam (минимум два ведущих подчёркивания, максимум одно последующее подчёркивание) текстово заменяется на _classname__spam, где classname — текущее имя класса со всеми ведущими подчёркиваниями, удалёнными.
Это не гарантирует конфиденциальность: внешний пользователь по-прежнему может преднамеренно получить доступ к атрибуту «_classname__spam», и закрытые значения отображаются в словаре объекта __dict__. Многие Python-программисты вообще не утруждают себя использованием имён закрытых переменных.
Мой класс определяет __del__, но он не вызывается при удалении объекта.
Существует несколько возможных причин этому.
Оператор del не обязательно вызывает __del__() – он просто уменьшает счетчик ссылок объекта, и если он достигнет нуля, __del__() вызывается.
Если ваши структуры данных содержат циклические ссылки (например, дерево, где каждый дочерний элемент имеет ссылку на родителя, а каждый родительский элемент имеет список дочерних элементов), счетчики ссылок никогда не вернутся к нулю. Время от времени Python выполняет алгоритм обнаружения таких циклов, но сборщик мусора может запуститься некоторое время спустя после исчезновения последней ссылки на вашу структуру данных, поэтому ваш метод __del__() может быть вызван в неудобное и случайное время. Это неудобно, если вы пытаетесь воспроизвести проблему. Хуже того, порядок, в котором выполняются методы __del__() объекта, является произвольным. Вы можете запустить gc.collect(), чтобы принудительно выполнить сборку мусора, но есть патологические случаи, когда объекты никогда не будут собраны.
Несмотря на коллектор циклов, все же рекомендуется определять явный метод close() для объектов, который будет вызываться всякий раз, когда вы закончите с ними. Метод close() может затем удалить атрибуты, которые ссылаются на подобъекты. Не вызывайте __del__() напрямую – __del__() должен вызывать close(), и close() должно гарантировать, что его можно вызывать более одного раза для одного и того же объекта.
Другой способ избежать циклических ссылок — использовать модуль weakref, который позволяет указывать на объекты без увеличения их счетчика ссылок. Например, структуры данных типа «дерево» должны использовать слабые ссылки для ссылок на родительские и братские элементы (если они им нужны!).
Наконец, если метод __del__() вызывает исключение, сообщение об ошибке выводится в sys.stderr.
Как получить список всех экземпляров заданного класса?
Python не отслеживает все экземпляры класса (или встроенного типа). Вы можете запрограммировать конструктор класса на отслеживание всех экземпляров, сохраняя список слабых ссылок на каждый экземпляр.
Почему результат id() кажется не уникальным?
Встроенная функция id() возвращает целое число, которое гарантированно уникально в течение срока существования объекта. Поскольку в CPython это адрес памяти объекта, часто бывает так, что после удаления объекта из памяти, следующий только что созданный объект размещается в той же позиции в памяти. Это показано в следующем примере:
>>> id(1000) 13901272 >>> id(2000) 13901272
Два идентификатора принадлежат различным целочисленным объектам, созданным до и удаленным сразу после выполнения вызова id(). Чтобы убедиться, что объекты, идентификаторы которых вы хотите проверить, все еще существуют, создайте еще одну ссылку на объект:
>>> a = 1000; b = 2000 >>> id(a) 13901272 >>> id(b) 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 (это ничего не делает, так как модуль с именем foo уже существует)
- bar.foo_var = foo.foo_var
Последний шаг терпит неудачу, потому что Python еще не завершил интерпретацию foo и словарь глобальных символов для foo все еще пуст.
То же самое происходит, когда вы используете import foo, а затем пытаетесь получить доступ к foo.foo_var в глобальном коде.
Для решения этой проблемы есть (по крайней мере) три возможных способа.
Гвидо ван Россум рекомендует избегать всех использований from <module> import ..., и помещать весь код внутри функций. Инициализацию глобальных переменных и переменных класса следует выполнять только с использованием констант или встроенных функций. Это означает, что все из импортированного модуля ссылается как <module>.<name>.
Джим Роскинд предлагает выполнять шаги в следующем порядке в каждом модуле:
- экспорт (глобальные переменные, функции и классы, которым не нужны импортированные базовые классы)
-
importинструкции - активный код (включая глобальные переменные, которые инициализируются из импортированных значений).
van Rossum не очень любит этот подход, потому что импорты появляются в странном месте, но он работает.
Маттиас Урлихс рекомендует перестроить код таким образом, чтобы рекурсивный импорт не был необходим в первую очередь.
Эти решения не взаимоисключающие.
__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'
© 2001–2022 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.8/faq/programming.html