装饰器以更改功能行为

时间:2019-02-27 15:27:13

标签: python python-2.7 function python-decorators

我发现我有两个不相关的函数,它们以不同的方式实现相同的行为。我现在想知道是否有办法通过装饰器来有效地处理此问题,从而避免在行为被添加到其他地方时一遍又一遍地编写相同的逻辑。

基本上,我在两个不同的类中有两个函数,它们具有一个名为exact_match的标志。这两个函数都会检查它们所属的对象中是否存在某种类型的对等。 exact_match标志用于强制检查浮点比较,而不是带有公差。您可以在下面查看我的操作方式。

def is_close(a, b, rel_tol=1e-09, abs_tol=0.0):
    return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)


def _equal(val_a, val_b):
"""Wrapper for equality test to send in place of is_close."""
    return val_a == val_b

    @staticmethod
def get_equivalence(obj_a, obj_b, check_name=True, exact_match=False):
    equivalence_func = is_close
    if exact_match:
        # If we're looking for an exact match, changing the function we use to the equality tester.
        equivalence_func = _equal

    if check_name:
        return obj_a.name == obj_b.name

    # Check minimum resolutions if they are specified
    if 'min_res' in obj_a and 'min_res' in obj_b and not equivalence_func(obj_a['min_res'], obj_b['min_res']):
        return False

    return False

如您所见,标准过程让我们在不需要完全匹配时使用is_close函数,但在我们需要时将其调出。现在,另一个功能也需要相同的逻辑,换出该功能。当我知道可能需要换出特定的函数调用时,是否可以使用装饰器或类似方法来处理这种类型的逻辑?

2 个答案:

答案 0 :(得分:3)

不需要装饰器;只需将所需的函数作为参数传递给get_equivalence(现在只不过是适用的包装器而已 参数)。

def make_eq_with_tolerance(rel_tol=1e-09, abs_tol=0.0):
    def _(a, b):
        return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
    return _    

# This is just operator.eq, by the way
def _equal(val_a, val_b-):
    return val_a == val_b

def same_name(a, b):
    return a.name == b.name

现在get_equivalence接受三个参数:两个要比较的对象 还有一个在这两个参数上调用的函数。

@staticmethod
def get_equivalence(obj_a, obj_b, equivalence_func):

    return equivalence_func(obj_a, obj_b)

一些示例调用:

get_equivalence(a, b, make_eq_with_tolerance())
get_equivalence(a, b, make_eq_with_tolerance(rel_tol=1e-12))  # Really tight tolerance
get_equivalence(a, b, _equal)
get_equivalence(a, b, same_name)

答案 1 :(得分:0)

我想出了一个可能不太正确的替代解决方案,但是答案让我按原本的意愿来解决问题。

我的解决方案使用实用程序类,该实用程序类可用作类的成员或该类的mixin,以方便的方式提供实用程序功能。下面,在其他地方定义了函数_equalsis_close,因为它们的实现不在重点之列。

class EquivalenceUtil(object):
    def __init__(self, equal_comparator=_equals, inexact_comparator=is_close):
        self.equals = equal_comparator
        self.default_comparator = inexact_comparator

    def check_equivalence(self, obj_a, obj_b, exact_match=False, **kwargs):
        return self.equals(obj_a, obj_b, **kwargs) if exact_match else self.default_comparator(obj_a, obj_b, **kwargs)

这是一个可以像这样使用的简单类:

class BBOX(object):
    _equivalence = EquivalenceUtil()

    def __init__(self, **kwargs):
        ...

    @classmethod
    def are_equivalent(cls, bbox_a, bbox_b, exact_match=False):
        """Test for equivalence between two BBOX's."""
        bbox_list = bbox_a.as_list
        other_list = bbox_b.as_list
        for _index in range(0, 3):
            if not cls._equivalence.check_equivalence(bbox_list[_index], 
                                                      other_list[_index], 
                                                      exact_match=exact_match):
            return False
        return True

对于用户如何在幕后检查事物,此解决方案对用户而言是不透明的,这对我的项目很重要。此外,它非常灵活,可以在一个类中以多种方式和多种方式重复使用,并且可以轻松地添加到新类中。

在我的原始示例中,代码可以变成这样:

class TileGrid(object):

    def __init__(self, **kwargs):
        ...

    @staticmethod
    def are_equivalent(grid1, grid2, check_name=False, exact_match=False):
        if check_name:
            return grid1.name == grid2.name
        # Check minimum resolutions if they are specified
        if 'min_res' in grid1 and 'min_res' in grid2 and not cls._equivalence.check_equivalence(grid1['min_res'], grid2['min_res'], exact_match=exact_match):
            return False

        # Compare the bounding boxes of the two grids if they exist in the grid
        if 'bbox' in grid1 and 'bbox' in grid2:
            return BBOX.are_equivalent(grid1.bbox, grid2.bbox, exact_mach=exact_match)

        return False

在一般情况下,我不推荐这种方法,因为我忍不住感觉到它有一些代码味道,但是它确实满足了我的需要,并且将为我当前的代码库解决很多问题。我们有特定的要求,这是一个特定的解决方案。 chepner的解决方案可能最适合让用户决定功能应如何测试等效性的一般情况。