Python 类成员懒初始化
我想知道在Python中,如何在访问类成员的时候才初始化它,也就是说,只有在用到这个成员的时候才创建它。我试过下面的代码,虽然能用,但有没有更简单的方法呢?
class MyClass(object):
_MY_DATA = None
@staticmethod
def _retrieve_my_data():
my_data = ... # costly database call
return my_data
@classmethod
def get_my_data(cls):
if cls._MY_DATA is None:
cls._MY_DATA = MyClass._retrieve_my_data()
return cls._MY_DATA
5 个回答
8
另一种让代码更简洁的方法是写一个包装函数,这个函数可以执行你想要的逻辑:
def memoize(f):
def wrapped(*args, **kwargs):
if hasattr(wrapped, '_cached_val'):
return wrapped._cached_val
result = f(*args, **kwargs)
wrapped._cached_val = result
return result
return wrapped
你可以这样使用它:
@memoize
def expensive_function():
print "Computing expensive function..."
import time
time.sleep(1)
return 400
print expensive_function()
print expensive_function()
print expensive_function()
输出结果是:
Computing expensive function...
400
400
400
现在你的类方法看起来可以是这样的,例如:
class MyClass(object):
@classmethod
@memoize
def retrieve_data(cls):
print "Computing data"
import time
time.sleep(1) #costly DB call
my_data = 40
return my_data
print MyClass.retrieve_data()
print MyClass.retrieve_data()
print MyClass.retrieve_data()
输出结果:
Computing data
40
40
40
需要注意的是,这样做只会为函数的任何一组参数缓存一个值,所以如果你想根据输入值计算不同的结果,你就需要让 memoize
变得稍微复杂一些。
23
这个回答是针对普通的实例属性/方法,而不是类属性、classmethod
或者staticmethod
。
对于Python 3.8及以上版本,可以考虑使用cached_property
装饰器,它可以缓存结果。
from functools import cached_property
class MyClass:
@cached_property
def my_lazy_attr(self):
print("Initializing and caching attribute, once per class instance.")
return 7**7**8
对于Python 3.2及以上版本,可以同时使用property
和lru_cache
装饰器,后者也可以缓存结果。
from functools import lru_cache
class MyClass:
@property
@lru_cache()
def my_lazy_attr(self):
print("Initializing and caching attribute, once per class instance.")
return 7**7**8
来源: Maxime R.的回答
46
class MyMetaClass(type):
@property
def my_data(cls):
if getattr(cls, '_MY_DATA', None) is None:
my_data = ... # costly database call
cls._MY_DATA = my_data
return cls._MY_DATA
class MyClass(metaclass=MyMetaClass):
# ...
这样一来,my_data
就成了这个类的一个属性,所以昂贵的数据库调用会被推迟,直到你尝试访问MyClass.my_data
的时候。数据库调用的结果会被缓存,存储在MyClass._MY_DATA
中,这个调用只会为这个类执行一次。
对于Python 2,你需要使用class MyClass(object):
,并在类定义的主体中添加一个__metaclass__ = MyMetaClass
属性来关联元类。
示例:
>>> class MyMetaClass(type):
... @property
... def my_data(cls):
... if getattr(cls, '_MY_DATA', None) is None:
... print("costly database call executing")
... my_data = 'bar'
... cls._MY_DATA = my_data
... return cls._MY_DATA
...
>>> class MyClass(metaclass=MyMetaClass):
... pass
...
>>> MyClass.my_data
costly database call executing
'bar'
>>> MyClass.my_data
'bar'
之所以这样有效,是因为像property
这样的数据描述符会在对象的父类型上查找;对于类来说,父类型是type
,而type
可以通过使用元类来扩展。