【问题标题】:Decorator to alter function behavior装饰器改变函数行为
【发布时间】:2019-07-21 09:04:14
【问题描述】:

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

基本上我在两个不同的类中有两个函数,它们有一个名为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,但在需要时我们换掉函数调用。现在另一个函数需要同样的逻辑,换出函数。当我知道可能需要换出特定的函数调用时,有没有办法使用装饰器或类似的东西来处理这种类型的逻辑?

【问题讨论】:

  • 您是否尝试更改它们的类或实例——换句话说,您要装饰什么?
  • 这个想法是某种类型的装饰器或机制来处理基于布尔参数切换使用哪个函数的样板。所以当exact_match 为真时,在函数内部使用_equal,而不是默认函数。我喜欢一个简单的答案,但我的想法是,希望能够在调用者不需要传递函数的地方放置一些东西,即保持函数的使用简单。跨度>
  • 你没有回答我的问题。你想切换到什么功能上?

标签: python python-2.7 function python-decorators


【解决方案1】:

我想出了一个可能不太正确的替代解决方案,但答案让我按照我最初想要的方式解决问题。

我的解决方案使用一个实用程序类,它可以用作类的成员或作为该类的 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 的解决方案可能最适合让用户决定函数应如何测试等效性的一般情况。

【讨论】:

    【解决方案2】:

    不需要装饰器;只需将所需的函数作为参数传递给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)
    

    【讨论】:

    • 我没有考虑过这个选项,似乎有点明显。我喜欢它的简单性,但我仍然想知道是否有一种方法可以在不需要调用者传入函数的情况下处理它,即只发送一个 bool 而不必担心实现。
    • 传递一个布尔值来改变行为通常被认为是一种反模式。如果您知道标志应该做什么,那么只需传递布尔值将选择的东西就更清楚了。
    • 我想得越多,我就越意识到我在说服自己接受一个没有多大意义的想法。你的解决方案很好。
    • 我接受了您的回答,但发布了我自己的解决方案,虽然总体上可能不太正确,但更符合我的初衷。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-02-12
    • 1970-01-01
    • 1970-01-01
    • 2012-07-28
    • 2015-09-13
    • 1970-01-01
    • 2017-03-22
    相关资源
    最近更新 更多