FAQ з програмування

Загальні питання

Чи існує налагоджувач рівня вихідного коду з точками зупинки, однокроковим режимом тощо?

Так.

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

Модуль pdb — це простий, але адекватний налагоджувач у консольному режимі для Python. Це частина стандартної бібліотеки Python і задокументована в Library Reference Manual. Ви також можете написати власний налагоджувач, використовуючи код для pdb як приклад.

The IDLE interactive development environment, which is part of the standard Python distribution (normally available as Tools/scripts/idle3), includes a graphical debugger.

PythonWin — це середовище розробки Python, яке містить налагоджувач графічного інтерфейсу на основі pdb. Налагоджувач PythonWin забарвлює точки зупину та має чимало цікавих функцій, таких як налагодження програм, які не належать до PythonWin. PythonWin доступний як частина проекту pywin32 і як частина дистрибутива ActivePython.

Eric is an IDE built on PyQt and the Scintilla editing component.

trepan3k є gdb-подібним налагоджувачем.

Visual Studio Code — це середовище розробки з інструментами налагодження, яке інтегрується з програмним забезпеченням для керування версіями.

Існує кілька комерційних Python IDE, які містять графічні налагоджувачі. Вони включають:

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

Так.

Pylint and Pyflakes do basic checking that will help you catch bugs sooner.

Static type checkers such as Mypy, Pyre, and Pytype can check type hints in Python source code.

Як я можу створити автономний двійковий файл зі сценарію Python?

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

One is to use the freeze tool, which is included in the Python source tree as Tools/freeze. It converts Python byte code to C arrays; with a C compiler you can embed all your modules into a new program, which is then linked with the standard Python modules.

Він працює шляхом рекурсивного сканування джерела на наявність інструкцій імпорту (в обох формах) і пошуку модулів у стандартному шляху Python, а також у вихідному каталозі (для вбудованих модулів). Потім він перетворює байт-код для модулів, написаних на Python, у код C (ініціалізатори масивів, які можна перетворити на об’єкти коду за допомогою модуля marshal) і створює спеціальний файл конфігурації, який містить лише ті вбудовані модулі, які фактично використовуються в програма. Потім він компілює згенерований код C і пов’язує його з рештою інтерпретатора Python, щоб сформувати самодостатній двійковий файл, який діє точно так само, як ваш сценарій.

Наступні пакети можуть допомогти зі створенням консолі та виконуваних файлів графічного інтерфейсу користувача:

Чи існують стандарти кодування чи керівництво по стилю для програм Python?

Так. Стиль кодування, необхідний для модулів стандартної бібліотеки, задокументовано як PEP 8.

Основна мова

Чому я отримую помилку UnboundLocalError, коли змінна має значення?

It can be a surprise to get the UnboundLocalError in previously working code when it is modified by adding an assignment statement somewhere in the body of a function.

Цей код:

>>> x = 10
>>> def bar():
...     print(x)
...
>>> bar()
10

працює, але цей код:

>>> x = 10
>>> def foo():
...     print(x)
...     x += 1

results in an UnboundLocalError:

>>> foo()
Traceback (most recent call last):
  ...
UnboundLocalError: local variable 'x' referenced before assignment

Це пояснюється тим, що коли ви робите призначення змінній в області видимості, ця змінна стає локальною для цієї області і затьмарює будь-яку змінну з подібним іменем у зовнішній області. Оскільки останній оператор у foo присвоює нове значення x, компілятор розпізнає його як локальну змінну. Отже, коли попередній print(x) намагається надрукувати неініціалізовану локальну змінну, виникає помилка.

У наведеному вище прикладі ви можете отримати доступ до зовнішньої змінної області видимості, оголосивши її глобальною:

>>> x = 10
>>> def foobar():
...     global x
...     print(x)
...     x += 1
...
>>> foobar()
10

Ця явна декларація потрібна, щоб нагадати вам, що (на відміну від зовнішньо аналогічної ситуації зі змінними класу та екземпляра) ви фактично змінюєте значення змінної у зовнішній області:

>>> print(x)
11

Ви можете зробити подібне у вкладеній області, використовуючи ключове слово nonlocal:

>>> def foo():
...    x = 10
...    def bar():
...        nonlocal x
...        print(x)
...        x += 1
...    bar()
...    print(x)
...
>>> foo()
10
11

Які правила для локальних і глобальних змінних у Python?

У Python змінні, на які посилаються лише всередині функції, є неявно глобальними. Якщо змінній присвоєно значення будь-де в тілі функції, вона вважається локальною, якщо вона явно не оголошена як глобальна.

Хоча це трохи дивно спочатку, мить міркування пояснює це. З одного боку, вимога global для призначених змінних забезпечує захист від ненавмисних побічних ефектів. З іншого боку, якби global був потрібний для всіх глобальних посилань, ви б використовували global весь час. Ви повинні оголосити як глобальне кожне посилання на вбудовану функцію або на компонент імпортованого модуля. Цей безлад перекреслив би корисність оголошення global для визначення побічних ефектів.

Чому всі лямбда-вирази, визначені в циклі з різними значеннями, повертають однаковий результат?

Припустімо, що ви використовуєте цикл for для визначення кількох різних лямбда-виразів (або навіть простих функцій), наприклад:

>>> squares = []
>>> for x in range(5):
...     squares.append(lambda: x**2)

Це дає вам список, який містить 5 лямбда-виразів, які обчислюють x**2. Можна очікувати, що під час виклику вони повертатимуть, відповідно, 0, 1, 4, 9 і 16. Однак, коли ви насправді спробуєте, ви побачите, що всі вони повертають 16:

>>> squares[2]()
16
>>> squares[4]()
16

Це відбувається тому, що x не є локальним для лямбда-вираз, а визначено у зовнішній області видимості, і доступ до нього здійснюється під час виклику лямбда-виразки — а не тоді, коли вона визначена. Наприкінці циклу значення x дорівнює 4, тому всі функції тепер повертають 4**2, тобто 16. Ви також можете перевірити це, змінивши значення x і подивившись, як змінюються результати лямбда-виражень:

