Дескриптор для отложенной загрузки
Я хотел реализовать отложенную загрузку переменных, но, похоже, немного неправильно понимаю дескрипторы. Я хотел бы иметь объектные переменные, которые при первом доступе вызовут функцию obj.load(), которая инициализирует переменные с их реальным значением. я написал
class ToLoad(object):
def __init__(self, loader_name="load")
self.loader_name=loader_name
def __get__(self, obj, type):
if not (hasattr(obj, "_loaded") and obj._loaded):
obj._loaded=True
getattr(obj, self.loader_name)()
return None
class Test(object):
x=ToLoad()
def __init__(self, y):
self.y=y
def load(self):
print("Loading {}".format(self.y))
self.x=self.y
t1=Test(1)
t2=Test(2)
print("A", t1.x)
print("B", t2.x)
print("C", t1.x)
который не возвращает реальное значение при загрузке, по крайней мере, в первый раз. Может ли кто-нибудь предложить другой способ решения этой проблемы? Я не уверен, как вернуть правильное значение в get, так как в этот момент я не знаю, что атрибут называется "х"? Любым другим путем?
ЧЕРТ, не могу ответить на мой собственный вопрос... Итак, вот РЕДАКТИРОВАТЬ: Спасибо за вклад! Однако моя функция load () не возвращает саму переменную, поскольку она загружает много разных переменных. И я хочу минимизировать нотацию для использования отложенной загрузки. Вот я и придумал декоратор
class to_load:
def __init__(self, *vars, loader="load"):
self.vars=vars
self.loader=loader
def __call__(self, cls):
def _getattr(obj, attr):
if attr in self.vars:
getattr(obj, self.loader)()
return getattr(obj, attr)
else:
raise AttributeError
cls.__getattr__=_getattr
return cls
@to_load("a", "b")
class Test:
def load(self):
print("Loading")
self.a=1
self.b=2
t=Test()
print("Starting")
print(t.a)
print(t.b)
#print(t.c)
Это нормально? Я не уверен, что я ломаю вещи.
2 ответа
Ну, здесь есть две проблемы:
- Вы возвращаетесь
None
от__get__
тогда как это должно быть значение, которое вы хотитеx
представлять. - Ты сделаешь
x = y
, но ваш дескриптор не реализует__set__
,
Таким образом, вместо установки флага "загружен", вы должны создать атрибут с фактическим значением и проверить это. Если вы не хотите, чтобы это было только для чтения, вы должны реализовать __set__
, В противном случае вместо self.x = self.y
в load
, вернуть значение и пусть __get__
позаботиться о назначении.
class ToLoad(object):
def __init__(self, var, func):
self.var = var
self.func = func
# style note: try to avoid overshadowing built-ins (e.g. type)
def __get__(self, obj, cls):
try:
return getattr(obj, self.var)
except AttributeError:
value = getattr(obj, self.func)()
setattr(obj, self.var, value)
return value
class Foo(object):
x = ToLoad('x', '_load_x')
def __init__(self, y):
self.y = y
def _load_x(self):
print('Loading {0} into x'.format(self.y))
return self.y
a = Foo(1)
b = Foo(2)
print(a.x)
print(b.x)
print(a.x)
То, что вы хотите, вероятно, больше похоже на это:
class Test(object):
def __init__(self, y):
self.y=y
def __getattr__(self, attr):
return self.load(attr)
def load(self, attr):
print("Loading `{}`".format(attr)) # ie "Loading `x`"
# get the value for `attr` somewhere, here always self.y
val = self.y
# store it on this object to avoid reloading it
setattr(self, attr, val)
return val
t1=Test(1)
t2=Test(2)
print("A", t1.x)
print("B", t2.x)
print("C", t1.x)
Чтобы ваш код работал, вам нужно несколько return
s:
class ToLoad(object):
def __init__(self, loader_name="load"):
self.loader_name=loader_name
def __get__(self, obj, type):
if not (hasattr(obj, "_loaded") and obj._loaded):
obj._loaded=True
return getattr(obj, self.loader_name)()
return None
class Test(object):
x=ToLoad()
def __init__(self, y):
self.y=y
def load(self):
print("Loading {}".format(self.y))
self.x=self.y
return self.x
t1=Test(1)
t2=Test(2)
print("A", t1.x)
print("B", t2.x)
print("C", t1.x)
Дескрипторы знают, на каком объекте они хранятся, но они не знают, на каком атрибуте. Вы хотите перехватить доступ к атрибуту, а не изменить возвращаемое значение, поэтому вы хотите __getattr__
вместо дескрипторов.