wbg*_*wbg 31 python variables module itunes lazy-loading
我正试图找到一种懒惰加载模块级变量的方法.
具体来说,我写了一个很小的Python库来与iTunes交谈,我想要一个DOWNLOAD_FOLDER_PATH模块变量.不幸的是,iTunes不会告诉你它的下载文件夹在哪里,所以我写了一个函数来抓取几个播客曲目的文件路径并爬上目录树,直到它找到"下载"目录.
这需要一两秒,所以我想懒得评估它,而不是模块导入时间.
有没有办法在第一次访问时懒惰地分配模块变量,还是我必须依赖一个函数?
Ale*_*lli 57
你不能用模块来做,但你可以伪装一个类"好像"它是一个模块,例如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()
Run Code Online (Sandbox Code Playgroud)
现在任何人都可以import itun......并且实际上是你的itun._Sneaky()实例.的__getattr__是有没有让你访问其他任何在itun.py可能更方便您作为一个顶级模块目标代码,比里面_Sneaky!_)
Chr*_*mer 13
我在Python 3.3上使用了Alex的实现,但这很糟糕地崩溃了:代码
def __getattr__(self, name):
return globals()[name]
Run Code Online (Sandbox Code Playgroud)
是不正确的,因为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 :-)
Run Code Online (Sandbox Code Playgroud)
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__)
Run Code Online (Sandbox Code Playgroud)
该模块现在需要定义一个init函数.此函数可用于导入可能导入自己的模块:
def __init__(module):
...
# do something that imports config.py again
...
Run Code Online (Sandbox Code Playgroud)
代码可以放入另一个模块,并且可以使用上面示例中的属性进行扩展.
也许那对某人有用.
从 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}")
Run Code Online (Sandbox Code Playgroud)
从中可以清楚地看出,_long_function()导入模块时不会执行 ,例如:
print("-- before import --")
import somemodule
print("-- after import --")
Run Code Online (Sandbox Code Playgroud)
结果只是:
-- 导入前 -- -- 导入后 --
但是当您尝试从模块访问名称时,模块级__getattr__将被调用,模块级将调用_long_function,它将执行长时间运行的任务,将其缓存为模块级变量,并将结果返回给调用它的代码。
例如,对于模块“somemodule.py”中的第一个块,以下代码:
import somemodule
print("--")
print(somemodule.DOWNLOAD_FOLDER_PATH)
print('--')
print(somemodule.DOWNLOAD_FOLDER_PATH)
print('--')
Run Code Online (Sandbox Code Playgroud)
产生:
—— 正在确定 DOWNLOAD_FOLDER_PATH... /一些/路径/这里 —— /一些/路径/这里 ——
或者,更清楚地:
# 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("--") # --
Run Code Online (Sandbox Code Playgroud)
最后,您还可以实现__dir__为PEP介绍,如果你想显示(如代码内省工具),这DOWNLOAD_FOLDER_PATH 是可用的。
根据 Python 文档,这样做的正确方法是子类化types.ModuleType,然后动态更新模块的__class__. 所以,这里有一个关于Christian Tismer 答案的粗略解决方案,但可能根本不相似:
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
Run Code Online (Sandbox Code Playgroud)
事实证明,从Python 3.7开始,可以通过__getattr__()在PEP 562中指定的模块级别定义a来完全做到这一点。
# 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}")
Run Code Online (Sandbox Code Playgroud)