>>> x = 8
>>> squares[2]()
64

Щоб уникнути цього, вам потрібно зберегти значення в змінних, локальних для лямбда-виразів, щоб вони не покладалися на значення глобального x:

>>> squares = []
>>> for x in range(5):
...     squares.append(lambda n=x: n**2)

Тут n=x створює нову змінну n, локальну для лямбда-виразу, яка обчислюється, коли лямбда-визначення визначено таким чином, щоб воно мало те саме значення, яке x мав у цій точці циклу. Це означає, що значення n буде 0 у першому лямбда, 1 у другому, 2 у третьому, і так далі. Тому кожна лямбда тепер повертатиме правильний результат:

>>> squares[2]()
4
>>> squares[4]()
16

Зауважте, що ця поведінка не властива лямбда-виразам, але також стосується звичайних функцій.

Як поділитися глобальними змінними між модулями?

Канонічний спосіб обміну інформацією між модулями в одній програмі полягає у створенні спеціального модуля (часто називається config або cfg). Просто імпортуйте модуль конфігурації в усі модулі вашої програми; потім модуль стає доступним як глобальне ім’я. Оскільки існує лише один екземпляр кожного модуля, будь-які зміни, внесені до об’єкта модуля, відображаються всюди. Наприклад:

config.py:

x = 0   # Default value of the 'x' configuration setting

mod.py:

import config
config.x = 1

main.py:

import config
import mod
print(config.x)

Note that using a module is also the basis for implementing the singleton design pattern, for the same reason.

Які «найкращі практики» використання імпорту в модулі?

Загалом, не використовуйте from modulename import *. Це захаращує простір імен імпортера, і значно ускладнює для лінтерів виявлення невизначених імен.

Імпорт модулів у верхній частині файлу. Це дає зрозуміти, які інші модулі вимагає ваш код, і уникає запитань про те, чи входить ім’я модуля в область видимості. Використання одного імпорту на рядок полегшує додавання та видалення імпортованих модулів, але використання кількох імпортів на рядок займає менше місця на екрані.

Доцільно імпортувати модулі в такому порядку:

  1. standard library modules – e.g. sys, os, argparse, re

  2. third-party library modules (anything installed in Python’s site-packages directory) – e.g. dateutil, requests, PIL.Image

  3. locally developed modules

Іноді необхідно перемістити імпорт до функції чи класу, щоб уникнути проблем із циклічним імпортом. Гордон Макміллан каже:

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

У цьому випадку, якщо другий модуль використовується лише в одній функції, імпорт можна легко перемістити в цю функцію. До моменту виклику імпорту перший модуль завершить ініціалізацію, і другий модуль зможе виконувати свій імпорт.

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

Переміщуйте імпорт у локальну область, наприклад, у визначення функції, лише якщо це необхідно для вирішення проблеми, як-от уникнення циклічного імпорту або намагання скоротити час ініціалізації модуля. Ця техніка особливо корисна, якщо багато імпортів непотрібні залежно від того, як виконується програма. Ви також можете перемістити імпорт у функцію, якщо модулі використовуються лише в цій функції. Зауважте, що завантаження модуля вперше може бути дорогим через одноразову ініціалізацію модуля, але багаторазове завантаження модуля практично безкоштовне, коштуючи лише кількох пошуків у словнику. Навіть якщо назва модуля вийшла за межі видимості, модуль, імовірно, доступний у sys.modules.

Чому значення за замовчуванням є спільними між об’єктами?

Цей тип помилок зазвичай кусає програмістів-неофітів. Розглянемо цю функцію:

def foo(mydict={}):  # Danger: shared reference to one dict for all calls
    ... compute something ...
    mydict[key] = value
    return mydict

Під час першого виклику цієї функції mydict містить один елемент. Другого разу mydict містить два елементи, тому що коли foo() починає виконуватися, mydict починає з елементом, який уже є в ньому.

Часто очікується, що виклик функції створює нові об’єкти для значень за замовчуванням. Такого не буває. Значення за замовчуванням створюються рівно один раз під час визначення функції. Якщо цей об’єкт змінено, як і словник у цьому прикладі, наступні виклики функції посилатимуться на цей змінений об’єкт.

За визначенням, незмінні об’єкти, такі як числа, рядки, кортежі та None, захищені від змін. Зміни змінних об’єктів, таких як словники, списки та екземпляри класів, можуть призвести до плутанини.

Через цю функцію хороша практика програмування не використовувати змінні об’єкти як значення за замовчуванням. Натомість використовуйте None як значення за замовчуванням і всередині функції перевірте, чи параметр None, і створіть новий список/словник/що завгодно, якщо він є. Наприклад, не пишіть:

def foo(mydict={}):
    ...

але

def foo(mydict=None):
    if mydict is None:
        mydict = {}  # create a new dict for local namespace

Ця функція може бути корисною. Якщо у вас є функція, обчислення якої потребує багато часу, поширеною технікою є кешування параметрів і результуючого значення кожного виклику функції та повернення кешованого значення, якщо те саме значення запитується знову. Це називається «запам’ятовування» і може бути реалізовано так:

# Callers can only provide two parameters and optionally pass _cache by keyword
def expensive(arg1, arg2, *, _cache={}):
    if (arg1, arg2) in _cache:
        return _cache[(arg1, arg2)]

    # Calculate the value
    result = ... expensive computation ...
    _cache[(arg1, arg2)] = result           # Store result in the cache
    return result

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

Як я можу передати необов’язкові або ключові параметри з однієї функції в іншу?

Зберіть аргументи за допомогою специфікаторів * і ** у списку параметрів функції; це дає вам позиційні аргументи як кортеж і ключові аргументи як словник. Потім ви можете передати ці аргументи під час виклику іншої функції за допомогою * і **:

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

Яка різниця між аргументами та параметрами?

Parameters are defined by the names that appear in a function definition, whereas arguments are the values actually passed to a function when calling it. Parameters define what kind of arguments a function can accept. For example, given the function definition:

