如何使用现代 Python 重定向模块导入?
How can I redirect module imports with modern Python?
我正在维护一个 python 包,我在其中进行了一些重组。现在,我想支持仍然使用 from my_package.old_subpackage.foo import Foo
而不是新的 from my_package.new_subpackage.foo import Foo
的客户,而无需明确重新引入许多进行转发的文件。 (old_subpackage
仍然存在,但不再包含 foo.py
。)
我知道有“加载器”和“查找器”,我的印象是我应该为我的目的实现一个 loader,但我只实现了一个查找器 目前为止:
RENAMED_PACKAGES = {
'my_package.old_subpackage.foo': 'my_package.new_subpackage.foo',
}
# TODO: ideally, we would not just implement a "finder", but also a "loader"
# (using the importlib.util.module_for_loader decorator); this would enable us
# to get module contents that also pass identity checks
class RenamedFinder:
@classmethod
def find_spec(cls, fullname, path, target=None):
renamed = RENAMED_PACKAGES.get(fullname)
if renamed is not None:
sys.stderr.write(
f'WARNING: {fullname} was renamed to {renamed}; please adapt import accordingly!\n')
return importlib.util.find_spec(renamed)
return None
sys.meta_path.append(RenamedFinder())
然而,https://docs.python.org/3.5/library/importlib.html#importlib.util.module_for_loader 和相关功能似乎已被弃用。我知道这不是我想要实现的非常 pythonic 的事情,但我很高兴得知它是可以实现的。
在旧模块的初始化文件中,从较新的模块导入它
旧 (package.oldpkg):
foo = __import__("Path to new module")
新(package.newpkg):
class foo:
bar = "thing"
所以
package.oldpkg.foo.bar 与 package.newpkg.foo.bar
相同
希望对您有所帮助!
将所有旧包名称合并为my_package
。
旧包(old_package):
- image_processing (class) 将被删除并替换为 better_image_processing
- text_recognition (class) 将被删除并替换为 better_text_recognition
- foo (变量) 会被移动到 better_text_recognition
- still_there(class)不会动
新包:
- super_image_processing
- 更好_text_recognition
重定向器(my_package 的 class):
class old_package:
image_processing = super_image_processing # Will be replaced
text_recognition = better_text_recognition # Will be replaced
你的主要新模块(my_package):
#imports here
class super_image_processing:
def its(gets,even,better):
pass
class better_text_recognition:
def now(better,than,ever):
pass
class old_package:
#Links
image_processing = super_image_processing
text_recognition = better_text_recognition
still_there = __import__("path to unchanged module")
这允许您删除一些 文件 并保留其余的。如果你想重定向变量你会这样做:
class super_image_processing:
def its(gets,even,better):
pass
class better_text_recognition:
def now(better,than,ever):
pass
class old_package:
#Links
image_processing = super_image_processing
text_recognition = better_text_recognition
foo = text_recognition.foo
still_there = __import__("path to unchanged module")
这行得通吗?
在导入包的 __init__.py
时,您可以将任何您想要的对象放入 sys.modules
,您放入其中的值将通过 import
语句返回:
from . import new_package
from .new_package import module1, module2
import sys
sys.modules["my_lib.old_package"] = new_package
sys.modules["my_lib.old_package.module1"] = module1
sys.modules["my_lib.old_package.module2"] = module2
如果有人现在使用 import my_lib.old_package
或 import my_lib.old_package.module1
,他们将获得对 my_lib.new_package.module1
的引用。由于导入机器已经在 sys.modules
字典中找到了键,它甚至从未开始寻找旧文件。
如果您想避免立即导入所有子模块,可以通过在 sys.modules
:
中放置一个带有 __getattr__
的模块来模拟延迟加载
from types import ModuleType
import importlib
import sys
class LazyModule(ModuleType):
def __init__(self, name, mod_name):
super().__init__(name)
self.__mod_name = name
def __getattr__(self, attr):
if "_lazy_module" not in self.__dict__:
self._lazy_module = importlib.import(self.__mod_name, package="my_lib")
return self._lazy_module.__getattr__(attr)
sys.modules["my_lib.old_package"] = LazyModule("my_lib.old_package", "my_lib.new_package")
我认为这就是您要找的:
RENAMED_PACKAGES = {
'my_package.old_subpackage.foo': 'my_package.new_subpackage.foo',
}
class RenamedFinder:
@classmethod
def find_spec(cls, fullname, path, target=None):
renamed = RENAMED_PACKAGES.get(fullname)
if renamed is not None:
sys.stderr.write(
f'WARNING: {fullname} was renamed to {renamed}; please adapt import accordingly!\n')
spec = importlib.util.find_spec(renamed)
spec.loader = cls
return spec
return None
@staticmethod
def create_module(spec):
return importlib.import_module(spec.name)
@staticmethod
def exec_module(module):
pass
sys.meta_path.append(RenamedFinder())
不过,IMO 更可取,因为它更具可读性、更明确,并为您提供更多控制。当 my_package.new_subpackage.foo
开始偏离 my_package.old_subpackage.foo
而您仍然需要提供旧版本以实现向后兼容性时,它可能会变得很有用,尤其是在您的软件包的其他版本中。出于这个原因,您可能需要保留两者的代码。
我正在维护一个 python 包,我在其中进行了一些重组。现在,我想支持仍然使用 from my_package.old_subpackage.foo import Foo
而不是新的 from my_package.new_subpackage.foo import Foo
的客户,而无需明确重新引入许多进行转发的文件。 (old_subpackage
仍然存在,但不再包含 foo.py
。)
我知道有“加载器”和“查找器”,我的印象是我应该为我的目的实现一个 loader,但我只实现了一个查找器 目前为止:
RENAMED_PACKAGES = {
'my_package.old_subpackage.foo': 'my_package.new_subpackage.foo',
}
# TODO: ideally, we would not just implement a "finder", but also a "loader"
# (using the importlib.util.module_for_loader decorator); this would enable us
# to get module contents that also pass identity checks
class RenamedFinder:
@classmethod
def find_spec(cls, fullname, path, target=None):
renamed = RENAMED_PACKAGES.get(fullname)
if renamed is not None:
sys.stderr.write(
f'WARNING: {fullname} was renamed to {renamed}; please adapt import accordingly!\n')
return importlib.util.find_spec(renamed)
return None
sys.meta_path.append(RenamedFinder())
然而,https://docs.python.org/3.5/library/importlib.html#importlib.util.module_for_loader 和相关功能似乎已被弃用。我知道这不是我想要实现的非常 pythonic 的事情,但我很高兴得知它是可以实现的。
在旧模块的初始化文件中,从较新的模块导入它
旧 (package.oldpkg):
foo = __import__("Path to new module")
新(package.newpkg):
class foo:
bar = "thing"
所以
package.oldpkg.foo.bar 与 package.newpkg.foo.bar
希望对您有所帮助!
将所有旧包名称合并为my_package
。
旧包(old_package):
- image_processing (class) 将被删除并替换为 better_image_processing
- text_recognition (class) 将被删除并替换为 better_text_recognition
- foo (变量) 会被移动到 better_text_recognition
- still_there(class)不会动
新包:
- super_image_processing
- 更好_text_recognition
重定向器(my_package 的 class):
class old_package:
image_processing = super_image_processing # Will be replaced
text_recognition = better_text_recognition # Will be replaced
你的主要新模块(my_package):
#imports here
class super_image_processing:
def its(gets,even,better):
pass
class better_text_recognition:
def now(better,than,ever):
pass
class old_package:
#Links
image_processing = super_image_processing
text_recognition = better_text_recognition
still_there = __import__("path to unchanged module")
这允许您删除一些 文件 并保留其余的。如果你想重定向变量你会这样做:
class super_image_processing:
def its(gets,even,better):
pass
class better_text_recognition:
def now(better,than,ever):
pass
class old_package:
#Links
image_processing = super_image_processing
text_recognition = better_text_recognition
foo = text_recognition.foo
still_there = __import__("path to unchanged module")
这行得通吗?
在导入包的 __init__.py
时,您可以将任何您想要的对象放入 sys.modules
,您放入其中的值将通过 import
语句返回:
from . import new_package
from .new_package import module1, module2
import sys
sys.modules["my_lib.old_package"] = new_package
sys.modules["my_lib.old_package.module1"] = module1
sys.modules["my_lib.old_package.module2"] = module2
如果有人现在使用 import my_lib.old_package
或 import my_lib.old_package.module1
,他们将获得对 my_lib.new_package.module1
的引用。由于导入机器已经在 sys.modules
字典中找到了键,它甚至从未开始寻找旧文件。
如果您想避免立即导入所有子模块,可以通过在 sys.modules
:
__getattr__
的模块来模拟延迟加载
from types import ModuleType
import importlib
import sys
class LazyModule(ModuleType):
def __init__(self, name, mod_name):
super().__init__(name)
self.__mod_name = name
def __getattr__(self, attr):
if "_lazy_module" not in self.__dict__:
self._lazy_module = importlib.import(self.__mod_name, package="my_lib")
return self._lazy_module.__getattr__(attr)
sys.modules["my_lib.old_package"] = LazyModule("my_lib.old_package", "my_lib.new_package")
我认为这就是您要找的:
RENAMED_PACKAGES = {
'my_package.old_subpackage.foo': 'my_package.new_subpackage.foo',
}
class RenamedFinder:
@classmethod
def find_spec(cls, fullname, path, target=None):
renamed = RENAMED_PACKAGES.get(fullname)
if renamed is not None:
sys.stderr.write(
f'WARNING: {fullname} was renamed to {renamed}; please adapt import accordingly!\n')
spec = importlib.util.find_spec(renamed)
spec.loader = cls
return spec
return None
@staticmethod
def create_module(spec):
return importlib.import_module(spec.name)
@staticmethod
def exec_module(module):
pass
sys.meta_path.append(RenamedFinder())
不过,IMO my_package.new_subpackage.foo
开始偏离 my_package.old_subpackage.foo
而您仍然需要提供旧版本以实现向后兼容性时,它可能会变得很有用,尤其是在您的软件包的其他版本中。出于这个原因,您可能需要保留两者的代码。