【问题标题】:How can I bind a function to an object instance using decorator?如何使用装饰器将函数绑定到对象实例?
【发布时间】:2019-01-04 14:01:08
【问题描述】:

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

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

在此之后,如果我们查看对象的实例条件属性

,我们应该有对给定函数的引用

编辑 1:

f = Transition()
f1 = Transition()

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

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

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

【问题讨论】:

  • 不清楚您的问题是什么...您希望所有其他对象正常查找其属性,但一个特定对象应该使用属性/函数吗?你为什么要使用装饰器语法呢?你想把这个函数作为一个方法放在那个类中吗?你如何定义它是哪个属性?
  • 欢迎来到 SO。恐怕您的问题并不是真正的问题-您说明了您想做什么,但没有解释您的实际问题是什么。请注意,如果问题是“如何做到这一点”,您应该首先自己尝试。
  • @brunodesthuilliers 我做到了。我也在网上搜索了答案,但没有找到。
  • @AlexandreManeta“你做了”什么?解释你的实际问题?对不起,但你没有 - 你的帖子绝对没有提到你为实现你的功能做了什么,也没有提到你遇到了什么问题。您可能想阅读帮助部分,特别是这个:stackoverflow.com/help/how-to-ask 和这个:stackoverflow.com/help/on-topic

标签: python python-3.x decorator


【解决方案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 参数)。我们通过将实例作为非局部变量保存在装饰器代码中来做到这一点。

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

      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
      

      【讨论】:

      • 这适用于 Foo 类的每个实例,还是仅适用于 Foo 类的一个实例。例如,如果您有一个 f 变量和一个 f1,它们是否都会将此函数作为属性
      • @AlexandreManeta 我添加了一个更通用的版本。
      • @AlexandreManeta 这适用于 Foo 类的每个实例。如果您希望它仅应用于一个实例,则需要以某种方式告诉装饰器是哪个实例(并且属性根本不起作用)。
      • 您的第一个示例几乎 也适用于实例 - 可以在实例上设置属性,就像那样。所缺少的只是一种告诉函数,当它运行时,它也被绑定的实例的方法。 (Python 通过插入 self 参数做了什么)
      • @jsbueno setattr(instance, fn.__name__, functools.partial(fn, instance)) 会工作
      猜你喜欢
      • 2016-01-21
      • 1970-01-01
      • 2017-05-07
      • 2013-01-12
      • 1970-01-01
      • 2021-04-24
      • 2019-02-03
      相关资源
      最近更新 更多