python-2.7执行对象导入代码

时间:2020-12-07 20:24:26

I'm trying to make a deprecation system that allows code to run transparently for regular users, but flag deprecated objects in developer mode.

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

One issue that I'm having is that I can import a deprecated object into another module even if I'm in developer mode. This means that I'm missing places where the deprecated object is used.

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

For example in module1.py:

例如在module1.py中:

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

Then in module2.py:

然后在module2.py中:

from module1 import MyObject

I already have DeprecatedObject set up so that any interaction with it raises a DeprecationWarning - is there any way that I can make it error on import? ie. even importing module2.py would raise an exception.

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

I'm imagining something like:

我想象的是:

import warnings

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

3 个解决方案

#1


2  

The module level __getattr__ feature allows, among other things, for module level names to undergo a correct deprecation process at import time. This feature is coming in Python 3.7, see PEP 562 for details (since you've tagged with Python 2.7, it can't help you, but I mention it for the benefit of future readers).

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

On Python 2.7 you have two inferior options:

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

  • Trigger deprecation warning in the object __init__.
  • 在对象__init__中触发弃​​用警告。

  • Use Guido's hack to replace the module with a patched version of itself after import. Wrapping a proxy object around the module allows you to control name resolution.
  • 导入后,使用Guido的hack将模块替换为自身的修补版本。在模块周围包装代理对象允许您控制名称解析。

#2


1  

First off, I recommend looking into the built-in warnings module. It has tools made specifically for this type of thing. Having a non-fatal warning in place makes more sense than raising an exception.

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

Now, for your case, one possible course of action would be to "replace" the deprecated class with a function. This means renaming the class to something else, and having a function with the original name which checks whether or not developer mode is enabled and acts accordingly. The result would be something like:

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

class MyDeprecatedClass:
    pass

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

Or, with warnings:

或者,有警告:

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

What this does is it checks whether or not developer mode is enabled, and only raises the exception (or warning) if it is. Otherwise, it passes all the arguments given to it to the constructor of the renamed class, meaning all old that relies on it will work fine.

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

#3


0  

Your initial approach is almost exactly what I would advise, except that you allow for both types of objects to exist simultaneously. I would start with a full-blown if statement in your module, that only allows one of the objects to be defined at a time. Something more like:

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

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

If the difference between the deprecated version and the non-deprecated version is something simple, e.g., that could be easily accomplished with a function or class decorator (like raising a warning), you could simplify the code above to something like:

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

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

Using a module-level if to avoid multiple versions of the class floating around is the basic tool here. You can add any number of layers of complexity to your process. One technique I have seen for a similar purpose (where the particular version of a class depends on some import-time condition like OS), is to create a package named module1, and implement two different versions of your classes in different modules entirely. The package structure would look like this:

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

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

Both _development and _production define the same names, but different versions. The underscores in front of the module names imply that they should never be imported directly. You expose module1 as a module rather than as a package using its __init__ file, which would look something like this:

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

__all__ = ['MyModule']

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

If you have a lot of names, you can automate the public import using __all__ in __init__:

如果您有很多名称,可以使用__in__中的__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))

This form of separation allows you to test both the production and the dev versions without having two separate test flows. Your tests can import the private modules directly.

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

#1


2  

The module level __getattr__ feature allows, among other things, for module level names to undergo a correct deprecation process at import time. This feature is coming in Python 3.7, see PEP 562 for details (since you've tagged with Python 2.7, it can't help you, but I mention it for the benefit of future readers).

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

On Python 2.7 you have two inferior options:

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

  • Trigger deprecation warning in the object __init__.
  • 在对象__init__中触发弃​​用警告。

  • Use Guido's hack to replace the module with a patched version of itself after import. Wrapping a proxy object around the module allows you to control name resolution.
  • 导入后,使用Guido的hack将模块替换为自身的修补版本。在模块周围包装代理对象允许您控制名称解析。

#2


1  

First off, I recommend looking into the built-in warnings module. It has tools made specifically for this type of thing. Having a non-fatal warning in place makes more sense than raising an exception.

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

Now, for your case, one possible course of action would be to "replace" the deprecated class with a function. This means renaming the class to something else, and having a function with the original name which checks whether or not developer mode is enabled and acts accordingly. The result would be something like:

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

class MyDeprecatedClass:
    pass

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

Or, with warnings:

或者,有警告:

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

What this does is it checks whether or not developer mode is enabled, and only raises the exception (or warning) if it is. Otherwise, it passes all the arguments given to it to the constructor of the renamed class, meaning all old that relies on it will work fine.

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

#3


0  

Your initial approach is almost exactly what I would advise, except that you allow for both types of objects to exist simultaneously. I would start with a full-blown if statement in your module, that only allows one of the objects to be defined at a time. Something more like:

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

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

If the difference between the deprecated version and the non-deprecated version is something simple, e.g., that could be easily accomplished with a function or class decorator (like raising a warning), you could simplify the code above to something like:

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

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

Using a module-level if to avoid multiple versions of the class floating around is the basic tool here. You can add any number of layers of complexity to your process. One technique I have seen for a similar purpose (where the particular version of a class depends on some import-time condition like OS), is to create a package named module1, and implement two different versions of your classes in different modules entirely. The package structure would look like this:

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

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

Both _development and _production define the same names, but different versions. The underscores in front of the module names imply that they should never be imported directly. You expose module1 as a module rather than as a package using its __init__ file, which would look something like this:

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

__all__ = ['MyModule']

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

If you have a lot of names, you can automate the public import using __all__ in __init__:

如果您有很多名称,可以使用__in__中的__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))

This form of separation allows you to test both the production and the dev versions without having two separate test flows. Your tests can import the private modules directly.

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