Python: static variable decorator

前端 未结 8 685

I\'d like to create a decorator like below, but I can\'t seem to think of an implementation that works. I\'m starting to think it\'s not possible, but thought I would ask yo

8条回答
  •  一生所求
    2021-01-06 16:04

    By the time your decorator gets the function object f, it's already been compiled -- specifically, it's been compiled with the knowledge that x is local (because it's assigned with the += assignment), the normal optimization (in 2.* you can defeat the optimization, at a staggering price in performance, by starting f with exec ''; in 2.*, you cannot defeat the optimization). Essentially, to use the syntax you crave, you have to recompile f (by recovering its sources, if you know they'll be available at runtime, or, much harder, by bytecode hacks) with somehow-modified sources -- once you've decided to go that way, the simplest approach is probably to change x into f.x throughout the body of f.

    Personally, if and when I find myself fighting so hard against the language (or other technology) that I'm trying to bend to my will to impose my desires, I acknowledge that I'm either using the wrong language (or other technology), if those desires are absolutely crucial, and then the solution must be to change technology; or, if those desires are not that crucial, give up on them.

    Either way, I give up trying to distort the language too far away from its obvious design intentions: even if I did come up with some hacky, fragile kludge, it would no doubt be unmaintainable. In this case, Python's desire intentions are very clear: barenames that get re-bound within a functions are locals of that function unless explicitly designated as globals -- period. So, your attempt to make barenames (that get re-bound within a function) mean something completely different than "locals" is exactly this kind of fight.

    Edit: If you're willing to give up on the insistence on using barenames for your "statics", then suddenly you're not fighting against Python any more, but rather "going with the grain" of the language (despite the design glitch of global [and nonlocal], but, that's a separate rant;-). So, for example:

    class _StaticStuff(object):
      _static_stack = []
      def push(self, d):
        self._static_stack.append(d)
      def pop(self):
        self._static_stack.pop()
      def __getattr__(self, n):
        return self._static_stack[-1][n]
      def __setattr__(self, n, v):
        self._static_stack[-1][n] = v
    import __builtin__
    __builtin__.static = _StaticStuff()
    
    def with_static(**variables):
      def dowrap(f):
        def wrapper(*a, **k):
          static.push(variables)
          try: return f(*a, **k)
          finally: static.pop()
        return wrapper
      return dowrap
    
    @with_static(x=0)
    def f():
        static.x += 1
        print static.x
    
    f()
    f()
    

    This works just like you desire, printing 1 and then 2. (I'm using __builtin__ to make it simplest to use with_static to decorate functions living in any module whatsoever, of course). You could have several different implementations, but the key point of any good implementation is that "static variables" will be qualified names, not barenames -- making it explicit that they're not local variables, playing with the grain of the language, and so forth. (Similar built-in containers, and qualified names based on them, should have been used in Python's design, instead of the global and nonlocal design glitches, to indicate other kinds of variables that aren't local ones and therefore should not be using barenames... ah well, you can implement yourself a globvar special container on the same lines of the above static ones, without even needing decoration, though I'm not so sure that is entirely feasible for the nonlocal case [perhaps with some decoration and the tiniest amount of black magic...;=)]).

    Edit: a comments points out that the code as given doesn't work when you only decorate a function that returns a closure (instead of decorating the closure itself). That's right: of course, you have to decorate the specific function that uses the static (and there can be only one, by definition of function-static variables!), not a random function that doesn't in fact use the static but rather just happens to be in some lexical connection with the one that does. For example:

    def f():
      @with_static(x=0)
      def g():
        static.x += 1
        print static.x
      return g
    
    x = f()
    x()
    x()
    

    this works, while moving the decorator to f instead of g doesn't (and couldn't possibly).

    If the actual desiderata are not about static variables (visible and usable only within a single function) but some hybrid thing that's usable throughout a certain peculiar bundle of functions, that needs to be specified very precisely (and no doubt implemented very differently, depending on what the actual specs are) -- and ideally that needs to happen in a new and separate SO questions, because this one (which is specifically about static instead), and this answer to this specific question, are already plenty big enough.

提交回复
热议问题