def func(foo, bar=None, **kwargs):
    pass

foo, bar і kwargs є параметрами func. Однак під час виклику func, наприклад:

func(42, bar=314, extra=somevar)

значення 42, 314 і somevar є аргументами.

Чому зміна списку „y“ також змінила список „x“?

Якщо ви написали такий код:

>>> x = []
>>> y = x
>>> y.append(10)
>>> y
[10]
>>> x
[10]

можливо, вам буде цікаво, чому додавання елемента до y також змінило x.

Є два чинники, які призводять до такого результату:

  1. Змінні - це просто імена, які посилаються на об’єкти. Виконання y = x не створює копію списку — воно створює нову змінну y, яка посилається на той самий об’єкт, на який посилається x. Це означає, що є лише один об’єкт (список), і як x, так і y посилаються на нього.

  2. Списки mutable, що означає, що ви можете змінювати їхній вміст.

After the call to append(), the content of the mutable object has changed from [] to [10]. Since both the variables refer to the same object, using either name accesses the modified value [10].

Якщо натомість ми призначимо незмінний об’єкт x:

>>> x = 5  # ints are immutable
>>> y = x
>>> x = x + 1  # 5 can't be mutated, we are creating a new object here
>>> x
6
>>> y
5

ми бачимо, що в цьому випадку x і y більше не рівні. Це тому, що цілі числа immutable, і коли ми робимо x = x + 1, ми не змінюємо int 5, збільшуючи його значення; натомість ми створюємо новий об’єкт (int 6) і призначаємо його x (тобто змінюємо, на який об’єкт посилається x). Після цього призначення ми маємо два об’єкти (цілі 6 і 5) і дві змінні, які посилаються на них (x тепер посилається на 6, але y все ще посилається на 5).

Some operations (for example y.append(10) and y.sort()) mutate the object, whereas superficially similar operations (for example y = y + [10] and sorted(y)) create a new object. In general in Python (and in all cases in the standard library) a method that mutates an object will return None to help avoid getting the two types of operations confused. So if you mistakenly write y.sort() thinking it will give you a sorted copy of y, you’ll instead end up with None, which will likely cause your program to generate an easily diagnosed error.

Однак існує один клас операцій, де одна й та сама операція іноді має різну поведінку з різними типами: розширені оператори присвоєння. Наприклад, += змінює списки, але не кортежі чи цілі (a_list += [1, 2, 3] еквівалентно a_list.extend([1, 2, 3]) і змінює a_list, тоді як some_tuple += (1, 2, 3) і some_int += 1 створюють нові об’єкти).

Іншими словами:

  • Якщо у нас є змінний об’єкт (list, dict, set тощо), ми можемо використати певні операції, щоб змінити його, і всі змінні, які посилаються на нього, будуть побачити зміни.

  • Якщо у нас є незмінний об’єкт (str, int, tuple тощо), усі змінні, які посилаються на нього, завжди бачитимуть те саме значення, але операції, що перетворюють це значення в нове значення завжди повертає новий об’єкт.

Якщо ви хочете знати, чи дві змінні посилаються на той самий об’єкт, ви можете скористатися оператором is або вбудованою функцією id().

Як написати функцію з вихідними параметрами (виклик за посиланням)?

Пам’ятайте, що в Python аргументи передаються шляхом присвоєння. Оскільки присвоєння лише створює посилання на об’єкти, немає псевдоніма між ім’ям аргументу у викликаючому та викликаному, а тому немає виклику за посиланням як такого. Бажаного ефекту можна досягти кількома способами.

  1. Повертаючи кортеж результатів:

    >>> def func1(a, b):
    ...     a = 'new-value'        # a and b are local names
    ...     b = b + 1              # assigned to new objects
    ...     return a, b            # return new values
    ...
    >>> x, y = 'old-value', 99
    >>> func1(x, y)
    ('new-value', 100)
    

    Це майже завжди найясніше рішення.

  2. За допомогою глобальних змінних. Це небезпечно для потоків і не рекомендується.

  3. Передаючи змінний (змінний на місці) об’єкт:

    >>> def func2(a):
    ...     a[0] = 'new-value'     # 'a' references a mutable list
    ...     a[1] = a[1] + 1        # changes a shared object
    ...
    >>> args = ['old-value', 99]
    >>> func2(args)
    >>> args
    ['new-value', 100]
    
  4. Передаючи словник, який мутується:

    >>> def func3(args):
    ...     args['a'] = 'new-value'     # args is a mutable dictionary
    ...     args['b'] = args['b'] + 1   # change it in-place
    ...
    >>> args = {'a': 'old-value', 'b': 99}
    >>> func3(args)
    >>> args
    {'a': 'new-value', 'b': 100}
    
  5. Або об’єднайте значення в екземпляр класу:

    >>> class Namespace:
    ...     def __init__(self, /, **args):
    ...         for key, value in args.items():
    ...             setattr(self, key, value)
    ...
    >>> def func4(args):
    ...     args.a = 'new-value'        # args is a mutable Namespace
    ...     args.b = args.b + 1         # change object in-place
    ...
    >>> args = Namespace(a='old-value', b=99)
    >>> func4(args)
    >>> vars(args)
    {'a': 'new-value', 'b': 100}
    

    Майже ніколи не буває вагомих причин ускладнювати це.

Ваш найкращий вибір — повернути кортеж, що містить кілька результатів.

Як створити функцію вищого порядку в Python?

У вас є два варіанти: ви можете використовувати вкладені області або ви можете використовувати викликані об’єкти. Наприклад, припустімо, що ви хочете визначити linear(a,b), яка повертає функцію f(x), яка обчислює значення a*x+b. Використання вкладених областей:

def linear(a, b):
    def result(x):
        return a * x + b
    return result

Або за допомогою викликаного об’єкта:

class linear:

    def __init__(self, a, b):
        self.a, self.b = a, b

    def __call__(self, x):
        return self.a * x + self.b

