Python:大型项目并对各处使用的文件进行更改

时间:2013-11-20 20:31:50

标签: python eclipse pydev

假设我有一个Java大项目,以及一个在任何地方使用的类UsedEverywhere。如果我更改了该类的returnsSomething方法的返回类型,我的IDE会告诉我由于该更改而导致的所有内容。现在假设我在Python中有一个具有相同类的大型项目,并且我做了相同的更改。除非我还有一大套回归单元测试(我经常使用),否则我无法知道我有什么影响。这是由于动态的鸭子打字系统。对于我从任何地方都使用的类中删除方法的情况也是如此。在我们不知情的情况下保护大型项目免受破坏的最佳方法是什么?在进行某种回归测试之前,永远不会检测到这些破坏。

2 个答案:

答案 0 :(得分:2)

两件事:

  1. 自动测试,正如您所建议的那样。
  2. 静态分析:Pylint,PyChecker和/或pyflakes。这些, pylint是最严格的。

答案 1 :(得分:1)

这可能是这样一种情况,就returnSomething的情况而言,您可能想要使用“静态类型”装饰器,以确保有关该功能的事情。我把“静态打字”放在恐慌报价中有两个原因。首先,它是愚蠢的,我倾向于把愚蠢的东西放在恐慌的报价中。其次,在Python中执行此操作非常hacky,所以它不像真正的静态类型,你不应该这样想。 (例如,这个版本肯定会带来运行时性能损失,而如果Python是静态类型语言则不会。)

def ensure_return_type(permissible_types):
    from functools import wraps
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            potential_return = func(*args, **kwargs)
            if isinstance(potential_return, permissible_types):
                return potential_return
            else:
                err_msg = "Return value {} must be of type from {}".format(
                    potential_return, permissible_types)
                raise TypeError(err_msg)
        return wrapper
    return decorator

现在考虑一个像这样的简单函数:

def foo(x, y):
    return x + y

但是,如果没有人预料到在Python中这可以在intfloat str输入上正常工作呢?

foo(1, 1)
# prints 2

foo(1.5, -1.5)
# prints 0.0

foo("foo", "bar")
# prints 'foobar'

所以我们试试这个:

typed_foo = ensure_return_type((int, float))(foo)

我们看到了

In [22]: typed_foo(1, 1)
Out[22]: 2

In [23]: typed_foo(1.5, -1.5)
Out[23]: 0.0

In [24]: typed_foo("foo", "bar")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-24-575a47c185e3> in <module>()
----> 1 typed_foo("foo", "bar")

<ipython-input-15-58b2c0884600> in wrapper(*args, **kwargs)
     10                 err_msg = "Return value {} must be of type from {}".format(
     11                     potential_return, permissible_types)
---> 12                 raise TypeError(err_msg)
     13         return wrapper
     14     return decorator

TypeError: Return value foobar must be of type from (<type 'int'>, <type 'float'>)

当然,在您首先定义foo时,您可以更简单地使用Python中的常见装饰器语法:

@ensure_return_type((int, float))
def foo(x, y):
    return x + y

或者在你的情况下:

@ensure_return_type((... <your_types_here> ... ))
def returnsSomething(*args, **kwargs):
    # stuff...

执行此操作后,您可以轻松控制允许tuple允许返回的returnsSomething类型。这可以移出代码本身并移动到参数文件或其他形式的元数据(它所属的位置)。

另一个好处是你可以定义一个适配器类,比如

class ReturnsSomethingAdapter(object):
    # stuff

此类可能会大量使用@property和Descriptor模式来控制对其内部数据字段的读写访问。然后,您可以使returnsSomething输出的所有下游使用者计划始终期望ReturnsSomethingAdapter的实例。当消费者从ReturnsSomethingAdapter访问数据时,也许他们可以传递他们自己的类名或其他东西,并且适配器知道如何修改输出或重新格式化/重新键入它以供他们处理。

ReturnsSomethingAdapter内部(应该从某种抽象的适配器继承),你应该以这样的方式定义__subclasshook__,以便每当有其他人需要检查并查看他们是否收到了ReturnsSomethingAdapter的实例,isinstance只需要适配器的鸭子类型接口才能返回True(因此,具有相同@property和Descriptor模式的任何类都可以代替适配器的功能,允许以后更改的灵活性。)

这听起来像是很多工作,因为这是很多工作。但与此同时,您的大型系统似乎需要保证传递的类型以及需要验证和验证的接口。因此,如果必须验证其输出类型,则可能必须对该使用频繁的函数进行性能影响。在这种情况下,您需要灵活的东西,这就是为什么需要所有这些设计理念。