Как реализовать этот ленивый шаблон загрузки? - PullRequest
0 голосов
/ 29 февраля 2020

Следующий код иллюстрирует исходную ситуацию, которую я хочу улучшить:

# expensive function execution returning a value
def f(x:int):
    print(f"f({x})")
    return 2*x

# the value is to be stored globally
gvar = f(3)

# this function uses the global variable
def g():
    doing_something_with(gvar)
    return

Прежде всего я не хочу, чтобы gvar устанавливался немедленно, но при первом использовании ( отложенная загрузка):

gvar = None

def g():
    gvar = f(3)
    doing_something_with(gvar)
    return

Но я также не хочу, чтобы gvar устанавливался при каждом его использовании:

gvar = None

def g():
    if gvar is None:
      gvar = f(3)

    doing_something_with(gvar)
    return

И, наконец, я хочу, чтобы это произошло настолько прозрачно, насколько это возможно - результат должен быть примерно таким:

gvar = magic(f, 3)

def g():
    doing_something_with(gvar)
    return

Так что я могу просто использовать gvar, как если бы это была нормальная переменная, в то время как она фактически невидимо инкапсулирует механизм, который будет лениво загружать ее значение.

Так что, если я позвоню g несколько раз:

g() # prints f(3)
g() # -
g() # -

Возможно ли это?


Чтобы дать вам дополнительный контекст. Практическим сценарием является служба FastApi, где g() будет представлять путь. Некоторые из этих функций работы с путями могут использовать одну и ту же глобальную переменную, которую следует использовать только один раз. Чтобы сделать шаблон более пригодным для повторного использования и подключения, я бы хотел, чтобы ленивая загрузка происходила прозрачно.

1 Ответ

1 голос
/ 29 февраля 2020

Вы можете использовать functools.lru_cache для «кэширования» результатов первого вызова для каждого значения:

from functools import lru_cache

import time

@lru_cache(maxsize=100)
def f(x):
    time.sleep(1)    
    return x*x

print(time.time())
for x in range(5):
    print(f(x))

print(time.time())
for x in range(5):
    print(f(x))

print(time.time())

Вывод:

1583003419.4904697 # startime
0                  # 1st calculation of 0*0
1                  # 1st calculation of 1*1
4                  # 1st calculation of 2*2
9                  # 1st calculation of 3*3
16                 # 1st calculation of 4*4
1583003424.496077  # takes 5'1 seconds due to the sleep(1) in the function (5*1s)
0                  # 2nd time 0*0 taken from cache
1                  # 2nd time 1*1 taken from cache
4                  # 2nd time 2*2 taken from cache
9                  # 2nd time 3*3 taken from cache
16                 # 2nd time 4*4 taken from cache
1583003424.496102 # takes almost no time, due results already in cache and taken from it 

As для ленивых: до тех пор, пока вы не вызываете функцию, она ленива, не понимайте, о чем вы.

...