В обох випадках

taxes = linear(0.3, 2)

дає викликуваний об’єкт, де taxes(10e6) == 0,3 * 10e6 + 2.

Підхід викликаного об’єкта має той недолік, що він трохи повільніший і призводить до трохи довшого коду. Однак зауважте, що колекція викликів може ділитися своїм підписом через успадкування:

class exponential(linear):
    # __init__ inherited
    def __call__(self, x):
        return self.a * (x ** self.b)

Об’єкт може інкапсулювати стан кількома методами:

class counter:

    value = 0

    def set(self, x):
        self.value = x

    def up(self):
        self.value = self.value + 1

    def down(self):
        self.value = self.value - 1

count = counter()
inc, dec, reset = count.up, count.down, count.set

Тут inc(), dec() і reset() діють як функції, які спільно використовують ту саму змінну підрахунку.

Як скопіювати об’єкт у Python?

Загалом, спробуйте copy.copy() або copy.deepcopy() для загального випадку. Не всі об’єкти можна скопіювати, але більшість можна.

Деякі об’єкти можна легше скопіювати. Словники мають метод copy():

newdict = olddict.copy()

Послідовності можна скопіювати шляхом нарізки:

new_l = l[:]

Як я можу знайти методи або атрибути об’єкта?

For an instance x of a user-defined class, dir(x) returns an alphabetized list of the names containing the instance attributes and methods and attributes defined by its class.

Як мій код може виявити назву об’єкта?

Загалом, не може, тому що об’єкти насправді не мають назв. По суті, присвоєння завжди прив’язує ім’я до значення; те саме стосується операторів 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>

Arguably the class has a name: even though it is bound to two names and invoked through the name B the created instance is still reported as an instance of class A. However, it is impossible to say whether the instance’s name is a or b, since both names are bound to the same value.

Взагалі кажучи, вашому коду не потрібно «знати імена» певних значень. Якщо ви не навмисне пишете інтроспективні програми, це зазвичай свідчить про те, що зміна підходу може бути корисною.

У 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?

Yes. Usually this is done by nesting lambda within lambda. See the following three examples, slightly adapted from 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

Не пробуйте цього вдома, діти!

Що означає коса риска (/) у списку параметрів функції?

A slash in the argument list of a function denotes that the parameters prior to it are positional-only. Positional-only parameters are the ones without an externally usable name. Upon calling a function that accepts positional-only parameters, arguments are mapped to parameters based solely on their position. For example, divmod() is a function that accepts positional-only parameters. Its documentation looks like this:

>>> 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 literal замість SyntaxError?

Trying to lookup an int literal attribute in the normal manner gives a SyntaxError because the period is seen as a decimal point:

>>> 1.__class__
  File "<stdin>", line 1
  1.__class__
   ^
SyntaxError: invalid decimal literal

Рішення полягає в тому, щоб відокремити літерал від крапки пробілом або дужками.

>>> 1 .__class__
<class 'int'>
>>> (1).__class__
<class 'int'>

Як перетворити рядок на число?

For integers, use the built-in int() type constructor, e.g. int('144') == 144. Similarly, float() converts to floating-point, e.g. 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“).

Як перетворити число на рядок?

To convert, e.g., the number 144 to the string '144', use the built-in type constructor str(). If you want a hexadecimal or octal representation, use the built-in functions hex() or oct(). For fancy formatting, see the f-strings and Синтаксис рядка формату sections, e.g. "{:04d}".format(144) yields '0144' and "{:.3f}".format(1.0/3.0) yields '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() для визначення імені функції:

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

Чи існує еквівалент Perl chomp() для видалення завершальних символів нового рядка з рядків?

Ви можете використовувати S.rstrip("\r\n"), щоб видалити всі входження будь-якого термінатора рядка з кінця рядка S, не видаляючи інші пробіли в кінці. Якщо рядок S представляє більше одного рядка з кількома порожніми рядками в кінці, кінцеві знаки для всіх порожніх рядків буде видалено:

>>> lines = ("line 1 \r\n"
...          "\r\n"
...          "\r\n")
>>> lines.rstrip("\n\r")
'line 1 '

Оскільки це зазвичай бажано лише під час читання тексту по одному рядку, використання S.rstrip() цей спосіб працює добре.

Чи існує еквівалент scanf() або sscanf()?

Не як такої.

For simple input parsing, the easiest approach is usually to split the line into whitespace-delimited words using the split() method of string objects and then convert decimal strings to numeric values using int() or float(). split() supports an optional «sep» parameter which is useful if the line uses something other than whitespace as a separator.

For more complicated input parsing, regular expressions are more powerful than C’s sscanf and better suited for the task.

Що означає помилка «UnicodeDecodeError» або «UnicodeEncodeError»?

Перегляньте Юнікод HOWTO.

Can I end a raw string with an odd number of backslashes?

A raw string ending with an odd number of backslashes will escape the string’s quote:

>>> r'C:\this\will\not\work\'
  File "<stdin>", line 1
    r'C:\this\will\not\work\'
         ^
SyntaxError: unterminated string literal (detected at line 1)

There are several workarounds for this. One is to use regular strings and double the backslashes:

>>> 'C:\\this\\will\\work\\'
'C:\\this\\will\\work\\'

Another is to concatenate a regular string containing an escaped backslash to the raw string:

>>> r'C:\this\will\work' '\\'
'C:\\this\\will\\work\\'

It is also possible to use os.path.join() to append a backslash on Windows:

>>> os.path.join(r'C:\this\will\work', '')
'C:\\this\\will\\work\\'

Note that while a backslash will «escape» a quote for the purposes of determining where the raw string ends, no escaping occurs when interpreting the value of the raw string. That is, the backslash remains present in the value of the raw string:

>>> r'backslash\'preserved'
"backslash\\'preserved"

Also see the specification in the language reference.

Продуктивність

Моя програма надто повільна. Як це прискорити?

