我正试图找到一种懒惰加载模块级变量的方法。
具体来说,我写了一个很小的Python库来与iTunes交谈,我希望有一个DOWNLOAD_FOLDER_PATH
模块变量。不幸的是,iTunes不会告诉你它的下载文件夹在哪里,所以我写了一个函数来抓取几个播客曲目的文件路径并爬上目录树,直到它找到“下载”目录。
这需要一两秒钟,所以我想懒得评估它,而不是模块导入时间。
有没有办法在第一次访问时懒惰地分配模块变量,还是我必须依赖一个函数?
答案 0 :(得分:55)
你不能用模块来做,但你可以伪装一个类“好像”它是一个模块,例如itun.py
,代码......:
import sys
class _Sneaky(object):
def __init__(self):
self.download = None
@property
def DOWNLOAD_PATH(self):
if not self.download:
self.download = heavyComputations()
return self.download
def __getattr__(self, name):
return globals()[name]
# other parts of itun that you WANT to code in
# module-ish ways
sys.modules[__name__] = _Sneaky()
现在任何人都可以import itun
...并实际上获得你的itun._Sneaky()
个实例。 __getattr__
允许您访问itun.py
中的任何其他内容,您可以更方便地编码为顶级模块对象,而不是_Sneaky
内部!_)
答案 1 :(得分:12)
我在Python 3.3上使用了Alex的实现,但这很糟糕地崩溃了: 代码
def __getattr__(self, name):
return globals()[name]
不正确,因为应该引发AttributeError
,而不是KeyError
。
这在Python 3.3下立即崩溃,因为很多内省都已完成
在导入期间,查找__path__
,__loader__
等属性
这是我们现在在项目中使用的版本,允许延迟导入
在一个模块中。模块的__init__
被延迟,直到第一个属性访问
没有特殊名称:
""" config.py """
# lazy initialization of this module to avoid circular import.
# the trick is to replace this module by an instance!
# modelled after a post from Alex Martelli :-)
Lazy module variables--can it be done?
class _Sneaky(object):
def __init__(self, name):
self.module = sys.modules[name]
sys.modules[name] = self
self.initializing = True
def __getattr__(self, name):
# call module.__init__ after import introspection is done
if self.initializing and not name[:2] == '__' == name[-2:]:
self.initializing = False
__init__(self.module)
return getattr(self.module, name)
_Sneaky(__name__)
模块现在需要定义 init 功能。可以使用此功能 导入可能导入自己的模块:
def __init__(module):
...
# do something that imports config.py again
...
代码可以放入另一个模块,可以使用属性进行扩展 如上例所示。
也许这对某些人有用。
答案 2 :(得分:4)
事实证明,从Python 3.7开始,可以按照PEP 562中的规定,在模块级别定义__getattr__()
来干净地做到这一点。
# mymodule.py
from typing import Any
DOWNLOAD_FOLDER_PATH: str
def _download_folder_path() -> str:
global DOWNLOAD_FOLDER_PATH
DOWNLOAD_FOLDER_PATH = ... # compute however ...
return DOWNLOAD_FOLDER_PATH
def __getattr__(name: str) -> Any:
if name == "DOWNLOAD_FOLDER_PATH":
return _download_folder_path()
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
答案 3 :(得分:3)
有没有办法在第一次访问时懒惰地分配一个模块变量,还是我必须依赖一个函数?
我认为你说一个函数是解决你问题的最佳方法是正确的。 我将举一个简短的例子来说明。
#myfile.py - an example module with some expensive module level code.
import os
# expensive operation to crawl up in directory structure
如果处于模块级别,则会在导入时执行昂贵的操作。没有办法阻止这种情况,没有懒惰地导入整个模块!!
#myfile2.py - a module with expensive code placed inside a function.
import os
def getdownloadsfolder(curdir=None):
"""a function that will search upward from the user's current directory
to find the 'Downloads' folder."""
# expensive operation now here.
您将通过使用此方法遵循最佳做法。
答案 4 :(得分:2)
最近我遇到了同样的问题,并找到了一种方法。
class LazyObject(object):
def __init__(self):
self.initialized = False
setattr(self, 'data', None)
def init(self, *args):
#print 'initializing'
pass
def __len__(self): return len(self.data)
def __repr__(self): return repr(self.data)
def __getattribute__(self, key):
if object.__getattribute__(self, 'initialized') == False:
object.__getattribute__(self, 'init')(self)
setattr(self, 'initialized', True)
if key == 'data':
return object.__getattribute__(self, 'data')
else:
try:
return object.__getattribute__(self, 'data').__getattribute__(key)
except AttributeError:
return super(LazyObject, self).__getattribute__(key)
使用此LazyObject
,您可以为对象定义init
方法,对象将被懒惰地初始化,示例代码如下:
o = LazyObject()
def slow_init(self):
time.sleep(1) # simulate slow initialization
self.data = 'done'
o.init = slow_init
上面的o
对象与'done'
对象具有完全相同的方法,例如,您可以这样做:
# o will be initialized, then apply the `len` method
assert len(o) == 4
带有测试的完整代码(在2.7中工作)可以在这里找到:
答案 5 :(得分:2)
从Python 3.7开始(由于PEP-562),现在可以在模块级__getattr__
中实现:
在模块内部,放置类似以下内容的
:def _long_function():
# print() function to show this is called only once
print("Determining DOWNLOAD_FOLDER_PATH...")
# Determine the module-level variable
path = "/some/path/here"
# Set the global (module scope)
globals()['DOWNLOAD_FOLDER_PATH'] = path
# ... and return it
return path
def __getattr__(name):
if name == "DOWNLOAD_FOLDER_PATH":
return _long_function()
# Implicit else
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
由此可见,导入模块时_long_function()
不会执行,例如:
print("-- before import --")
import somemodule
print("-- after import --")
仅得到:
-- before import -- -- after import --
但是,当您尝试从模块中访问名称时,模块级__getattr__
将被调用,而模块级_long_function
将被调用import somemodule
print("--")
print(somemodule.DOWNLOAD_FOLDER_PATH)
print('--')
print(somemodule.DOWNLOAD_FOLDER_PATH)
print('--')
,该模块将执行长时间运行的任务,将其缓存为模块级变量,然后将结果返回给调用它的代码。
例如,在模块“ somemodule.py”中位于上面的第一个块的情况下,以下代码:
# LINE OF CODE # OUTPUT
import somemodule # (nothing)
print("--") # --
print(somemodule.DOWNLOAD_FOLDER_PATH) # Determining DOWNLOAD_FOLDER_PATH...
# /some/path/here
print("--") # --
print(somemodule.DOWNLOAD_FOLDER_PATH) # /some/path/here
print("--") # --
产生:
-- Determining DOWNLOAD_FOLDER_PATH... /some/path/here -- /some/path/here --
或更明确地说:
__dir__
最后,您还可以实现PEP所描述的DOWNLOAD_FOLDER_PATH
,如果您想表明(例如,对内省工具进行编码)#add port
sudo firewall-cmd --add-port=3001/tcp --permanent
# add service
sudo firewall-cmd --permanent --add-service=http
# reload !!! IMPORTANT !!!
sudo firewall-cmd --reload
可用。
答案 6 :(得分:0)
如果该变量存在于类而不是模块中,那么您可以重载getattr,或者更好的是,在init中填充它。
答案 7 :(得分:0)
Python文档的正确方法是子类types.ModuleType
,然后动态更新模块的__class__
。因此,这是一个关于Christian Tismer's answer的松散解决方案,但可能与之不太相似:
import sys
import types
class _Sneaky(types.ModuleType):
@property
def DOWNLOAD_FOLDER_PATH(self):
if not hasattr(self, '_download_folder_path'):
self._download_folder_path = '/dev/block/'
return self._download_folder_path
sys.modules[__name__].__class__ = _Sneaky