首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >将__future__样式导入用于Python中的模块特定功能

将__future__样式导入用于Python中的模块特定功能
EN

Stack Overflow用户
提问于 2015-04-28 04:24:58
回答 5查看 1.3K关注 0票数 12

Python的未来语句from __future__ import feature提供了一种很好的方法来简化到新语言特性的转换。有没有可能为Python库实现类似的特性:from myproject.__future__ import feature

在import语句上设置模块范围的常量非常简单。对我来说不明显的是,如何确保这些常量不会传播到在导入的模块中执行的代码--它们还应该需要将来的导入才能启用新特性。

这是最近在NumPy的一个discussion of possible indexing changes上出现的。我不期望它能真正在NumPy中使用,但我可以看到它对其他项目很有用。

作为一个具体的例子,假设我们确实想要在NumPy的某个未来版本中改变索引的工作方式。这将是一个向后不兼容的更改,因此我们决定使用未来语句来简化转换。使用此新功能的脚本如下所示:

代码语言:javascript
复制
import numpy as np
from numpy.__future__ import orthogonal_indexing

x = np.random.randn(5, 5)
print(x[[0, 1], [0, 1]])  # should use the "orthogonal indexing" feature
# prints a 2x2 array of random numbers

# we also want to use a legacy project that uses indexing, but
# hasn't been updated to the use the "orthogonal indexing" feature
from legacy_project import do_something

do_something(x)  # should *not* use "orthogonal indexing"

如果这是不可能的,那么启用本地选项的最接近值是多少?例如,可以这样写:

代码语言:javascript
复制
from numpy import future
future.enable_orthogonal_indexing()

使用像上下文管理器这样的东西是很好的,但问题是我们不想将选项传播到嵌套作用域:

代码语言:javascript
复制
with numpy.future.enable_orthogonal_indexing():
    print(x[[0, 1], [0, 1]])  # should use the "orthogonal indexing" feature
    do_something(x)  # should *not* use "orthogonal indexing" inside do_something
EN

回答 5

Stack Overflow用户

发布于 2015-04-28 04:53:35

Python中的__future__既是一个模块,也不是一个模块。Python __future__实际上不是从任何地方导入的-它是Python字节码编译器使用的一种构造,故意选择这样就不需要创建新的语法。在库目录中还有一个__future__.py;它可以这样导入:import __future__;然后,例如,您可以访问该__future__.print_function,以了解哪个Python版本使该特性可选地可用,以及在哪个版本中默认启用该特性。

可以创建一个知道正在导入的内容的__future__模块。下面是一个myproject/__future__.py的例子,它可以在每个模块的基础上拦截功能导入:

代码语言:javascript
复制
import sys
import inspect

class FutureMagic(object):
    inspect = inspect

    @property
    def more_magic(self):
        importing_frame = self.inspect.getouterframes(
                  self.inspect.currentframe())[1][0]
        module = importing_frame.f_globals['__name__']
        print("more magic imported in %s" % module)

sys.modules[__name__] = FutureMagic()

在加载时,该模块被替换为FutureMagic()实例。每当从myproject.FutureMagic导入more_magic时,都会调用more_magic属性方法,并打印出导入该特性的模块的名称:

代码语言:javascript
复制
>>> from myproject.__future__ import more_magic
more magic imported in __main__

现在,您可以对导入此功能的模块进行记账。执行import myproject.__future__myproject.__future__.more_magic将触发相同的机制,但您还可以确保more_magic导入位于文件的开头-此时它的全局变量不应包含任何其他内容,而不应包含从该伪模块返回的值;否则,仅为检查而访问该值。

然而,真正的问题是:如何使用它来找出从哪个模块调用函数是相当昂贵的,并且会限制此功能的有用性。

因此,一种可能更有效的方法可能是使用import hooks在执行from mypackage.__future__ import more_magic的模块的抽象语法树上进行源翻译,可能会将所有object[index]更改为__newgetitem__(operand, index)

票数 5
EN

Stack Overflow用户

发布于 2015-04-28 05:12:58

Python本身做这件事的方式非常简单:

importer中,当您尝试从.py文件导入时,代码首先扫描模块中的future statements

请注意,在将来的语句之前只允许字符串、注释、空行和其他将来的语句,这意味着它不需要完全解析代码就可以做到这一点。这一点很重要,因为未来的语句可能会改变代码的解析方式(实际上,这就是让它们…的全部意义);字符串、注释和空行可以由lexer步骤处理,将来的语句可以用一个非常简单的专用解析器进行解析。

然后,如果找到任何未来的语句,Python将设置相应的标志位,然后重新查找到文件的顶部,并使用这些标志调用compile。例如,对于from __future__ import unicode_literals,它执行flags |= __future__.unicode_literals.compiler_flag,这会将flags0更改为0x20000

在这个“真正的编译”步骤中,未来的语句被视为普通的导入,您将在模块的全局变量中的变量unicode_literals中得到一个__future__._Feature值。

现在,您不能完全做同样的事情,因为您不会重新实现或包装编译器。但您可以做的是使用类似于未来的语句来发出AST转换步骤的信号。如下所示:

代码语言:javascript
复制
flags = []
for line in f:
    flag = parse_future(line)
    if flag is None:
        break
    flags.append(flag)
f.seek(0)
contents = f.read()
tree = ast.parse(contents, f.name)
for flag in flags:
    tree = transformers[flag](tree)
code = compile(tree, f.name)

当然,您必须编写parse_future函数,以便为空行、注释或字符串返回0,为可识别的未来导入返回一个标志(如果需要,可以动态查找),或者为其他任何内容返回None。并且您必须为每个标志编写AST transformers。但它们可以非常简单-例如,您可以将Subscript节点转换为不同的Subscript节点,甚至可以转换为基于索引形式调用不同函数的Call节点。

要将其挂接到导入系统中,请参见PEP 302。请注意,这在Python3.3中变得更简单,在Python3.4中也变得更简单,所以如果您需要这些版本中的一个,请阅读import system文档以了解您的最低版本。

有关在现实生活中使用的导入钩子和AST转换器的一个很好的示例,请参阅MacroPy。(请注意,它使用的是旧的2.3风格的导入钩子机制;同样,如果您可以使用3.3+或3.4+,您自己的代码会更简单。当然,您的代码不会动态生成转换,这是MacroPy…最复杂的部分。)

票数 5
EN

Stack Overflow用户

发布于 2015-04-28 04:45:41

不,你不能。真正的__future__导入是特殊的,因为它的效果对于它发生的单个文件是本地的。但是普通的导入是全局的:一旦一个模块执行了import blahblah就会被执行并在全球范围内可用;其他稍后执行import blah的模块只会检索已经导入的模块。这意味着如果from numpy.__future__更改了import numpy中的某些内容,执行numpy的所有内容都将看到更改。

顺便说一句,我不认为这是邮件列表消息所暗示的。我把它理解为一种全局的效果,相当于设置了一个像numpy.useNewIndexing = True这样的标志。这意味着,如果您知道应用程序的所有部分都将使用该标志,则只应在应用程序的顶层设置该标志。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/29905278

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档