Загалом, це важко. По-перше, ось список речей, які слід запам’ятати перед подальшим зануренням:

  • Характеристики продуктивності відрізняються в різних реалізаціях Python. Цей FAQ присвячений CPython.

  • Поведінка може відрізнятися в різних операційних системах, особливо коли мова йде про введення-виведення або багатопотоковість.

  • Ви завжди повинні знаходити гарячі точки у своїй програмі перед спробою оптимізувати будь-який код (дивіться модуль profile).

  • Написання тестових сценаріїв дозволить вам швидко виконувати ітерації під час пошуку покращень (дивіться модуль timeit).

  • Настійно рекомендується добре охопити код (через модульне тестування або будь-яку іншу техніку), перш ніж потенційно вводити регресії, приховані в складних оптимізаціях.

З огляду на це, існує багато прийомів, щоб пришвидшити код Python. Ось кілька загальних принципів, які допоможуть досягти прийнятного рівня продуктивності:

  • Пришвидшення ваших алгоритмів (або заміна на швидші) може дати набагато більші переваги, ніж спроба розсипати трюки мікрооптимізації по всьому коду.

  • Використовуйте правильні структури даних. Навчальна документація для модулів Вбудовані типи і collections.

  • Коли стандартна бібліотека надає примітив для виконання чогось, швидше за все (хоча це не гарантовано) буде швидше, ніж будь-яка альтернатива, яку ви можете придумати. Це подвійно вірно для примітивів, написаних на C, таких як вбудовані модулі та деякі типи розширень. Наприклад, обов’язково використовуйте або вбудований метод list.sort(), або пов’язану функцію sorted() для виконання сортування (і перегляньте Sorting HOW TO для прикладів помірно просунутого використання ).

  • Абстракції, як правило, створюють непрямість і змушують перекладача працювати більше. Якщо рівень опосередкованості переважає обсяг виконаної корисної роботи, ваша програма працюватиме повільніше. Вам слід уникати надмірної абстракції, особливо у формі крихітних функцій або методів (які також часто шкодять читабельності).

If you have reached the limit of what pure Python can allow, there are tools to take you further away. For example, Cython can compile a slightly modified version of Python code into a C extension, and can be used on many different platforms. Cython can take advantage of compilation (and optional type annotations) to make your code significantly faster than when interpreted. If you are confident in your C programming skills, you can also write a C extension module yourself.

Дивись також

Вікі-сторінка, присвячена порадам щодо продуктивності.

Який найефективніший спосіб об’єднати багато рядків?

Об’єкти str і bytes є незмінними, тому конкатенація багатьох рядків разом неефективна, оскільки кожна конкатенація створює новий об’єкт. У загальному випадку загальна вартість виконання є квадратичною щодо загальної довжини рядка.

Щоб накопичити багато об’єктів str, рекомендована ідіома полягає в тому, щоб помістити їх у список і викликати str.join() в кінці:

chunks = []
for s in my_strings:
    chunks.append(s)
result = ''.join(chunks)

(іншою досить ефективною ідіомою є використання io.StringIO)

Щоб накопичити багато об’єктів bytes, рекомендована ідіома полягає в тому, щоб розширити об’єкт bytearray за допомогою конкатенації на місці (оператор +=):

result = bytearray()
for b in my_bytes_objects:
    result += b

Послідовності (кортежі/списки)

Як конвертувати між кортежами та списками?

Конструктор типу tuple(seq) перетворює будь-яку послідовність (фактично, будь-яку ітерацію) у кортеж з тими самими елементами в тому самому порядку.

Наприклад, tuple([1, 2, 3]) дає (1, 2, 3), tuple('abc') дає ('a', 'b ', 'c'). Якщо аргумент є кортежем, він не створює копію, а повертає той самий об’єкт, тому дешево викликати tuple(), коли ви не впевнені, що об’єкт уже є кортежем.

Конструктор типу list(seq) перетворює будь-яку послідовність або ітерацію в список з тими самими елементами в тому самому порядку. Наприклад, list((1, 2, 3)) дає [1, 2, 3], а list('abc') дає ['a', 'b ', 'c']. Якщо аргумент є списком, він створює копію так само, як seq[:].

Що таке негативний індекс?

Послідовності Python індексуються позитивними та негативними числами. Для додатних чисел 0 є першим індексом, 1 є другим індексом і так далі. Для негативних індексів -1 є останнім індексом, а -2 є передостаннім (передостаннім) індексом і так далі. Подумайте про seq[-n] як про те саме, що seq[len(seq)-n].

Використання від’ємних індексів може бути дуже зручним. Наприклад, S[:-1] — це весь рядок, за винятком останнього символу, який корисний для видалення кінцевого символу нового рядка з рядка.

Як мені виконати ітерацію по послідовності у зворотному порядку?

Використовуйте вбудовану функцію reversed():

for x in reversed(sequence):
    ...  # do something with x ...

Це не торкнеться вашої оригінальної послідовності, але створить нову копію зі зворотним порядком повторення.

Як видалити дублікати зі списку?

Перегляньте кулінарну книгу Python для довгого обговорення багатьох способів зробити це:

Якщо ви не проти змінити порядок списку, відсортуйте його, а потім скануйте з кінця списку, видаляючи дублікати по ходу:

if mylist:
    mylist.sort()
    last = mylist[-1]
    for i in range(len(mylist)-2, -1, -1):
        if last == mylist[i]:
            del mylist[i]
        else:
            last = mylist[i]

Якщо всі елементи списку можна використовувати як набір ключів (тобто всі вони hashable), це часто швидше

mylist = list(set(mylist))

Це перетворює список на набір, таким чином видаляючи дублікати, а потім знову на список.

Як видалити кілька елементів зі списку

Як і у випадку з видаленням дублікатів, явне повторення у зворотному порядку з умовою видалення є однією з можливостей. Однак простіше і швидше використовувати заміну фрагмента з неявною або явною прямою ітерацією. Ось три варіації:

mylist[:] = filter(keep_function, mylist)
mylist[:] = (x for x in mylist if keep_condition)
mylist[:] = [x for x in mylist if keep_condition]

