Часто задаваемые вопросы по программированию
-
- Существует ли отладчик на уровне исходного кода с точками останова, пошаговым выполнением и т.д.?
- Существуют ли инструменты для поиска ошибок или выполнения статического анализа?
- Как создать автономный двоичный файл из скрипта Python?
- Существуют ли стандарты кодирования или руководство по стилю для программ на Python?
-
- Почему я получаю ошибку UnboundLocalError, когда переменная имеет значение?
- Какие правила для локальных и глобальных переменных в Python?
- Почему лямбда-функции, определённые в цикле с различными значениями, все возвращают один и тот же результат?
- Как поделиться глобальными переменными между модулями?
- Какие «лучшие практики» использования import в модуле?
- Почему значения по умолчанию разделяются между объектами?
- Как передавать необязательные или именованные параметры от одной функции к другой?
- В чём разница между аргументами и параметрами?
- Почему изменение списка ‘y’ также изменило список ‘x’?
- Как написать функцию с параметрами вывода (передача по ссылке)?
- Как создать функцию высшего порядка в Python?
- Как скопировать объект в Python?
- Как найти методы или атрибуты объекта?
- Как код может определить имя объекта?
- Что с приоритетом оператора запятой?
- Существует ли эквивалент тернарного оператора C «?:»?
- Можно ли написать зашифрованные однострочные выражения на Python?
- Что означает символ слеша (/) в списке параметров функции?
-
- Как указать шестнадцатеричные и восьмеричные целые числа?
- Почему -22 // 10 возвращает -3?
- Как получить атрибут целочисленной константы вместо SyntaxError?
- Как преобразовать строку в число?
- Как преобразовать число в строку?
- Как изменить строку на месте?
- Как использовать строки для вызова функций/методов?
- Существует ли аналог Perl chomp() для удаления хвостовых символов новой строки из строк?
- Существует ли аналог scanf() или sscanf()?
- Что означает ошибка «UnicodeDecodeError» или «UnicodeEncodeError»?
- Можно ли завершить строку-сырец нечётным числом обратных слешей?
-
Последовательности (Кортежи/списки)
- Как преобразовать кортежи в списки и обратно?
- Что такое отрицательный индекс?
- Как итерировать по последовательности в обратном порядке?
- Как удалить дубликаты из списка?
- Как удалить несколько элементов из списка
- Как создать массив в Python?
- Как создать многомерный список?
- Как применить метод или функцию к последовательности объектов?
- Почему a_tuple[i] += [‘item’] вызывает исключение, когда сложение работает?
- Хочу выполнить сложное сортирование: можно ли выполнить преобразование Шварца в Python?
- Как отсортировать один список по значениям из другого списка?
-
- Что такое класс?
- Что такое метод?
- Что такое self?
- Как проверить, является ли объект экземпляром данного класса или подкласса?
- Что такое делегирование?
- Как вызвать метод, определённый в базовом классе, из производного класса, который его расширяет?
- Как организовать код, чтобы было легче изменить базовый класс?
- Как создать статические данные и статические методы класса?
- Как перегрузить конструкторы (или методы) в Python?
- При попытке использовать __spam, я получаю ошибку о _SomeClassName__spam.
- Мой класс определяет __del__, но он не вызывается при удалении объекта.
- Как получить список всех экземпляров заданного класса?
- Почему результат функции id кажется не уникальным?
- Когда я могу полагаться на проверки тождественности с оператором is?
- Как подкласс контролирует данные, хранящиеся в неизменяемом экземпляре?
- Как кэшировать вызовы методов?
Общие вопросы
Есть ли отладчик на уровне исходного кода с точками останова, пошаговым выполнением и т. д.?
Да.
Ниже описаны несколько отладчиков для Python, а встроенная функция breakpoint() позволяет вам перейти к любому из них.
Модуль pdb — это простой, но адекватный отладчик командной строки для Python. Он входит в стандартную библиотеку Python и documented in the Library
Reference Manual. Вы также можете создать свой собственный отладчик, взяв за основу код pdb.
Интерактивная среда разработки IDLE, которая входит в стандартное распространение Python (обычно доступна как Tools/scripts/idle3), включает графический отладчик.
PythonWin — это среда разработки Python, которая включает графический отладчик, основанный на pdb. Отладчик PythonWin выделяет точки останова цветом и имеет множество полезных функций, таких как отладка программ, не использующих PythonWin. PythonWin доступен как часть проекта pywin32 и как часть дистрибутива ActivePython.
Eric — это IDE, построенная на PyQt и компоненте редактирования Scintilla.
trepan3k — это отладчик, похожий на gdb.
Visual Studio Code — это среда разработки с инструментами отладки, которая интегрируется с системами управления версиями.
Существует ряд коммерческих IDE для Python, которые включают графические отладчики. К ним относятся:
Есть ли инструменты для поиска ошибок или выполнения статического анализа?
Да.
Pylint и Pyflakes выполняют базовые проверки, которые помогут вам быстрее обнаружить ошибки.
Статические анализаторы типов, такие как Mypy, Pyre и Pytype, могут проверять подсказки типов в исходном коде Python.
Как создать автономный бинарник из скрипта Python?
Вам не нужно компилировать Python в код C, если все, что вам нужно, — это автономная программа, которую пользователи могут загрузить и запустить, не устанавливая предварительно дистрибутив Python. Есть ряд инструментов, которые определяют набор модулей, необходимых программе, и объединяют эти модули с бинарником Python, чтобы создать один исполняемый файл.
Один из них — инструмент freeze, который входит в исходный код Python как Tools/freeze. Он преобразует байткод Python в массивы C; с помощью компилятора C вы можете встроить все ваши модули в новую программу, которая затем связывается со стандартными модулями Python.
Он работает, рекурсивно сканируя ваш исходный код на наличие операторов импорта (в обоих форматах) и ищет модули в стандартном пути Python, а также в каталоге исходных файлов (для встроенных модулей). Затем он преобразует байткод модулей, написанных на Python, в код C (инициализаторы массивов, которые можно преобразовать в объекты кода с помощью модуля marshal) и создает настраиваемый конфигурационный файл, содержащий только те встроенные модули, которые фактически используются в программе. Затем он компилирует сгенерированный код C и связывает его с остальной частью интерпретатора Python, образуя автономный двоичный файл, который работает точно так же, как и ваш скрипт.
Для создания консольных и графических исполняемых файлов могут помочь следующие пакеты:
- Nuitka (кроссплатформенный)
- PyInstaller (кроссплатформенный)
- PyOxidizer (кроссплатформенный)
- cx_Freeze (кроссплатформенный)
- py2app (только macOS)
- py2exe (только Windows)
Есть ли стандарты кодирования или руководство по стилю для программ Python?
Да. Стиль кодирования, используемый для модулей стандартной библиотеки, документирован как PEP 8.
Язык ядра
Почему я получаю UnboundLocalError, когда переменная имеет значение?
Удивительно получить UnboundLocalError в ранее работающем коде, когда он изменяется добавлением оператора присваивания где-то в теле функции.
Этот код:
>>> x = 10 >>> def bar(): ... print(x) ... >>> bar() 10
работает, но этот код:
>>> x = 10 >>> def foo(): ... print(x) ... x += 1
приводит к UnboundLocalError:
>>> foo() Traceback (most recent call last): ... UnboundLocalError: local variable 'x' referenced before assignment
Это происходит потому, что когда вы присваиваете значение переменной в области видимости, эта переменная становится локальной для этой области видимости и затемняет любую переменную с аналогичным именем во внешней области видимости. Поскольку в последней инструкции в foo присваивается новое значение x, компилятор распознает её как локальную переменную. Соответственно, когда предыдущая print(x) пытается вывести значение неинициализированной локальной переменной, возникает ошибка.
В приведенном выше примере вы можете получить доступ к переменной внешней области видимости, объявив её глобальной:
>>> x = 10 >>> def foobar(): ... global x ... print(x) ... x += 1 ... >>> foobar() 10
Это явное объявление необходимо, чтобы напомнить вам, что (в отличие от поверхностно аналогичной ситуации с переменными класса и экземпляра) вы фактически изменяете значение переменной во внешней области видимости:
>>> print(x) 11
Вы можете сделать то же самое во вложенной области видимости, используя ключевое слово nonlocal:
>>> def foo(): ... x = 10 ... def bar(): ... nonlocal x ... print(x) ... x += 1 ... bar() ... print(x) ... >>> foo() 10 11
Какие правила для локальных и глобальных переменных в Python?
В Python переменные, которые ссылаются только внутри функции, неявно являются глобальными. Если переменной присваивается значение где-либо в теле функции, она предполагается локальной, если явно не объявлена как глобальная.
Хотя это может показаться немного неожиданным на первый взгляд, внимательное рассмотрение объясняет это. С одной стороны, требование global для назначенных переменных предоставляет защиту от непреднамеренных побочных эффектов. С другой стороны, если global потребовалось бы для всех глобальных ссылок, вы бы постоянно использовали global. Вам нужно было бы объявлять как глобальные все ссылки на встроенную функцию или компонент импортированного модуля. Это беспорядок разрушил бы полезность объявления global для определения побочных эффектов.
Почему лямбда-функции, определенные в цикле с разными значениями, все возвращают один и тот же результат?
Предположим, вы используете цикл for для определения нескольких разных лямбда-функций (или даже обычных функций), например:
>>> squares = [] >>> for x in range(5): ... squares.append(lambda: x**2)
Это дает вам список, содержащий 5 лямбда-функций, которые вычисляют x**2. Вы можете ожидать, что при вызове они будут возвращать соответственно 0, 1, 4, 9, и 16. Однако, когда вы это действительно попробуете, вы увидите, что они все возвращают 16:
>>> squares[2]() 16 >>> squares[4]() 16
Это происходит потому, что x не является локальной для лямбда-функций, а определена во внешней области видимости, и к ней обращаются при вызове лямбды — а не при её определении. В конце цикла значение x составляет 4, поэтому все функции теперь возвращают 4**2, то есть 16. Вы также можете проверить это, изменив значение x и посмотреть, как меняются результаты лямбда-функций:
>>> x = 8 >>> squares[2]() 64
Чтобы избежать этого, вам нужно сохранить значения в переменных, локальных для лямбда-функций, так что они не полагаются на значение глобального x:
>>> squares = [] >>> for x in range(5): ... squares.append(lambda n=x: n**2)
Здесь, n=x создает новую переменную n локальную для лямбда-функции и вычисляется при определении лямбда-функции, так что она имеет то же самое значение, что и x в этот момент в цикле. Это означает, что значение n будет 0 в первой лямбда-функции, 1 во второй, 2 в третьей и так далее. Таким образом, каждая лямбда-функция теперь вернет правильный результат:
>>> squares[2]() 4 >>> squares[4]() 16
Обратите внимание, что это поведение характерно не только для лямбда-функций, но и для обычных функций.
Какие «лучшие практики» для использования import в модуле?
В общем случае не используйте from modulename import *. Это засоряет пространство имён импортера и затрудняет линтерам обнаружение неопределённых имён.
Импортируйте модули в верхней части файла. Это ясно показывает, какие другие модули требуется вашему коду, и избегает вопросов о том, находится ли имя модуля в области видимости. Использование одного импорта в строке упрощает добавление и удаление импортов модулей, но использование нескольких импортов в одной строке экономит место на экране.
Хорошей практикой является импорт модулей в следующем порядке:
- модули стандартной библиотеки — например,
sys,os,argparse,re - модули сторонних библиотек (все, установленные в каталоге site-packages Python) — например,
dateutil,requests,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, мы не изменяем int 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. Посмотрите на следующие три примера, слегка адаптированные из Ulf Bartelt:
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 — это ошибка, которая может подстерегать.
Как получить атрибут целочисленной литералы вместо SyntaxError?
Попытка получить атрибут целочисленной литералы обычным способом даёт 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'.
Как изменить строку на месте?
Вы не можете, потому что строки неизменяемы. В большинстве ситуаций вы должны просто сконструировать новую строку из различных частей, из которых вы хотите её собрать. Однако если вам нужен объект, способный изменять данные 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('w', s)
>>> print(a)
array('w', 'Hello, world')
>>> a[0] = 'y'
>>> print(a)
array('w', 'yello, world')
>>> a.tounicode()
'yello, world'
Как использовать строки для вызова функций/методов?
Существуют различные методы.
-
Лучший способ — использовать словарь, который сопоставляет строки с функциями. Основным преимуществом этого метода является то, что строки не обязательно должны совпадать с именами функций. Это также основной метод, используемый для эмуляции конструкции case:
def a(): pass def b(): pass dispatch = {'go': a, 'stop': b} # Note lack of parens for funcs dispatch[get_input()]() # Note trailing parens to call function -
Используйте встроенную функцию
getattr():import foo getattr(foo, 'bar')()
Обратите внимание, что
getattr()работает с любыми объектами, включая классы, экземпляры классов, модули и т.д.Это используется во многих местах стандартной библиотеки, например так:
class Foo: def do_foo(self): ... def do_bar(self): ... f = getattr(foo_instance, 'do_' + opname) f() -
Используйте
locals()для разрешения имени функции:def myFunc(): print("hello") fname = "myFunc" f = locals()[fname] f()
Существует ли эквивалент Perl chomp() для удаления заключительных символов новой строки из строк?
Можно использовать S.rstrip("\r\n") , чтобы удалить все вхождения любого разделителя строк с конца строки S без удаления других trailing пробелов. Если строка 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 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"
Также см. спецификацию в справочнике языка.
Производительность
Моя программа слишком медленная. Как её ускорить?
Это непростой вопрос в общем случае. Прежде всего, вот список вещей, которые следует помнить, прежде чем углубляться дальше:
- Характеристики производительности различаются в различных реализациях Python. Этот раздел часто задаваемых вопросов фокусируется на 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 также предоставляет методы для создания массивов фиксированных типов с компактными представлениями, но они медленнее индексируются, чем списки. Также обратите внимание, что NumPy и другие сторонние пакеты определяют массивоподобные структуры с различными характеристиками.
Чтобы получить списки в стиле Lisp, вы можете эмулировать ячейки cons, используя кортежи:
lisp_list = ("like", ("this", ("example", None) ) )
Если требуется изменчивость, вы можете использовать списки вместо кортежей. Здесь аналог Lisp car — lisp_list[0], а аналог cdr — lisp_list[1]. Делайте это только в том случае, если вы точно уверены, что вам это действительно нужно, потому что это обычно намного медленнее, чем использование списков Python.
Как создать многомерный список?
Вероятно, вы пытались создать многомерный массив так:
>>> A = [[None] * 2] * 3
Это выглядит правильно, если вы его распечатаете:
>>> A [[None, None], [None, None], [None, None]]
Но когда вы присваиваете значение, оно появляется в нескольких местах:
>>> A[0][0] = 5 >>> A [[5, None], [5, None], [5, None]]
Причина в том, что копирование списка с помощью * не создаёт копии, а только создаёт ссылки на существующие объекты. *3 создаёт список, содержащий 3 ссылки на один и тот же список длиной два. Изменения в одной строке будут отображаться во всех строках, что, практически наверняка, не является желаемым результатом.
Рекомендуемый подход — сначала создать список нужной длины, а затем заполнить каждый элемент вновь созданным списком:
A = [None] * 3
for i in range(3):
A[i] = [None] * 2
Это создаёт список, содержащий 3 различных списка длиной два. Вы также можете использовать список понимания:
w, h = 2, 3 A = [[None] * w for i in range(h)]
Или вы можете использовать расширение, которое предоставляет тип данных матрицы; NumPy — самый известный.
Как применить метод или функцию к последовательности объектов?
Чтобы вызвать метод или функцию и накопить возвращаемые значения в список, список понимания — элегантное решение:
result = [obj.method() for obj in mylist] 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
Исключение немного более неожиданно, и ещё более неожиданно, что, несмотря на ошибку, append сработал:
>>> a_tuple[0] ['foo', 'item']
Чтобы понять, почему это происходит, вам нужно знать, что (а) если объект реализует магический метод __iadd__(), он вызывается при выполнении оператора расширенного присваивания +=, а его возвращаемое значение используется в операторе присваивания; и (б) для списков __iadd__() эквивалентно вызову extend() для списка и возврату списка. Вот почему мы говорим, что для списков += — это «сокращённая» форма list.extend():
>>> a_list = [] >>> a_list += [1] >>> a_list [1]
Это эквивалентно:
>>> result = a_list.__iadd__([1]) >>> a_list = result
Объект, на который указывает a_list, был изменён, и указатель на изменённый объект присваивается обратно a_list. Конечный результат присваивания — ничего, так как это указатель на тот же объект, на который a_list указывал ранее, но присваивание всё же происходит.
Таким образом, в нашем примере кортежа происходит следующее:
>>> result = a_tuple[0].__iadd__(['item']) >>> a_tuple[0] = result Traceback (most recent call last): ... TypeError: 'tuple' object does not support item assignment
__iadd__() выполняется успешно, и, следовательно, список расширяется, но даже если result указывает на тот же объект, что и a_tuple[0], это последнее присваивание всё равно приводит к ошибке, так как кортежи неизменяемы.
Хочу выполнить сложное сортирование: можно ли выполнить преобразование Шварца в Python?
Этот метод, приписываемый Рэндолфу Шварцу из сообщества Perl, сортирует элементы списка по метрике, которая отображает каждый элемент в его «значение сортировки». В Python используйте аргумент key для метода list.sort():
Isorted = L[:] Isorted.sort(key=lambda s: int(s[10:15]))
Как отсортировать один список по значениям из другого списка?
Объедините их в итератор кортежей, отсортируйте получившийся список, а затем выберите нужный элемент.
>>> list1 = ["what", "I'm", "sorting", "by"]
>>> list2 = ["something", "else", "to", "sort"]
>>> pairs = zip(list1, list2)
>>> pairs = sorted(pairs)
>>> pairs
[("I'm", 'else'), ('by', 'sort'), ('sorting', 'to'), ('what', 'something')]
>>> result = [x[1] for x in pairs]
>>> result
['else', 'sort', 'to', 'something']
Объекты
Что такое класс?
Класс — это особый тип объекта, созданный при выполнении оператора класса. Объекты класса используются как шаблоны для создания экземпляров объектов, которые воплощают как данные (атрибуты), так и код (методы), специфичные для типа данных.
Класс может основываться на одном или нескольких других классах, называемых базовыми классами. Затем он наследует атрибуты и методы своих базовых классов. Это позволяет объектной модели последовательно уточняться путем наследования. У вас может быть общий 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() также проверяет виртуальное наследование от абстрактного базового класса. Таким образом, тест вернёт 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__(); обратитесь к справочнику языка для получения дополнительной информации о контроле доступа к атрибутам.
Обратите внимание, что в более общих случаях делегирование может стать сложнее. Когда необходимо установить атрибуты, а также извлекать их, класс должен также определить метод __setattr__(), и он должен сделать это тщательно. Базовая реализация __setattr__() примерно эквивалентна следующему:
class X:
...
def __setattr__(self, name, value):
self.__dict__[name] = value
...
Многие реализации __setattr__() вызывают object.__setattr__() для установки атрибута в self без возникновения бесконечной рекурсии:
class X:
def __setattr__(self, name, value):
# Custom logic here...
object.__setattr__(self, name, value)
В качестве альтернативы, можно устанавливать атрибуты, вставляя записи в self.__dict__ напрямую.
Как вызвать метод, определённый в базовом классе, из производного класса, который его расширяет?
Используйте встроенную функцию super():
class Derived(Base):
def meth(self):
super().meth() # calls Base.meth
В примере super() автоматически определит экземпляр, с которого он был вызван (значение self), найдёт порядок разрешения методов (MRO) с type(self).__mro__, и вернёт следующий элемент после Derived в MRO: Base.
Как организовать свой код, чтобы упростить изменение базового класса?
Вы можете назначить базовый класс псевдониму и наследоваться от псевдонима. Тогда вам нужно будет только изменить значение, присвоенное псевдониму. Кстати, этот трюк также полезен, если вы хотите динамически (например, в зависимости от доступности ресурсов) решить, какой базовый класс использовать. Пример:
class Base:
...
BaseAlias = Base
class Derived(BaseAlias):
...
Как создать статические данные и статические методы класса?
В Python поддерживаются как статические данные, так и статические методы (в смысле C++ или Java).
Для статических данных просто определите атрибут класса. Чтобы присвоить новое значение атрибуту, вы должны явно использовать имя класса в присваивании:
class C:
count = 0 # number of times C.__init__ called
def __init__(self):
C.count = C.count + 1
def getcount(self):
return C.count # or return self.count
c.count также относится к C.count для любого c такого, что isinstance(c,
C) выполняется, за исключением случаев переопределения самим c или каким-либо классом в пути поиска базовых классов от c.__class__ до C.
Предупреждение: в методе C присваивание, подобное self.count = 42 создаёт новый и несвязанный экземпляр, названный «count», в собственном словаре self. Переназначение имени статических данных класса должно всегда указывать на класс, как внутри метода, так и вне его:
C.count = 314
Статические методы возможны:
class C:
@staticmethod
def static(arg1, arg2, arg3):
# No 'self' parameter!
...
Однако, намного более прямой способ получения эффекта статического метода — через простую функцию на уровне модуля:
def getcount():
return C.count
Если ваш код организован так, что определяет один класс (или тесно связанную иерархию классов) на модуль, это обеспечивает желаемую инкапсуляцию.
Как перегрузить конструкторы (или методы) в Python?
Этот ответ на самом деле относится ко всем методам, но вопрос обычно возникает в первую очередь в контексте конструкторов.
В C++ вы бы написали
class C {
C() { cout << "No arguments\n"; }
C(int i) { cout << "Argument is " << i << "\n"; }
}
В Python вы должны написать один конструктор, который обрабатывает все случаи с использованием аргументов по умолчанию. Например:
class C:
def __init__(self, i=None):
if i is None:
print("No arguments")
else:
print("Argument is", i)
Это не совсем эквивалентно, но достаточно близко на практике.
Вы также можете попробовать использовать список аргументов переменной длины, например:
def __init__(self, *args):
...
Тот же подход работает для всех определений методов.
Я пытаюсь использовать __spam, и получаю ошибку про _SomeClassName__spam.
Имена переменных с двумя ведущими подчёркиваниями «искажаются», чтобы обеспечить простой, но эффективный способ определения закрытых переменных класса. Любой идентификатор вида __spam (минимум два ведущих подчёркивания, максимум одно последующее подчёркивание) текстово заменяется на _classname__spam, где classname — это текущее имя класса с удалёнными ведущими подчёркиваниями.
Идентификатор можно использовать без изменений внутри класса, но для доступа к нему вне класса необходимо использовать искажённое имя:
class A:
def __one(self):
return 1
def two(self):
return 2 * self.__one()
class B(A):
def three(self):
return 3 * self._A__one()
four = 4 * A()._A__one()
В частности, это не гарантирует конфиденциальности, поскольку внешний пользователь всё ещё может намеренно получить доступ к закрытому атрибуту; многие программисты 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
Когда я могу полагаться на тесты идентичности с оператором is?
Оператор is проверяет тождественность объектов. Тест a is b эквивалентен id(a) == id(b).
Самое важное свойство теста на тождественность состоит в том, что объект всегда тождественен самому себе, a is a всегда возвращает True. Тесты на тождественность обычно быстрее, чем тесты на равенство. И в отличие от тестов на равенство, тесты на тождественность гарантируют, что возвращается булево значение True или False.
Однако, тесты на тождественность могут быть заменены тестами на равенство только тогда, когда тождественность объекта гарантирована. Обычно существует три ситуации, где тождественность гарантируется:
1) Присваивания создают новые имена, но не изменяют тождественность объекта. После присваивания new = old, гарантируется, что new is old.
2) Добавление объекта в контейнер, хранящий ссылки на объекты, не изменяет тождественность объекта. После присваивания списка s[0] = x, гарантируется, что s[0] is x.
3) Если объект является синглетоном, это означает, что может существовать только один экземпляр этого объекта. После присваиваний a = None и b = None, гарантируется, что a is b потому что None является синглетоном.
В большинстве других случаев тесты на тождественность нежелательны, и предпочтительны тесты на равенство. В частности, тесты на тождественность не должны использоваться для проверки констант, таких как 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) Как рекомендуется в PEP 8, тест на тождественность является предпочтительным способом проверки None. Это читается как обычный английский язык в коде и избегает путаницы с другими объектами, которые могут иметь булевые значения, которые приводят к ложному утверждению.
2) Обнаружение необязательных аргументов может быть сложным, когда None является допустимым входным значением. В таких ситуациях вы можете создать объект-синглетон-сентиннель, гарантированно отличающийся от других объектов. Например, вот как реализовать метод, который ведет себя как 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) Реализации контейнеров иногда нуждаются в дополнении тестов на равенство тестами на тождественность. Это предотвращает путаницу в коде с объектами, такими как float('NaN') которые не равны сами себе.
Например, вот реализация collections.abc.Sequence.__contains__():
def __contains__(self, value):
for v in self:
if v is value or v == value:
return True
return False
Как подкласс может управлять данными, хранящимися в неизменяемом экземпляре?
При наследовании от неизменяемого типа переопределите метод __new__(), а не метод __init__(). Последний выполняется после создания экземпляра, что слишком поздно для изменения данных в неизменяемом экземпляре.
Все эти неизменяемые классы имеют другой сигнатуру, чем родительский класс:
from datetime import date
class FirstOfMonthDate(date):
"Always choose the first day of the month"
def __new__(cls, year, month, day):
return super().__new__(cls, year, month, 1)
class NamedInt(int):
"Allow text names for some numbers"
xlat = {'zero': 0, 'one': 1, 'ten': 10}
def __new__(cls, value):
value = cls.xlat.get(value, value)
return super().__new__(cls, value)
class TitleStr(str):
"Convert str to name suitable for a URL path"
def __new__(cls, s):
s = s.lower().replace(' ', '-')
s = ''.join([c for c in s if c.isalnum() or c == '-'])
return super().__new__(cls, s)
Классы можно использовать так:
>>> FirstOfMonthDate(2012, 2, 14)
FirstOfMonthDate(2012, 2, 1)
>>> NamedInt('ten')
10
>>> NamedInt(20)
20
>>> TitleStr('Blog: Why Python Rocks')
'blog-why-python-rocks'
Как кэшировать вызовы методов?
Два основных инструмента для кэширования методов — functools.cached_property() и functools.lru_cache(). Первый хранит результаты на уровне экземпляра, а второй — на уровне класса.
Подход cached_property работает только с методами, не принимающими аргументов. Он не создает ссылку на экземпляр. Результат кэшированного метода будет храниться до тех пор, пока экземпляр жив.
Преимущество состоит в том, что когда экземпляр больше не используется, кэшированный результат метода высвобождается сразу же. Недостаток заключается в том, что если накапливаются экземпляры, то накапливаются и результаты кэшированных методов. Они могут расти без ограничений.
Подход lru_cache работает с методами, которые имеют хешируемые аргументы. Он создает ссылку на экземпляр, если не предпринимаются специальные усилия для передачи слабых ссылок.
Преимущества алгоритма наименее часто используемых элементов заключаются в том, что кэш ограничен указанным maxsize. Недостатком является то, что экземпляры сохраняются до тех пор, пока они не выйдут из кэша или пока кэш не будет очищен.
Этот пример демонстрирует различные техники:
class Weather:
"Lookup weather information on a government website"
def __init__(self, station_id):
self._station_id = station_id
# The _station_id is private and immutable
def current_temperature(self):
"Latest hourly observation"
# Do not cache this because old results
# can be out of date.
@cached_property
def location(self):
"Return the longitude/latitude coordinates of the station"
# Result only depends on the station_id
@lru_cache(maxsize=20)
def historic_rainfall(self, date, units='mm'):
"Rainfall on a given date"
# Depends on the station_id, date, and units.
Вышеупомянутый пример предполагает, что station_id никогда не изменяется. Если соответствующие атрибуты экземпляра изменяемы, подход cached_property не может быть реализован, потому что он не может обнаружить изменения в атрибутах.
Чтобы подход lru_cache работал, когда 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?
Когда модуль импортируется в первый раз (или когда исходный файл изменился с момента создания текущего скомпилированного файла), файл .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) - Механизм импорта пытается прочитать
foo_varиз глобальных переменныхfoo, чтобы установитьbar.foo_var = foo.foo_var
Последний шаг терпит неудачу, потому что Python ещё не закончил интерпретацию foo и словарь глобальных символов для foo всё ещё пуст.
То же самое происходит, когда вы используете import foo, а затем пытаетесь получить доступ к foo.foo_var в глобальном коде.
Существует (по крайней мере) три возможных обходных пути для этой проблемы.
Гвидо ван Россум рекомендует избегать всех применений from <module> import ..., и помещать весь код внутри функций. Инициализация глобальных переменных и переменных класса должна использовать только константы или встроенные функции. Это означает, что всё из импортированного модуля ссылается как <module>.<name>.
Джим Роскинд предлагает выполнить шаги в следующем порядке в каждом модуле:
- экспорт (глобальные переменные, функции и классы, которым не нужны импортированные базовые классы)
-
importоператоры - активный код (включая глобальные переменные, инициализированные из импортированных значений).
Ван Россум не очень любит этот подход, потому что импорты появляются в странном месте, но он работает.
Маттиас Урлихс рекомендует перестроить код таким образом, чтобы рекурсивный импорт не был необходим в первую очередь.
Эти решения не взаимоисключающие.
__import__(‘x.y.z’) возвращает <модуль ‘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–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.13/faq/programming.html