【问题标题】:Function instance variables inside a class类中的函数实例变量
【发布时间】:2013-10-08 03:55:34
【问题描述】:

我试图在我的方法中实现一个所谓的静态变量,类似于decorator method described in this Stackoverflow thread。具体来说,我定义了一个装饰器函数如下:

def static_var(varName, value):
    def decorate(function):
        setattr(function,varName,value)
        return function
    return decorate

如示例所示,这可用于将变量附加到函数:

@static_var('seed', 0)
def counter():
    counter.seed +=1
    return counter.seed

这个方法会返回它被调用的次数。

我遇到的问题是,如果我在类中定义方法,这不起作用:

class Circle(object):

    @static_var('seed',0)
    def counter(self):
        counter.seed +=1
        return counter.seed

如果我实例化 Circle 并运行 counter

>>>> myCircle = Circle()
>>>> myCircle.counter()

我收到以下错误:NameError: global name 'counter' is not defined

我对此的回应是,也许我需要使用self.counter,即

class Circle(object):

    @static_var('seed',0)
    def counter(self):
        self.counter.seed +=1
        return self.counter.seed

但是这会产生错误,AttributeError: 'instancemethod' object has no attribute 'seed'

这是怎么回事?

【问题讨论】:

    标签: python oop


    【解决方案1】:

    您想访问函数对象,但您访问的是方法。 Python 将实例和类上的函数视为descriptors,在查找时返回绑定方法。

    用途:

    @static_var('seed',0)
    def counter(self):
        self.counter.__func__.seed += 1
    

    到达被包装的函数对象。

    在 Python 3 中,您还可以访问类上的函数对象:

    @static_var('seed',0)
    def counter(self):
        Circle.counter.seed += 1
    

    在 Python 2 中仍会返回未绑定的方法对象(没有附加实例的方法)。

    当然,仅仅因为你能做到这一点,并不一定是一个好主意。使用方法,您就有了一个类,为您提供了存储该计数器的替代位置。你可以把它放在Countertype(self) 上,后者会给你一个计数器每个子类

    【讨论】:

    • 啊,我明白了。我确实尝试过Circle.counter.seed,但我使用的是 Python 2.7,所以它当然不管用。
    • 当然,一旦你有了一个类,你还不如将计数存储在实例中并完成。
    • 请记住,只有一个函数对象,它是附加到类的。所以计数器在所有实例之间共享。无论如何,既然你有一个类,你应该使用类或实例属性,而不是在方法上伪造静态变量。
    【解决方案2】:

    你想要达到的目标看起来根本不应该做。

    在第一种情况下,您可以轻松得多:

    def counter():
        counter.seed += 1
        return counter
    counter.seed = 0
    

    在第二种情况下,您可以轻松地将“函数状态”放入类中。

    class Circle(object):
        seed = 0
    
        # if you want the count to be unique per instance
        def counter_inst(self):
            self.seed += 1
            return self.seed
    
        # if you want the count to be shared between all instances of the class
        @classmethod
        def counter_cls(cls):
            cls.seed += 1
            return cls.seed
    

    【讨论】:

      【解决方案3】:

      问题是类方法是descriptor objects,而不是函数。如果您在方法中做更多的工作,您可以对这两种类型的可调用对象使用相同的装饰器,在包含 v3.x 的 Python v2.6 中。这就是我的意思:

      def static_var(var_name, value):
          def decorator(function):
              setattr(function, var_name, value)
              return function
          return decorator
      
      # apply it to method
      class Circle(object):
          @static_var('seed', 0)
          def counter(self):
              counter_method = Circle.counter.__get__(self, Circle).__func__  # added
              counter_method.seed +=1
              return counter_method.seed
      
      myCircle = Circle()
      print(myCircle.counter())  # 1
      print(myCircle.counter())  # 2
      

      方法版本的作用是调用描述符的__get__方法获取绑定的方法实例对象,然后访问其__func__attribute获取附加了命名属性的实际函数实例。

      对于 2.6 之前的 Python 版本,您需要使用im_func而不是__func__

      更新:

      大多数问题都可以通过更改装饰器来避免,以便它在调用的开头添加一个参数,并编写装饰函数来引用它而不是它们自己来访问变量。另一个好处是这种方法适用于 Python 2.x 和 3.x:

      def static_var(var_name, value):
          def decorator(function):
              static_vars = getattr(function, 'static_vars', None)
              if static_vars:  # already have a container?
                  setattr(static_vars, var_name, value)  # add another var to it
                  return function
              else:
                  static_vars = type('Statics', (object,), {})()  # create container
                  setattr(static_vars, var_name, value)  # add first var to it
                  def decorated(*args, **kwds):
                      return function(static_vars, *args, **kwds)
                  decorated.static_vars = static_vars
                  return decorated
          return decorator
      
      @static_var('seed', 0)  # apply it to a function
      def counter(static_vars):
          static_vars.seed +=1
          return static_vars.seed
      
      print(counter())  # 1
      print(counter())  # 2
      
      class Circle(object):
          @static_var('seed', 0)  # apply it to a method
          def counter(static_vars, self):
              static_vars.seed +=1
              return static_vars.seed
      
      myCircle = Circle()
      print(myCircle.counter())  # 1
      print(myCircle.counter())  # 2
      

      这个装饰器允许添加多个静态:

      @static_var('seed', 0)  # add two of them to a function
      @static_var('offset', 42)
      def counter2(static_vars):
          static_vars.seed += 1
          static_vars.offset *= 2
          return static_vars.seed + static_vars.offset
      
      print(counter2())  # 1 + 2*42 = 85
      print(counter2())  # 2 + 2*84 = 170
      

      【讨论】:

        【解决方案4】:

        我能否介绍另一种替代方案,它可能使用起来会更好一些,并且对于方法和函数来说看起来都一样:

        @static_var2('seed',0)
        def funccounter(statics, add=1):
            statics.seed += add
            return statics.seed
        
        print funccounter()       #1
        print funccounter(add=2)  #3
        print funccounter()       #4
        
        class ACircle(object):
            @static_var2('seed',0)
            def counter(statics, self, add=1):
                statics.seed += add
                return statics.seed
        
        c = ACircle()
        print c.counter()      #1
        print c.counter(add=2) #3
        print c.counter()      #4
        d = ACircle()
        print d.counter()      #5
        print d.counter(add=2) #7
        print d.counter()      #8
        

        如果你喜欢这个用法,这里是实现:

        class StaticMan(object):
            def __init__(self):
                self.__dict__['_d'] = {}
        
            def __getattr__(self, name):
                return self.__dict__['_d'][name]
            def __getitem__(self, name):
                return self.__dict__['_d'][name]
            def __setattr__(self, name, val):
                self.__dict__['_d'][name] = val
            def __setitem__(self, name, val):
                self.__dict__['_d'][name] = val
        
        def static_var2(name, val):
            def decorator(original):
                if not hasattr(original, ':staticman'):    
                    def wrapped(*args, **kwargs):
                        return original(getattr(wrapped, ':staticman'), *args, **kwargs)
                    setattr(wrapped, ':staticman', StaticMan())
                    f = wrapped
                else:
                    f = original #already wrapped
        
                getattr(f, ':staticman')[name] = val
                return f
            return decorator
        

        【讨论】:

        • 所以你刚刚重新发明了self
        • @ErikAllik:不完全是。这基本上是一种将函数属性提升到函数体中的方法,而无需引用函数名
        • 我只能看到方法有一个新的statics 参数,它的功能与self 的功能相同——是的,在语义上你的解释略有不同,但我没有查看任何实际差异。
        • @ErikAllik:每个函数的statics 变量特定于该函数,而self 在类的所有实例方法之间共享。此外,我还更新了示例以显示如果您在不同的实例上调用 .counter() 会发生什么 - 它保留在初始函数中,而不是在实例或类中
        • 我不是在质疑你的例子的技术正确性或严谨性,只是实用性。
        猜你喜欢
        • 2020-03-22
        • 1970-01-01
        • 2021-10-02
        • 2021-05-03
        • 2014-09-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-12
        相关资源
        最近更新 更多