Розуміння списку може бути найшвидшим.

Як створити масив у Python?

Використовуйте список:

["this", 1, "is", "an", "array"]

За часовою складністю списки еквівалентні масивам C або Pascal; основна відмінність полягає в тому, що список Python може містити об’єкти багатьох різних типів.

The array module also provides methods for creating arrays of fixed types with compact representations, but they are slower to index than lists. Also note that NumPy and other third party packages define array-like structures with various characteristics as well.

To get Lisp-style linked lists, you can emulate cons cells using tuples:

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

If mutability is desired, you could use lists instead of tuples. Here the analogue of a Lisp car is lisp_list[0] and the analogue of cdr is lisp_list[1]. Only do this if you’re sure you really need to, because it’s usually a lot slower than using Python lists.

Як створити багатовимірний список?

Можливо, ви намагалися створити багатовимірний масив, подібний до цього:

>>> 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)]

Or, you can use an extension that provides a matrix datatype; NumPy is the best known.

How do I apply a method or function to a sequence of objects?

To call a method or function and accumulate the return values is a list, a list comprehension is an elegant solution:

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

result = [function(obj) for obj in mylist]

To just run the method or function without saving the return values, a plain for loop will suffice:

for obj in mylist:
    obj.method()

for obj in mylist:
    function(obj)

Чому a_tuple[i] += [„item“] викликає виключення, коли додавання працює?

Це пояснюється поєднанням того факту, що розширені оператори присвоєння є операторами присвоєння, а також різниці між змінними та незмінними об’єктами в Python.

Це обговорення загалом стосується випадків, коли розширені оператори присвоєння застосовуються до елементів кортежу, які вказують на змінні об’єкти, але ми будемо використовувати list і += як наш приклад.

Якщо ви написали:

>>> a_tuple = (1, 2)
>>> a_tuple[0] += 1
Traceback (most recent call last):
   ...
TypeError: 'tuple' object does not support item assignment

Причина винятку має бути зрозуміла одразу: 1 додається до об’єкта a_tuple[0], що вказує на (1), створюючи об’єкт результату 2, але коли ми намагаємося призначити результат обчислення, 2, елементу 0 кортежу, ми отримуємо помилку, оскільки ми не можемо змінити те, на що вказує елемент кортежу.

Під обкладинками цей доповнений оператор присвоєння робить приблизно наступне:

>>> result = a_tuple[0] + 1
>>> a_tuple[0] = result
Traceback (most recent call last):
  ...
TypeError: 'tuple' object does not support item assignment

Саме частина операції присвоєння створює помилку, оскільки кортеж є незмінним.

Коли ви пишете щось на зразок:

>>> a_tuple = (['foo'], 'bar')
>>> a_tuple[0] += ['item']
Traceback (most recent call last):
  ...
TypeError: 'tuple' object does not support item assignment

Виняток є трохи більш дивним, і ще більш дивним є той факт, що, незважаючи на помилку, додаток працював:

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

To see why this happens, you need to know that (a) if an object implements an __iadd__() magic method, it gets called when the += augmented assignment is executed, and its return value is what gets used in the assignment statement; and (b) for lists, __iadd__() is equivalent to calling extend() on the list and returning the list. That’s why we say that for lists, += is a «shorthand» for list.extend():

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

Це еквівалентно:

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

Об’єкт, на який вказує a_list, було змінено, і вказівник на змінений об’єкт знову призначено a_list. Кінцевим результатом призначення є no-op, оскільки це вказівник на той самий об’єкт, на який раніше вказував a_list, але призначення все одно відбувається.

Таким чином, у нашому прикладі кортежу те, що відбувається, еквівалентно:

>>> result = a_tuple[0].__iadd__(['item'])
>>> a_tuple[0] = result
Traceback (most recent call last):
  ...
TypeError: 'tuple' object does not support item assignment

The __iadd__() succeeds, and thus the list is extended, but even though result points to the same object that a_tuple[0] already points to, that final assignment still results in an error, because tuples are immutable.

Я хочу зробити складне сортування: чи можете ви виконати перетворення Шварца в 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']

Об’єкти

Що таке клас?

Клас — це окремий тип об’єкта, створений шляхом виконання оператора класу. Об’єкти класу використовуються як шаблони для створення об’єктів екземплярів, які втілюють як дані (атрибути), так і код (методи), специфічні для типу даних.

Клас може базуватися на одному або кількох інших класах, які називаються базовими класами. Потім він успадковує атрибути та методи своїх базових класів. Це дозволяє послідовно вдосконалювати об’єктну модель шляхом успадкування. У вас може бути загальний клас Поштова скринька, який надає основні методи доступу до поштової скриньки, і підкласи, такі як MboxMailbox, MaildirMailbox, OutlookMailbox, які обробляють різні конкретні формати поштових скриньок.

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

Метод — це функція для деякого об’єкта x, який зазвичай викликається як x.name(arguments...). Методи визначаються як функції всередині визначення класу:

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

Що таке себе?

Self — це просто умовна назва для першого аргументу методу. Метод, визначений як meth(self, a, b, c), слід викликати як x.meth(a, b, c) для деякого екземпляра x класу, в якому відбувається визначення; викликаний метод вважатиме, що він викликається як meth(x, a, b, c).

Дивіться також Чому «self» має використовуватися явно у визначеннях і викликах методів?.

Як перевірити, чи є об’єкт екземпляром заданого класу чи його підкласу?

Use the built-in function isinstance(obj, cls). You can check if an object is an instance of any of a number of classes by providing a tuple instead of a single class, e.g. isinstance(obj, (class1, class2, ...)), and can also check whether an object is one of Python’s built-in types, e.g. isinstance(obj, str) or isinstance(obj, (int, float, complex)).

Зауважте, що isinstance() також перевіряє віртуальне успадкування від abstract base class. Таким чином, тест поверне True для зареєстрованого класу, навіть якщо він прямо чи опосередковано не успадкував його. Щоб перевірити «справжнє успадкування», проскануйте MRO класу:

