如何使用装饰器将函数绑定到对象实例?

时间:2019-01-04 14:01:09

标签: python python-3.x decorator

我正在尝试将函数绑定到对象实例。例如,我有一个对象,并且试图将一个函数绑定到condition属性。 问题是我想使用装饰器来做到这一点:

class Transition(object):
    def __init__(self, enter, exit, condition=None):
        if not isinstance(enter, State) or not isinstance(exit, State):
            raise TypeError("Parameters should be an instance of the State class")
        self.enter = enter
        self.exit = exit
        self.condition = None
        self.effect = None

    def __repr__(self):
        print("Going from {} to {} with condition {} and effect {}".format(self.enter, self.exit, self.condition.__name__, self.effect.__name__)) 

    def __eq__(self, other):
        return self.enter == other.enter and self.exit == other.exit

    def is_enpoint(self, endpoints):
        """
        :parameter --> 'endpoints' is a tuple indicating where the transition flows(from, to) 
        :return --> boolean
        :description --> check if the given endpoints are valid 
        """
        return self.enter == endpoints[0] and self.exit == endpoints[1]

然后我将一个函数绑定到对象的实例。

@bind
my_condition():
    return True

此后,如果我们查看对象的实例 condition属性

,则应该对给定函数有一个引用

编辑1:

f = Transition()
f1 = Transition()

@bind(f)
def condition1():
    return True

@bind(f1)
def condition2():
    return False

f实例应引用 condition1 函数,而f2实例应引用 condition 属性的 condition2

3 个答案:

答案 0 :(得分:1)

当然,装饰器必须将要绑定到的实例作为参数获取-并且,如果装饰的函数本身在绑定的实例中传递一个参数,则代码将更加简洁。同样:如果定义为方法,则等效于self。 Python不会自动插入它,但它可以称为self,因此易于阅读;

class Transition:
   ...

f = Transition

def bind(instance):
    def decorator(func):
        def wrapper (*args, **kwargs):
              return func(instance, *args, **kwargs)
        setattr(instance, func.__name__, wrapper)
        return wrapper
    return decorator

@bind(f)
def condition(self, ...):
    ...

如果您想要一个更漂亮的装饰器,可以使用functools.partial-然后我也使用functools.wraps,因为好的装饰器仍然应该使用它:

import functools

...

def bind(func, instance=None):
    if instance is None:
         return functools.partial(bind, instance=func)
    @functools.wraps(func)
    def wrapper(*args, **kw):
         return func(instance, *args, **kw)
    setattr(instance, func.__name__, wrapper)
    return wrapper

之所以可行,是因为在Python中,当函数直接归属于实例时,其行为与任何普通属性完全一样:Python会检索该函数,然后对其进行调用,而无需进行任何修改-不会将其更改为方法,也不会插入self参数)。为此,我们将实例作为装饰器代码中的非本地变量保存。

答案 1 :(得分:0)

我只是在这里猜测,但是您可以使用装饰器将函数绑定到类,例如:

class Foo:
    pass

def bind(fn):
    setattr(Foo, fn.__name__, fn)
    return fn

@bind
def bar(self):
    return 7

用法:

>>> f = Foo()
>>> f.bar()
7

支持属性并绑定到自定义类的更高级示例:

def bind(cls):
    def decorator(fn):
        name = fn.fget.__name__ if isinstance(fn, property) else fn.__name__
        setattr(cls, name, fn)
        return fn
    return decorator

用法:

@bind(Foo)
def bar(self):
    return 7

@bind(Foo)
@property
def bat(self):
    import random
    return random.randint(1, 6)

>>> f = Foo()
>>> f.bar()
7
>>> f.bat
4

最后,您绝对应该使用该解决方案,但我无法阻止自己:

from functools import partial

def bind(clsname, first, second=None):
    if second is None:  # class binding
        cls = globals()[clsname]
        fn = first
        name = fn.fget.__name__ if isinstance(fn, property) else fn.__name__
        setattr(cls, name, fn)
    else:  # instance binding
        self = first
        fn = second
        name = fn.fget.__name__ if isinstance(fn, property) else fn.__name__
        setattr(self, name, partial(fn, self))

class BindableMeta(type):
    def __new__(cls, name, bases, dct):
        def inner(*args):
            return bind(name, *args)
        dct["bind"] = inner
        return type.__new__(cls, name, bases, dct)


class Bindable(metaclass=BindableMeta):
    pass

class Foo(Bindable):
    pass

f = Foo()
g = Foo()


@Foo.bind
def bar(self):
    return 5

@f.bind
def bat(self):
    return 5

@Foo.bind
@property
def prop(self):
    return 5


assert f.bar() == 5
assert f.bat() == 5
try:
    assert g.bat() == 5
except AttributeError:
    pass
else:
    raise RuntimeError("shouldn't work")
assert f.prop == 5

答案 2 :(得分:0)

我想我以前写过一篇关于类似问题的博客文章。它说明了如何将2个python类属性绑定在一起,以便更新一个自动更新另一个。我说明了解决此问题的两种可能方法。第一个问题在this博客文章中进行了举例说明。您可能还打算将帖子中说明的概念用于其他目的。