Python - 类属性的延迟加载
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/17486104/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Python - Lazy loading of class attributes
提问by whats canasta
Class foo has a bar. Bar is not loaded until it is accessed. Further accesses to bar should incur no overhead.
foo 班有一个酒吧。Bar 在被访问之前不会被加载。进一步访问 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
Is it possible to do something like this using properties or, better yet, attributes, instead of using a getter method?
是否可以使用属性或更好的属性来做这样的事情,而不是使用 getter 方法?
The goal is to lazy load without overhead on all subsequent accesses...
目标是延迟加载而不会对所有后续访问产生开销......
采纳答案by schlamar
There are some problems with the current answers. The solution with a property requires that you specify an additional class attribute and has the overhead of checking this attribute on each look up. The solution with __getattr__
has the issue that it hides this attribute until first access. This is bad for introspection and a workaround with __dir__
is inconvenient.
目前的答案存在一些问题。带有属性的解决方案要求您指定一个额外的类属性,并且在每次查找时都会产生检查此属性的开销。解决方案__getattr__
有一个问题,它在第一次访问之前隐藏此属性。这不利于内省,并且__dir__
不方便的解决方法。
A better solution than the two proposed ones is utilizing descriptors directly. The werkzeug library has already a solution as werkzeug.utils.cached_property
. It has a simple implementation so you can directly use it without having Werkzeug as dependency:
比两个提议的更好的解决方案是直接使用描述符。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
回答by Martijn Pieters
Sure, just have your property set an instance attribute that is returned on subsequent access:
当然,只需让您的属性设置一个在后续访问时返回的实例属性:
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
The property
descriptor is a data descriptor (it implements __get__
, __set__
and __delete__
descriptor hooks), so it'll be invoked even if a bar
attribute exists on the instance, with the end result that Python ignores that attribute, hence the need to test for a separate attribute on each access.
该property
描述符是一种数据描述符(它实现__get__
,__set__
并且__delete__
描述符钩),所以它会被调用即使bar
存在于实例属性,与最终结果的Python忽略属性,因此需要测试用于在单独的属性每次访问。
You can write your own descriptor that only implements __get__
, at which point Python uses an attribute on the instance over the descriptor if it exists:
您可以编写自己的仅实现 的描述符,__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()
If you prefer a __getattr__
approach (which has something to say for it), that'd be:
如果你更喜欢一种__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)
Subsequent access will find the bar
attribute on the instance and __getattr__
won't be consulted.
后续访问将bar
在实例上找到该属性并且__getattr__
不会被查阅。
Demo:
演示:
>>> 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'}
回答by Stefano Sanfilippo
Sure it is, try:
当然可以,试试:
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
Note that this is not thread-safe. cPython has GIL, so it's a relative issue, but if you plan to use this in a true multithread Python stack (say, Jython), you might want to implement some form of lock safety.
请注意,这不是线程安全的。cPython 有 GIL,所以这是一个相对的问题,但是如果您打算在真正的多线程 Python 堆栈(例如 Jython)中使用它,您可能想要实现某种形式的锁安全。