from collections.abc import Mapping

class P:
     pass

class C(P):
    pass

Mapping.register(P)
>>> c = C()
>>> isinstance(c, C)        # direct
True
>>> isinstance(c, P)        # indirect
True
>>> isinstance(c, Mapping)  # virtual
True

# Actual inheritance chain
>>> type(c).__mro__
(<class 'C'>, <class 'P'>, <class 'object'>)

# Test for "true inheritance"
>>> Mapping in type(c).__mro__
False

Зауважте, що більшість програм не дуже часто використовують isinstance() у визначених користувачем класах. Якщо ви розробляєте класи самостійно, більш правильним об’єктно-орієнтованим стилем є визначення методів у класах, які інкапсулюють конкретну поведінку, замість перевірки класу об’єкта та виконання інших дій на основі того, який це клас. Наприклад, якщо у вас є функція, яка щось робить:

def search(obj):
    if isinstance(obj, Mailbox):
        ...  # code to search a mailbox
    elif isinstance(obj, Document):
        ...  # code to search a document
    elif ...

Кращий підхід полягає в тому, щоб визначити метод search() для всіх класів і просто викликати його:

class Mailbox:
    def search(self):
        ...  # code to search a mailbox

class Document:
    def search(self):
        ...  # code to search a document

obj.search()

Що таке делегування?

Делегування — це об’єктно-орієнтована техніка (яка також називається шаблоном проектування). Припустімо, у вас є об’єкт x і ви хочете змінити поведінку лише одного з його методів. Ви можете створити новий клас, який забезпечує нову реалізацію методу, який ви хочете змінити, і делегує всі інші методи відповідному методу x.

Програмісти на Python можуть легко реалізувати делегування. Наприклад, наступний клас реалізує клас, який поводиться як файл, але перетворює всі записані дані у верхній регістр:

class UpperOut:

    def __init__(self, outfile):
        self._outfile = outfile

    def write(self, s):
        self._outfile.write(s.upper())

    def __getattr__(self, name):
        return getattr(self._outfile, name)

Here the UpperOut class redefines the write() method to convert the argument string to uppercase before calling the underlying self._outfile.write() method. All other methods are delegated to the underlying self._outfile object. The delegation is accomplished via the __getattr__() method; consult the language reference for more information about controlling attribute access.

Note that for more general cases delegation can get trickier. When attributes must be set as well as retrieved, the class must define a __setattr__() method too, and it must do so carefully. The basic implementation of __setattr__() is roughly equivalent to the following:

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

Most __setattr__() implementations must modify self.__dict__ to store local state for self without causing an infinite recursion.

Як викликати метод, визначений у базовому класі, з похідного класу, який його розширює?

Використовуйте вбудовану функцію super():

class Derived(Base):
    def meth(self):
        super().meth()  # calls Base.meth

У прикладі super() автоматично визначатиме екземпляр, з якого його було викликано (значення self), шукатиме method resolution order (MRO) за допомогою type(self) ).__mro__ і повертає наступний рядок після Derived у MRO: Base.

Як я можу організувати свій код, щоб полегшити зміну базового класу?

Ви можете призначити базовий клас псевдоніму та отримати його від псевдоніма. Тоді все, що вам потрібно змінити, це значення, присвоєне псевдоніму. До речі, цей прийом також зручний, якщо ви хочете динамічно (наприклад, залежно від наявності ресурсів) вирішувати, який базовий клас використовувати. Приклад:

class Base:
    ...

BaseAlias = Base

class Derived(BaseAlias):
    ...

Як створити статичні дані класу та статичні методи класу?

У Python підтримуються як статичні дані, так і статичні методи (у сенсі C++ або Java).

Для статичних даних просто визначте атрибут класу. Щоб призначити нове значення атрибуту, ви повинні явно використовувати ім’я класу в призначенні:

class C:
    count = 0   # number of times C.__init__ called

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

    def getcount(self):
        return C.count  # or return self.count

c.count також посилається на C.count для будь-якого c такого, що isinstance(c, C) має місце, якщо не перевизначено самим c або деякими на шляху пошуку базового класу від c.__class__ назад до C.

Застереження: у методі C призначення на кшталт self.count = 42 створює новий і непов’язаний екземпляр під назвою «count» у власному дикторі self. Повторне прив’язування назви статичних даних класу має завжди вказувати клас незалежно від того, чи знаходиться він у методі чи ні:

C.count = 314

Можливі статичні методи:

class C:
    @staticmethod
    def static(arg1, arg2, arg3):
        # No 'self' parameter!
        ...

Однак набагато більш простий спосіб отримати ефект статичного методу — це за допомогою простої функції на рівні модуля:

def getcount():
    return C.count

Якщо ваш код структурований таким чином, щоб визначити один клас (або тісно пов’язану ієрархію класів) на модуль, це забезпечує бажану інкапсуляцію.

Як я можу перевантажити конструктори (або методи) у Python?

Ця відповідь фактично стосується всіх методів, але зазвичай це питання виникає першим у контексті конструкторів.

У C++ ви б написали

class C {
    C() { cout << "No arguments\n"; }
    C(int i) { cout << "Argument is " << i << "\n"; }
}

У Python ви повинні написати єдиний конструктор, який ловить усі випадки, використовуючи аргументи за замовчуванням. Наприклад:

class C:
    def __init__(self, i=None):
        if i is None:
            print("No arguments")
        else:
            print("Argument is", i)

Це не зовсім еквівалентно, але досить близько на практиці.

Ви також можете спробувати список аргументів змінної довжини, наприклад.

def __init__(self, *args):
    ...

Той самий підхід працює для всіх визначень методів.

Я намагаюся використовувати __спам і отримую помилку про _SomeClassName__спам.

Назви змінних із подвійним підкресленням на початку «спотворені», щоб забезпечити простий, але ефективний спосіб визначення приватних змінних класу. Будь-який ідентифікатор у формі __spam (принаймні два символи підкреслення на початку, не більше одного символу підкреслення в кінці) текстово замінюється на _classname__spam, де classname є поточною назвою класу з будь-якими початковими символами підкреслення.

