【问题标题】:Adding attributes to instance methods in Python在 Python 中为实例方法添加属性
【发布时间】:2012-03-20 08:53:15
【问题描述】:

我想在我的一个类中为实例方法添加一个属性。我尝试了this question 中给出的答案,但这个答案仅适用于函数——据我所知。

例如,我希望能够执行以下操作:

class foo(object):
    ...
    def bar(self):
        self.bar.counter += 1
        return self.bar.counter
    bar.counter = 1
    ...

但是,当我调用 foo().bar() 我得到:

AttributeError: 'instancemethod' object has no attribute 'counter'

我这样做的目的是试图让人印象深刻,'counter' 变量是 bar() 方法的本地变量,同时也避免让我的类命名空间与另一个属性混淆。有没有办法做到这一点?有没有更 Pythonic 的方式来做到这一点?

【问题讨论】:

  • 应该 counter 是类级别(所有实例共享当前计数)还是实例级别(每个实例将从 1 开始)?
  • counter 应该是实例级别
  • 这个相关问题可能(或可能没有)有帮助:stackoverflow.com/questions/7034063/…

标签: python methods instance instance-methods


【解决方案1】:

在 Python 3 中,您的代码可以工作,但在 Python 2 中,查找方法时会发生一些包装。

类与实例

  • 类级别:将counter 与函数一起存储(直接或通过使用可变默认值)有效地使其成为类级别属性,因为无论您有多少实例,都只有一个函数有(它们都共享同一个函数对象)。

  • 实例级别:要使counter 成为实例级别属性,您必须在__init__ 中创建函数,然后用functools.partial 包装它(因此它的行为类似于普通方法),然后存储它在实例上 -- 现在每个实例都有一个函数对象。

班级级别

类静态变量的公认做法是使用可变的默认参数:

class foo(object):
    ...
    def bar(self, _counter=[0]):
        _counter[0] += 1
        return _counter[0]

如果你想让它更漂亮,你可以定义自己的可变容器:

class MutableDefault(object):
    def __init__(self, start=0):
        self.value = start
    def __iadd__(self, other):
        self.value += other
        return self
    def value(self):
        return self.value

并像这样更改您的代码:

class foo(object):
    def bar(self, _counter=MutableDefault()):
        _counter += 1
        return _counter.value

实例级别

from functools import partial

class foo(object):
    def __init__(self):
        def bar(self, _counter=MutableDefault(1)):   # create new 'bar' each time
            value = _counter.value
            _counter += 1
            return value
        self.bar = partial(bar, self)

总结

如您所见,当移动到 counter 的实例级别时,可读性受到严重影响。我强烈建议你重新评估强调counterbar 的一部分的重要性,如果它真的很重要,也许让bar 成为它自己的类,其实例成为foo 实例的一部分。如果它不是很重要,请按正常方式进行:

class foo(object):
    def __init__(self):
        self.bar_counter = 0
    def bar(self):
        self.bar_counter += 1
        return self.bar_counter

【讨论】:

  • 请注意,MutableDefault 远不是一个完整的实现——这留给读者作为练习。 :)
  • 默认参数只计算一次,所以它是类级别的。 OP 在评论中澄清了它应该是实例级别的问题。
  • @Series8217:是的,这就是我问这个问题的原因——更新了答案以显示如何在实例级别做到这一点。
  • 新答案看起来应该可行。不过,它实际上与猴子补丁一样非 Pythonic!
  • 感谢所有这些...听起来是在 Python 中完成此任务的唯一方法。我的初衷是让逻辑更容易理解,所以我想我最终会重构我的代码
【解决方案2】:

很抱歉挖掘了一篇较旧的帖子,但我在搜索中遇到了它,实际上找到了有解决方案的人。

这是一篇博客文章,描述了您遇到的问题以及如何创建一个可以满足您需求的装饰器。您不仅会获得所需的功能,而且作者也很好地解释了 Python 为何会以这种方式工作。

http://metapython.blogspot.com/2010/11/python-instance-methods-how-are-they.html

【讨论】:

    【解决方案3】:

    您不能直接将属性添加到实例方法,但实例方法是函数的包装器,您可以将属性添加到包装的函数。即

    class foo(object):
        def bar(self):
            # When invoked bar is an instancemethod.
            self.bar.__func__.counter = self.bar.__func__.counter + 1
            return self.bar.__func__.counter
        bar.counter = 1 # At this point bar is just a function
    

    这可行,但 counter 实际上变成了一个类变量。

    【讨论】:

      【解决方案4】:

      您需要一个__init__ 方法来初始化数据成员:

      class foo(object):
          def __init__(self):
              self.counter = 0
          def bar(self):
              self.counter += 1
              return self.counter
      

      将数据值直接附加到函数显然不是 Pythonic。

      【讨论】:

      • 我很欣赏这一点,但我的目标是将 counter 变量与 bar 方法联系起来(出于我在问题中所述的原因)。我将您的答案修改为: class foo(object): def init__(self): self.bar.counter = 0 def bar(self): self.bar.counter += 1 return self.bar.counter 但现在我在 __init 调用中得到相同的 AttributeError
      • 不像 OP 要求的那样将counter 绑定到bar
      • @EthanFurman:OP 要求一种“更 Pythonic”的方式来做到这一点。将数据直接附加到函数与 Pythonic 是相反的。
      • 尽管如此,您还没有提供一种方法来执行 OP 要求的操作,无论是否是 Python 的。
      • 我认为 OP 要求的是静态计数器,而不是每个实例的计数器。不过,这个问题有点含糊,所以我看不出有理由否决这个答案。
      【解决方案5】:

      如果您希望计数器在所有实例中保持不变,您可以执行以下操作,这仍然不是 Pythonic。

      class foo(object):
          def bar(self):
              self.bar.im_func.counter += 1
              return self.bar.im_func.counter
          bar.counter = 0
      

      【讨论】:

        猜你喜欢
        • 2012-05-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-11-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多