Поиск в пространстве имен
Словарь является отличной структурой данных для поиска переменных в пространстве имен, однако это действие может замедлить время работы программы.
Как работает поиск в пространстве имен
Пусть мы обращаемся к переменной X.
- Сначала смотрим, есть ли
X
в словареlocals()
, который хранит все локальные переменные. - Если там не оказалось
X
, смотрим вglobals()
, который хранит глобальные переменные. - Если и там не оказалось, тогда исследуется
__builtins__
. Технически это модуль, поэтому поиск идет по его пространству имен. Например, функциюall
можно получить так:getattr(__builtins__, 'all')
- Если ничего не найдено, вызывается
NameError
.
Оптимизация поиска
Посмотрим на примере, как вышеуказанная процедура влияет на время работы программы:
import math
from math import sin
def test1(x):
res = 1
for _ in range(1_000):
res += math.sin(x)
return res
def test2(x):
res = 1
for _ in range(1_000):
res += sin(x)
return res
def test3(x, sin=math.sin):
res = 1
for _ in range(1_000):
res += sin(x)
return res
def test4(x, sin=sin):
res = 1
for _ in range(1_000):
res += sin(x)
return res
Замерим время работы:
In [2]: %timeit test1(num)
90.7 µs ± 252 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
In [3]: %timeit test2(num)
70.2 µs ± 717 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
In [4]: %timeit test3(num)
68.1 µs ± 159 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
In [5]: %timeit test4(num)
67.8 µs ± 126 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
Посмотрим байт-код:
>>> dis.dis(test1)
...
18 LOAD_GLOBAL 1 (math)
20 LOAD_METHOD 2 (sin)
22 LOAD_FAST 0 (x)
24 CALL_METHOD 1
...
>>> dis.dis(test2)
...
18 LOAD_GLOBAL 1 (sin)
20 LOAD_FAST 0 (x)
22 CALL_FUNCTION 1
...
>>> dis.dis(test3)
...
18 LOAD_FAST 1 (sin)
20 LOAD_FAST 0 (x)
22 CALL_FUNCTION 1
...
>>> dis.dis(test4)
...
18 LOAD_FAST 1 (sin)
20 LOAD_FAST 0 (x)
22 CALL_FUNCTION 1
...
Как делается поиск sin
в каждой функции:
test1:
- math in locals()
- math in globals()
- sin in math
test2:
- sin in locals()
- sin in globals()
test3:
При объявлении функции:
- math in locals()
- math in globals()
- sin in math
При вызове функции:
- sin in locals()
test4:
При объявлении функции:
- sin in locals()
- sin in globals()
При вызове функции:
- sin in locals()
Выводы
Третья и четвертая функции выглядят совсем не в Python-стиле, однако данный трюк поможет сэкономить микросекунды. Это может быть полезно, если какая-то функция вызывается очень часто.
Также импорт нужных методов из модуля сэкономит немного времени на поиске внутри пространства имен данного модуля, поэтому для повышения производительности лучше использовать from ... import ...
.