Class foo有一个酒吧.条形码在访问之前不会加载.进一步访问bar应该不会产生任何开销.
class Foo(object):
def get_bar(self):
print "initializing"
self.bar = "12345"
self.get_bar = self._get_bar
return self.bar
def _get_bar(self):
print "accessing"
return self.bar
Run Code Online (Sandbox Code Playgroud)
是否可以使用属性或更好的属性来执行此类操作,而不是使用getter方法?
目标是在所有后续访问中没有开销的延迟加载......
sch*_*mar 13
目前的答案存在一些问题.具有属性的解决方案要求您指定其他类属性,并且具有在每次查找时检查此属性的开销.解决方案__getattr__的问题是它在第一次访问之前隐藏了此属性.这对于内省是不利的,并且解决方法__dir__是不方便的.
比提出的两个更好的解决方案是直接使用描述符.werkzeug库已经有了解决方案werkzeug.utils.cached_property.它有一个简单的实现,所以你可以直接使用它而不需要Werkzeug作为依赖:
_missing = object()
class cached_property(object):
"""A decorator that converts a function into a lazy property. The
function wrapped is called the first time to retrieve the result
and then that calculated result is used the next time you access
the value::
class Foo(object):
@cached_property
def foo(self):
# calculate something important here
return 42
The class has to have a `__dict__` in order for this property to
work.
"""
# implementation detail: this property is implemented as non-data
# descriptor. non-data descriptors are only invoked if there is
# no entry with the same name in the instance's __dict__.
# this allows us to completely get rid of the access function call
# overhead. If one choses to invoke __get__ by hand the property
# will still work as expected because the lookup logic is replicated
# in __get__ for manual invocation.
def __init__(self, func, name=None, doc=None):
self.__name__ = name or func.__name__
self.__module__ = func.__module__
self.__doc__ = doc or func.__doc__
self.func = func
def __get__(self, obj, type=None):
if obj is None:
return self
value = obj.__dict__.get(self.__name__, _missing)
if value is _missing:
value = self.func(obj)
obj.__dict__[self.__name__] = value
return value
Run Code Online (Sandbox Code Playgroud)
Mar*_*ers 11
当然,只需让您的属性设置一个在后续访问时返回的实例属性:
class Foo(object):
_cached_bar = None
@property
def bar(self):
if not self._cached_bar:
self._cached_bar = self._get_expensive_bar_expression()
return self._cached_bar
Run Code Online (Sandbox Code Playgroud)
该property描述符是一种数据描述符(它实现__get__,__set__并且__delete__描述符钩),所以它会被调用即使bar存在于实例属性,与最终结果的Python忽略属性,因此需要以测试在单独的属性每次访问.
您可以编写自己只实现的描述符,__get__此时Python在描述符上使用实例上的属性(如果存在):
class CachedProperty(object):
def __init__(self, func, name=None):
self.func = func
self.name = name if name is not None else func.__name__
self.__doc__ = func.__doc__
def __get__(self, instance, class_):
if instance is None:
return self
res = self.func(instance)
setattr(instance, self.name, res)
return res
class Foo(object):
@CachedProperty
def bar(self):
return self._get_expensive_bar_expression()
Run Code Online (Sandbox Code Playgroud)
如果您更喜欢某种__getattr__方法(对此有所帮助),那就是:
class Foo(object):
def __getattr__(self, name):
if name == 'bar':
bar = self.bar = self._get_expensive_bar_expression()
return bar
return super(Foo, self).__getattr__(name)
Run Code Online (Sandbox Code Playgroud)
后续访问将bar在实例上找到该属性,__getattr__不会被查阅.
演示:
>>> class FooExpensive(object):
... def _get_expensive_bar_expression(self):
... print 'Doing something expensive'
... return 'Spam ham & eggs'
...
>>> class FooProperty(FooExpensive):
... _cached_bar = None
... @property
... def bar(self):
... if not self._cached_bar:
... self._cached_bar = self._get_expensive_bar_expression()
... return self._cached_bar
...
>>> f = FooProperty()
>>> f.bar
Doing something expensive
'Spam ham & eggs'
>>> f.bar
'Spam ham & eggs'
>>> vars(f)
{'_cached_bar': 'Spam ham & eggs'}
>>> class FooDescriptor(FooExpensive):
... bar = CachedProperty(FooExpensive._get_expensive_bar_expression, 'bar')
...
>>> f = FooDescriptor()
>>> f.bar
Doing something expensive
'Spam ham & eggs'
>>> f.bar
'Spam ham & eggs'
>>> vars(f)
{'bar': 'Spam ham & eggs'}
>>> class FooGetAttr(FooExpensive):
... def __getattr__(self, name):
... if name == 'bar':
... bar = self.bar = self._get_expensive_bar_expression()
... return bar
... return super(Foo, self).__getatt__(name)
...
>>> f = FooGetAttr()
>>> f.bar
Doing something expensive
'Spam ham & eggs'
>>> f.bar
'Spam ham & eggs'
>>> vars(f)
{'bar': 'Spam ham & eggs'}
Run Code Online (Sandbox Code Playgroud)
当然可以,试试:
class Foo(object):
def __init__(self):
self._bar = None # Initial value
@property
def bar(self):
if self._bar is None:
self._bar = HeavyObject()
return self._bar
Run Code Online (Sandbox Code Playgroud)
请注意,这不是线程安全的。cPython 有 GIL,所以这是一个相对的问题,但是如果您打算在真正的多线程 Python 堆栈(例如 Jython)中使用它,您可能想要实现某种形式的锁安全。
| 归档时间: |
|
| 查看次数: |
18154 次 |
| 最近记录: |