临时通配符导入/numpy 公式的可读性

Temporarily wildcard import / readability of numpy formulas

当使用 numpy 时,代码中有很多 np.*,例如

import numpy as np
y = np.sin(np.abs(np.linspace(0, 2*np.pi)))

这会使公式变得混乱并降低它们的可读性。人们可以使用通配符 import

来解决这个问题
from numpy import *
y = sin(abs(linspace(0, 2*pi)))

但是,通配符导入几乎总是一个坏主意。

我想知道是否有可能将通配符导入到仅限于公式(或数学代码块)的上下文中。这将保持可读性并将命名空间污染限制在更容易控制的小代码区域。我想要这样的东西:

with import_wildcard(numpy):
    y2 = sin(abs(linspace(0, 2*pi)))

问题:

  1. 是否有某种语言结构允许这样做。
  2. 请求本身是否合理,还是我忽略了一个潜在的问题?

解决方案 1:临时通配符导入:

class import_wildcard(object):
    """Contextmanager to temporary import a package content into the global namespace."""
    def __init__(self, packagename):
        self.packagename = packagename
        self.package = __import__(self.packagename, globals(), locals())
        self.globals_backup = {}

    def __enter__(self):
        _globals = globals()
        for name in self.package.__dict__:
            if name in _globals:
                self.globals_backup[name] = _globals[name]
        _globals.update(self.package.__dict__)

    def __exit__(self, exc_type, exc_value, exc_tb):
        _globals = globals()
        for name in self.package.__dict__:
            if name not in self.globals_backup:
                del _globals[name]
        _globals.update(self.globals_backup)
        self.globals_backup.clear()


with import_wildcard('numpy'):
    y = sin(abs(linspace(0, 2*pi)))

到目前为止,我还没有遇到明显的缺点。当然,除了在上下文之外定义的与 numpy 中某些函数同名的变量将无法在上下文中访问。

方案二:临时提升指定对象

根据反馈,这是另一种更明确的方法。我们只是暂时将指定的对象提升到全局命名空间,而不是进行临时通配符导入。

class global_context(object):
    def __init__(self, *objects):
        """Return a context manager that has the given objects available in the global namespace.

        You can directly pass in an object if it has a __name__, otherwise use the string name.
        """
        def parse_object(obj):
            if isinstance(obj, str):
                ns, name = obj.split('.')
                return name, getattr(globals()[ns], name)
            else:
                return obj.__name__, obj
        self.identifiers = dict(parse_object(o) for o in objects)
        self.globals_backup = {}

    def __enter__(self):
        _globals = globals()
        for name, fn in self.identifiers.items():
            if name in _globals:
                self.globals_backup[name] = _globals[name]
        _globals.update(self.identifiers)

    def __exit__(self, exc_type, exc_value, exc_tb):
        _globals = globals()
        for name in self.identifiers:
            if name not in self.globals_backup:
                del _globals[name]
        _globals.update(self.globals_backup)
        self.globals_backup.clear()

用法:

import numpy as np
with global_context(np.sin, np.abs, np.linspace, 'np.pi'):
    y = sin(abs(linspace(0, 2*pi)))

我也会留下第一个解决方案,这样可以更轻松地讨论每种方法的优缺点,并且人们可以为每个解决方案投票。