This doesn’t guarantee privacy: an outside user can still deliberately access the «_classname__spam» attribute, and private values are visible in the object’s __dict__. Many Python programmers never bother to use private variable names at all.

Мій клас визначає __del__, але він не викликається, коли я видаляю об’єкт.

Для цього є кілька можливих причин.

The del statement does not necessarily call __del__() – it simply decrements the object’s reference count, and if this reaches zero __del__() is called.

If your data structures contain circular links (e.g. a tree where each child has a parent reference and each parent has a list of children) the reference counts will never go back to zero. Once in a while Python runs an algorithm to detect such cycles, but the garbage collector might run some time after the last reference to your data structure vanishes, so your __del__() method may be called at an inconvenient and random time. This is inconvenient if you’re trying to reproduce a problem. Worse, the order in which object’s __del__() methods are executed is arbitrary. You can run gc.collect() to force a collection, but there are pathological cases where objects will never be collected.

Despite the cycle collector, it’s still a good idea to define an explicit close() method on objects to be called whenever you’re done with them. The close() method can then remove attributes that refer to subobjects. Don’t call __del__() directly – __del__() should call close() and close() should make sure that it can be called more than once for the same object.

Іншим способом уникнути циклічних посилань є використання модуля weakref, який дозволяє вам вказувати на об’єкти, не збільшуючи їх кількість посилань. Деревоподібні структури даних, наприклад, повинні використовувати слабкі посилання для своїх батьківських і братських посилань (якщо вони їм потрібні!).

Finally, if your __del__() method raises an exception, a warning message is printed to 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) Assignments create new names but do not change object identity. After the assignment new = old, it is guaranteed that new is old.

2) Putting an object in a container that stores object references does not change object identity. After the list assignment s[0] = x, it is guaranteed that s[0] is x.

3) If an object is a singleton, it means that only one instance of that object can exist. After the assignments a = None and b = None, it is guaranteed that a is b because None is a singleton.

У більшості інших обставин тести на ідентичність є недоцільними, а тести на рівність є кращими. Зокрема, тести ідентичності не слід використовувати для перевірки таких констант, як int і str, які не гарантовано є одиночними:

>>> a = 1000
>>> b = 500
>>> c = b + 500
>>> a is c
False

>>> a = 'Python'
>>> b = 'Py'
>>> c = b + 'thon'
>>> a is c
False

Подібним чином нові екземпляри змінних контейнерів ніколи не бувають ідентичними:

>>> a = []
>>> b = []
>>> a is b
False

У коді стандартної бібліотеки ви побачите кілька загальних шаблонів для правильного використання тестів ідентичності:

1) As recommended by PEP 8, an identity test is the preferred way to check for None. This reads like plain English in code and avoids confusion with other objects that may have boolean values that evaluate to false.

2) Detecting optional arguments can be tricky when None is a valid input value. In those situations, you can create a singleton sentinel object guaranteed to be distinct from other objects. For example, here is how to implement a method that behaves like dict.pop():

_sentinel = object()

def pop(self, key, default=_sentinel):
    if key in self:
        value = self[key]
        del self[key]
        return value
    if default is _sentinel:
        raise KeyError(key)
    return default

3) Container implementations sometimes need to augment equality tests with identity tests. This prevents the code from being confused by objects such as float('NaN') that are not equal to themselves.

For example, here is the implementation of collections.abc.Sequence.__contains__():

def __contains__(self, value):
    for v in self:
        if v is value or v == value:
            return True
    return False

Як підклас може контролювати, які дані зберігаються в незмінному екземплярі?

When subclassing an immutable type, override the __new__() method instead of the __init__() method. The latter only runs after an instance is created, which is too late to alter data in an immutable instance.

Усі ці незмінні класи мають інший підпис, ніж їх батьківський клас:

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 працює лише з методами, які не приймають жодних аргументів. Він не створює посилання на екземпляр. Кешований результат методу зберігатиметься лише доти, доки екземпляр активний.

Перевагою є те, що коли екземпляр більше не використовується, кешований результат методу буде негайно опубліковано. Недоліком є те, що якщо екземпляри накопичуються, накопичений метод теж буде результатом. Вони можуть рости необмежено.

The lru_cache approach works with methods that have hashable arguments. It creates a reference to the instance unless special efforts are made to pass in weak references.

Перевага найменш використовуваного алгоритму полягає в тому, що кеш обмежений указаним 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 не може працювати, оскільки він не може виявити зміни в атрибутах.

To make the lru_cache approach work when the station_id is mutable, the class needs to define the __eq__() and __hash__() methods so that the cache can detect relevant attribute updates:

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 як команду оболонки), для xyz буде створено .pyc, оскільки 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

Проблема полягає в тому, що перекладач виконуватиме наступні кроки:

  • основний імпорт 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__(„x.y.z“) повертає <module „x“> ; як я можу отримати z?

Замість цього можна скористатися зручною функцією import_module() з importlib:

z = importlib.import_module('x.y.z')

Коли я редагую імпортований модуль і повторно імпортую його, зміни не відображаються. чому це відбувається

З міркувань ефективності та узгодженості Python читає файл модуля лише під час першого імпорту модуля. Якби цього не було, у програмі, що складається з багатьох модулів, кожен з яких імпортує той самий базовий модуль, базовий модуль аналізувався б і повторно аналізувався багато разів. Щоб примусово перечитати змінений модуль, виконайте наступне:

import importlib
import modname
importlib.reload(modname)

Попередження: ця техніка не є на 100% надійною. Зокрема, модулі, що містять оператори типу

from modname import some_objects

продовжить працювати зі старою версією імпортованих об’єктів. Якщо модуль містить визначення класу, існуючі екземпляри класу не будуть оновлені для використання нового визначення класу. Це може призвести до наступної парадоксальної поведінки:

>>> import importlib
>>> import cls
>>> c = cls.C()                # 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'