FAQ з програмування¶
Зміст
-
-
Чи існує налагоджувач рівня вихідного коду з точками зупинки, однокроковим режимом тощо?
Чи існують інструменти, які допомагають знайти помилки або виконати статичний аналіз?
Як я можу створити автономний двійковий файл зі сценарію Python?
Чи існують стандарти кодування чи керівництво по стилю для програм Python?
-
Чому я отримую помилку UnboundLocalError, коли змінна має значення?
Чому всі лямбда-вирази, визначені в циклі з різними значеннями, повертають однаковий результат?
Як я можу передати необов’язкові або ключові параметри з однієї функції в іншу?
Як написати функцію з вихідними параметрами (виклик за посиланням)?
-
Як перевірити, чи є об’єкт екземпляром заданого класу чи його підкласу?
How do I call a method defined in a base class from a derived class that overrides it?
Як я можу організувати свій код, щоб полегшити зміну базового класу?
Я намагаюся використовувати __спам і отримую помилку про _SomeClassName__спам.
Мій клас визначає __del__, але він не викликається, коли я видаляю об’єкт.
Коли я можу покладатися на перевірку ідентичності з оператором is?
Як підклас може контролювати, які дані зберігаються в незмінному екземплярі?
-
Загальні питання¶
Чи існує налагоджувач рівня вихідного коду з точками зупинки, однокроковим режимом тощо?¶
Так.
Нижче описано кілька налагоджувачів для 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/idle), 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, щоб сформувати самодостатній двійковий файл, який діє точно так само, як ваш сценарій.
Наступні пакети можуть допомогти зі створенням консолі та виконуваних файлів графічного інтерфейсу користувача:
Nuitka (Кросплатформенний)
PyInstaller (Cross-platform)
PyOxidizer (Кросплатформенний)
cx_Freeze (Кросплатформенний)
py2app (лише для macOS)
py2exe (Windows only)
Чи існують стандарти кодування чи керівництво по стилю для програм 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
Зауважте, що ця поведінка не властива лямбда-виразам, але також стосується звичайних функцій.
Які «найкращі практики» використання імпорту в модулі?¶
Загалом, не використовуйте from modulename import *
. Це захаращує простір імен імпортера, і значно ускладнює для лінтерів виявлення невизначених імен.
Імпорт модулів у верхній частині файлу. Це дає зрозуміти, які інші модулі вимагає ваш код, і уникає запитань про те, чи входить ім’я модуля в область видимості. Використання одного імпорту на рядок полегшує додавання та видалення імпортованих модулів, але використання кількох імпортів на рядок займає менше місця на екрані.
Доцільно імпортувати модулі в такому порядку:
standard library modules – e.g.
sys
,os
,getopt
,re
third-party library modules (anything installed in Python’s site-packages directory) – e.g. mx.DateTime, ZODB, PIL.Image, etc.
locally-developed modules
Іноді необхідно перемістити імпорт до функції чи класу, щоб уникнути проблем із циклічним імпортом. Гордон Макміллан каже:
Циклічний імпорт підходить, якщо обидва модулі використовують форму імпорту «import <module>». Вони зазнають невдачі, коли 2-й модуль хоче отримати ім’я з першого («з імені імпорту модуля»), а імпорт здійснюється на верхньому рівні. Це тому, що імена в 1-му ще недоступні, оскільки перший модуль зайнятий імпортом 2-го.
У цьому випадку, якщо другий модуль використовується лише в одній функції, імпорт можна легко перемістити в цю функцію. До моменту виклику імпорту перший модуль завершить ініціалізацію, і другий модуль зможе виконувати свій імпорт.
Також може знадобитися перемістити імпорт із верхнього рівня коду, якщо деякі модулі залежать від платформи. У цьому випадку може бути навіть неможливо імпортувати всі модулі у верхній частині файлу. У цьому випадку хорошим варіантом є імпорт правильних модулів у відповідний специфічний для платформи код.
Переміщуйте імпорт у локальну область, наприклад, у визначення функції, лише якщо це необхідно для вирішення проблеми, як-от уникнення циклічного імпорту або намагання скоротити час ініціалізації модуля. Ця техніка особливо корисна, якщо багато імпортів непотрібні залежно від того, як виконується програма. Ви також можете перемістити імпорт у функцію, якщо модулі використовуються лише в цій функції. Зауважте, що завантаження модуля вперше може бути дорогим через одноразову ініціалізацію модуля, але багаторазове завантаження модуля практично безкоштовне, коштуючи лише кількох пошуків у словнику. Навіть якщо назва модуля вийшла за межі видимості, модуль, імовірно, доступний у sys.modules
.
Як я можу передати необов’язкові або ключові параметри з однієї функції в іншу?¶
Зберіть аргументи за допомогою специфікаторів *
і **
у списку параметрів функції; це дає вам позиційні аргументи як кортеж і ключові аргументи як словник. Потім ви можете передати ці аргументи під час виклику іншої функції за допомогою *
і **
:
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 types 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
.
Є два чинники, які призводять до такого результату:
Змінні - це просто імена, які посилаються на об’єкти. Виконання
y = x
не створює копію списку — воно створює нову зміннуy
, яка посилається на той самий об’єкт, на який посилаєтьсяx
. Це означає, що є лише один об’єкт (список), і якx
, так іy
посилаються на нього.Списки 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 аргументи передаються шляхом присвоєння. Оскільки присвоєння лише створює посилання на об’єкти, немає псевдоніма між ім’ям аргументу у викликаючому та викликаному, а тому немає виклику за посиланням як такого. Бажаного ефекту можна досягти кількома способами.
Повертаючи кортеж результатів:
>>> 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[:]
Як я можу знайти методи або атрибути об’єкта?¶
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, due to 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+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 syntax error 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 Formatted string literals 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.
Продуктивність¶
Моя програма надто повільна. Як це прискорити?¶
Загалом, це важко. По-перше, ось список речей, які слід запам’ятати перед подальшим зануренням:
Характеристики продуктивності відрізняються в різних реалізаціях 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 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 to a sequence of objects?¶
Use a list comprehension:
result = [obj.method() for obj in mylist]
Чому a_tuple[i] += [„item“] викликає виключення, коли додавання працює?¶
Це пояснюється поєднанням того факту, що розширені оператори присвоєння є операторами присвоєння, а також різниці між змінними та незмінними об’єктами в Python.
Це обговорення загалом стосується випадків, коли розширені оператори присвоєння застосовуються до елементів кортежу, які вказують на змінні об’єкти, але ми будемо використовувати list
і +=
як наш приклад.
Якщо ви написали:
>>> a_tuple = (1, 2)
>>> a_tuple[0] += 1
Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment
Причина винятку має бути зрозуміла одразу: 1
додається до об’єкта a_tuple[0]
, що вказує на (1
), створюючи об’єкт результату 2
, але коли ми намагаємося призначити результат обчислення, 2
, елементу 0
кортежу, ми отримуємо помилку, оскільки ми не можемо змінити те, на що вказує елемент кортежу.
Під обкладинками цей доповнений оператор присвоєння робить приблизно наступне:
>>> result = a_tuple[0] + 1
>>> a_tuple[0] = result
Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment
Саме частина операції присвоєння створює помилку, оскільки кортеж є незмінним.
Коли ви пишете щось на зразок:
>>> a_tuple = (['foo'], 'bar')
>>> a_tuple[0] += ['item']
Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment
Виняток є трохи більш дивним, і ще більш дивним є той факт, що, незважаючи на помилку, додаток працював:
>>> a_tuple[0]
['foo', 'item']
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.
How do I call a method defined in a base class from a derived class that overrides it?¶
Використовуйте вбудовану функцію super()
:
class Derived(Base):
def meth(self):
super(Derived, self).meth()
For version prior to 3.0, you may be using classic classes: For a class
definition such as class Derived(Base): ...
you can call method meth()
defined in Base
(or one of Base
’s base classes) as Base.meth(self,
arguments...)
. Here, Base.meth
is an unbound method, so you need to
provide the self
argument.
Як я можу організувати свій код, щоб полегшити зміну базового класу?¶
Ви можете призначити базовий клас псевдоніму та отримати його від псевдоніма. Тоді все, що вам потрібно змінити, це значення, присвоєне псевдоніму. До речі, цей прийом також зручний, якщо ви хочете динамічно (наприклад, залежно від наявності ресурсів) вирішувати, який базовий клас використовувати. Приклад:
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'
Модулі¶
Як створити файл .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
Проблема полягає в тому, що перекладач виконуватиме наступні кроки:
main imports foo
Empty globals for foo are created
foo is compiled and starts executing
foo imports bar
Empty globals for bar are created
bar is compiled and starts executing
bar imports foo (which is a no-op since there already is a module named 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'