繁体   English   中英

python-2.7执行对象导入代码

[英]python-2.7 execute code on object import

我正在尝试建立一个允许代码以透明方式为常规用户运行的弃用系统,但在开发人员模式下标记已弃用的对象。

我遇到的一个问题是,即使我处于开发人员模式,我也可以将已弃用的对象导入另一个模块。 这意味着我缺少使用弃用对象的地方。

例如在module1.py中:

class MyObject(object):
    pass
MyObject = MyObject if not dev_mode() else DeprecatedObject

然后在module2.py中:

from module1 import MyObject

我已经设置了DeprecatedObject,以便与它进行任何交互都会引发DeprecationWarning - 有什么办法可以让我在导入时出错? 即。 甚至导入module2.py都会引发异常。

我想象的是:

import warnings

class DeprecatedObject(object):
    ...
    def __onimport__(self):
        warnings.warn("deprecated", DeprecationWarning)

除了其他方面,模块级别__getattr__功能允许模块级别名称在导入时进行正确的弃用过程。 这个功能将在Python 3.7中出现,有关详细信息,请参阅PEP 562 (因为您已经使用Python 2.7标记,它无法帮助您,但我为了未来读者的利益而提及它)。

在Python 2.7上,您有两个较差的选项:

  • 在对象__init__触发弃用警告。
  • 导入后使用Guido的hack用自身的修补版本替换模块。 在模块周围包装代理对象允许您控制名称解析。

首先,我建议查看内置warnings模块。 它有专门为这类东西制作的工具。 发出非致命警告比提出异常更有意义。

现在,对于您的情况,一种可能的做法是用函数“替换”已弃用的类。 这意味着将类重命名为其他类,并具有原始名称的函数,该函数检查开发者模式是否已启用并相应地执行。 结果将是这样的:

class MyDeprecatedClass:
    pass

def MyClass(*args, **kwargs):
    if dev_mode():
        raise DeprecationWarning
    else:
        return MyDeprecatedClass(*args, **kwargs)

或者,有警告:

def MyClass(*args, **kwargs):
    from warnings import warn
    if dev_mode():
        warn("Dont use this!!!!!!!!!")
    else:
        return MyDeprecatedClass(*args, **kwargs)

这样做是检查是否启用了开发人员模式,并且只提出异常(或警告)。 否则,它将给予它的所有参数传递给重命名类的构造函数,这意味着所有依赖它的旧函数都可以正常工作。

你的初始方法几乎就是我所建议的,除了你允许两种类型的对象同时存在。 我将从您的模块中的一个完整的if语句开始,它只允许一次定义一个对象。 更像是:

if dev_mode():
    class MyObject:
        # Define deprecated version here
        ...
else:
    class MyObject:
        # Define production version here
        ...

如果弃用版本和非弃用版本之间的差异很简单,例如,可以通过函数或类装饰器轻松完成(如提出警告),则可以将上面的代码简化为:

if dev_mode():
    def function_decorator(func, cls=None):
        # You can use the second argument when calling manually from a class decorator
        name = func.__name__ is cls is None else cls.__name__ + '.' + func.__name__
        warnings.warn("Importing deprecated function: {}".format(name))
        return func

    def class_decorator(cls):
        warnings.warn("Importing deprecated class: {}".format(cls.__name__))
        # Make additional modifications here (like adding function_decorator to all the class methods)
        return cls
else:
    def function_decorator(func):
        return func
    def class_decorator(cls):
        return cls

@class_decorator
class MyClass:
    pass

使用模块级if为了避免浮动类的多个版本是这里的基本工具。 您可以为流程添加任意数量的复杂层。 我看到的一个类似目的的技术(其中特定版本的类依赖于某些导入时间条件,如OS),是创建一个名为module1的包,并在不同的模块中完全实现两个不同版本的类。 包结构如下所示:

module1/
|
+-- __init__.py
|
+-- _development.py
|
+-- _production.py

_development_production定义了相同的名称,但版本不同。 模块名称前面的下划线表示永远不应该直接导入它们。 您将module1公开为模块而不是使用其__init__文件作为包,这看起来像这样:

__all__ = ['MyModule']

if dev_mode():
    from ._development import MyModule
else:
    from ._production import MyModule

如果您有很多名称,可以使用__init__ __all__自动执行公共导入:

import importlib, sys

__all__ = ['MyClass']

self = sys.modules[__name__]
sub = importlib.import_module('_development' if dev_mode() else '_production')
for name in __all__:
    setattr(self, name, getattr(sub, name))

这种分离形式允许您在没有两个单独的测试流程的情况下测试生产版本和开发版本。 您的测试可以直接导入私有模块。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM