简体   繁体   English

装饰器以更改功能行为

[英]Decorator to alter function behavior

I've found that I have two unrelated functions that implement identical behavior in different ways. 我发现我有两个不相关的函数,它们以不同的方式实现相同的行为。 I'm now wondering if there's a way, via decorators probably, to deal with this efficiently, to avoid writing the same logic over and over if the behavior is added elsewhere. 我现在想知道是否有办法通过装饰器来有效地处理此问题,从而避免在行为被添加到其他地方时一遍又一遍地编写相同的逻辑。

Essentially I have two functions in two different classes that have a flag called exact_match . 从本质上讲,我在两个不同的类中有两个函数,它们具有一个称为exact_match的标志。 Both functions check for some type of equivalence in the objects that they are members of. 这两个函数都会检查它们所属的对象中是否存在某种类型的对等。 The exact_match flag forces to function to check float comparisons exactly instead of with a tolerance. exact_match标志强制用于精确检查浮点比较,而不是带有公差。 You can see how I do this below. 您可以在下面查看我的操作方法。

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

As you can see, standard procedure has us use the function is_close when we don't need an exact match, but we swap out the function call when we do. 如您所见,标准过程让我们在不需要完全匹配时使用is_close函数,但在我们需要时将其调出。 Now another function needs this same logic, swapping out the function. 现在,另一个功能也需要相同的逻辑,换出该功能。 Is there a way to use decorators or something similar to handle this type of logic when I know a specific function call may need to be swapped out? 当我知道可能需要换出特定的函数调用时,是否可以使用装饰器或类似方法来处理这种类型的逻辑?

No decorator needed; 不需要装饰器; just pass the desired function as an argument to get_equivalence (which is now little more than a wrapper that applies the argument). 只需将所需的函数作为参数传递给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

Now get_equivalence takes three arguments: the two objects to compare and a function that gets called on those two arguments. 现在, get_equivalence接受三个参数:两个要比较的对象,以及在这两个参数上调用的函数。

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

    return equivalence_func(obj_a, obj_b)

Some example calls: 一些示例调用:

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)

I came up with an alternative solution that is perhaps less correct but answers let's me solve the problem as I originally wanted to. 我想出了一个可能不太正确的替代解决方案,但是答案让我按我原本的意愿来解决问题。

My solution uses a utility class that can be used as a member of a class or as a mixin for the class to provide the utility functions in a convenient way. 我的解决方案使用了一个实用程序类,该实用程序类可用作类的成员或该类的mixin,以方便的方式提供实用程序功能。 Below, the functions _equals and is_close are defined elsewhere as their implementations is besides the point. 下面,函数_equalsis_close在其他地方定义,因为它们的实现is_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)

It's a simple class that can be used like so: 这是一个简单的类,可以像这样使用:

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

This solution is more opaque to the user about how things are checked behind the scenes, which is important for my project. 对于用户如何在幕后检查事物,此解决方案对用户而言更加不透明,这对我的项目很重要。 Additionally it is pretty flexible and can be reused within a class in multiple places and ways, and easily added to a new class. 此外,它非常灵活,可以在一个类中以多种方式和位置重用,并且可以轻松地添加到新类中。

In my original example the code can turn into this: 在我的原始示例中,代码可以变成这样:

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

I can't recommend this approach in the general case, because I can't help but feel there's some code smell to it, but it does exactly what I need it to and will solve a great many problems for my current codebase. 在一般情况下,我不推荐这种方法,因为我不禁觉得它有一些代码味道,但是它确实满足了我的需要,并且将解决当前代码库的许多问题。 We have specific requirements, this is a specific solution. 我们有特定的要求,这是一个特定的解决方案。 The solution by chepner is probably best for the general case of letting the user decide how a function should test equivalence. chepner的解决方案可能最适合让用户决定功能应如何测试等效性的一般情况。

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

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