Python:静态变量装饰器

buk*_*zor 3 python variables static namespaces decorator

我想创建一个像下面这样的装饰器,但我似乎无法想到一个有效的实现.我开始认为这是不可能的,但我想我先问你们.

我意识到在Python中有各种其他方法可以创建静态变量,但我发现这些方法很难看.如果可能的话,我真的想使用下面的语法.

@static(x=0)
def f():
    x += 1
    print x

f() #prints 1
f() #prints 2
Run Code Online (Sandbox Code Playgroud)

我不在乎执行static是长期还是黑客,只要它像上面那样工作.

我创建了这个版本,但它只允许<function>.<varname>使用更长的函数和变量名来快速繁琐的语法.

def static(**assignments):
    def decorate(func):
        for var, val in assignments.items():
            setattr(func, var, val)
        return func
    return decorate
Run Code Online (Sandbox Code Playgroud)

我想到的各种事情,但无法开展工作的是:

  1. 将f(装饰函数)更改为可调用类,并以某种方式self透明地存储静态变量.
  2. 修改装饰器内的f()的全局变量,并以某种方式将'global x'语句插入到f的代码中.
  3. 将f更改为生成器,我们手动绑定变量,然后直接执行f代码.

Ale*_*lli 8

当你的装饰器得到函数对象时f,它已经被编译了 - 具体来说,它x是用本地的知识编译的(因为它被分配了+=赋值),正常的优化(在2.*你可以打败优化,在一个惊人的价格在性能上,通过启动fexec '';在2.*,你不能打败优化).从本质上讲,要使用您渴望的语法,您必须重新编译f(通过恢复其源,如果您知道它们将在运行时可用,或者更难以通过字节码黑客攻击)以及某些修改过的源 - 一旦您使用了决定走那条路,最简单的方法可能是改变xf.x整个身体f.

就个人而言,如果我发现自己与语言(或其他技术)的斗争如此艰难,以至于我试图屈服于我强加我的欲望,我承认我要么使用错误的语言(或其他技术)如果这些欲望绝对至关重要,那么解决方案必须是改变技术; 或者,如果这些欲望不那么重要,就放弃它们.

无论哪种方式,我都放弃试图将语言歪曲远离其明显的设计意图:即使我确实想出了一些hacky,脆弱的kludge,它无疑也是不可维护的.在这种情况下,Python的愿望非常明确:在函数内重新绑定的barenames是该函数的本地,除非明确指定为globals - period.因此,你尝试制作裸名(在一个函数中重新绑定)意味着完全不同于"本地人"正是这种斗争.

编辑:如果你愿意放弃坚持使用裸名为你的"静力学",那么突然你不再与Python作斗争,而是"与语言一脉相承 "(尽管存在设计故障)的global[和nonlocal],但是,这是一个独立的咆哮;-).所以,例如:

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()
Run Code Online (Sandbox Code Playgroud)

这就像你想要的那样工作,打印1然后打印2.(我用__builtin__它来制作最简单的with_static用于装饰生活在任何模块中的功能,当然).你可以有几个不同的实现,但任何好的实现的关键点是"静态变量"将是限定名称,而不是简单名称- 明确表示它们不是局部变量,使用语言的粒度,以及等等.(类似的内置容器中,并基于这些限定名,应该已经在Python的设计中使用,而不是globalnonlocal设计毛刺,以指示其他种类的不属于本地的,因此变量使用barenames不应该..啊,好吧,你可以globvar在上面static的同一条线上实现一个特殊的容器,甚至不需要装饰,虽然我不太确定这种情况完全可行nonlocal[也许一些装饰和最小量的黑色魔术...; =)]).

编辑:注释指出当你只修饰一个返回闭包的函数(而不是装饰闭包本身)时,给定的代码不起作用.那是对的:当然,你必须装饰使用的特定函数static(并且只有一个,通过函数static变量的定义!),而不是实际上不使用的随机函数,static而只是碰巧在与一个词汇的一些联系.例如:

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

x = f()
x()
x()
Run Code Online (Sandbox Code Playgroud)

这是有效的,同时移动装饰器fg不是(并且不可能).

如果实际的需求不是关于静态变量(仅在单个函数中可见和可用),而是一些在某些特殊函数中可用的混合事物,则需要非常精确地指定(并且毫无疑问实现方式非常不同,具体取决于实际的规格什么) - 理想情况下需要在一个新的和单独的SO问题中发生,因为这个问题(特别是关于静态的),以及对这个特定问题的回答,已经足够大了.

  • @buzkor,我非常了解设计意图,因为我是在设计意图形成时对其进行辩论的各方之一(尽管最终决定是 Guido 的)。`nonlocal` 只是 `global` 的又一个变体(我也不喜欢,我认为它们阻碍了 Python 的大部分其他相当一致的设计,并且使用一个特殊的关键字来生成限定名称会更好——但显然我在 Guido 的最终决定方面输掉了这场辩论)。我可以编辑我的答案以展示它是如何工作的。 (2认同)

小智 7

这是一个似乎有用的装饰器.请注意,这需要在函数末尾返回locals(),因为无法从外部设置本地(我没有太多编程经验,所以如果有办法,我不知道).

class Static(object):
    def __init__(self, **kwargs):
        self.kwargs = kwargs

    def __call__(self, f):
        def wrapped_f():
            try:
                new_kwargs = {}
                for key in self.kwargs:
                    i = getattr(f, key)
                    new_kwargs[key] = i
                self.kwargs = new_kwargs
            except:
                pass
            for key, value in f(**self.kwargs).items():
                setattr(f, key, value)
        return wrapped_f

@Static(x=0, y=5, z='...')
def f(x, y, z):
    x += 1
    y += 5
    print x, y, z
    return locals()
Run Code Online (Sandbox Code Playgroud)

输出将是:

>>> f()
1 10 ...
>>> f()
2 15 ...
>>> f()
3 20 ...
Run Code Online (Sandbox Code Playgroud)

编辑:

我在http://code.activestate.com/recipes/410698/找到了一些东西,并决定尝试将其添加到此处.它现在没有回报.

再次编辑:更改为使其快几秒.编辑3; 改为功能而不是类

def static(**kwargs):
    def wrap_f(function):
        def probeFunc(frame, event, arg):
            if event == 'call':
                frame.f_locals.update(kwargs)
                frame.f_globals.update(kwargs)
            elif event == 'return':
                for key in kwargs:
                    kwargs[key] = frame.f_locals[key]
                sys.settrace(None)
            return probeFunc
        def traced():
            sys.settrace(probeFunc)
            function()
        return traced
    return wrap_f
Run Code Online (Sandbox Code Playgroud)

测试:

@static(x=1)
def f():
    x += 1

global_x = 1
def test_non_static():
    global global_x
    global_x += 1


print 'Timeit static function: %s' % timeit.timeit(f)
print 'Timeit global variable: %s' % timeit.timeit(test_non_static)
Run Code Online (Sandbox Code Playgroud)

输出:

Timeit static function: 5.10412869535
Timeit global variable: 0.242917510783
Run Code Online (Sandbox Code Playgroud)

使用settrace会大大减慢它的速度.


DRa*_*ayX 7

这是一个非常简单的解决方案,就像普通的python静态变量一样.

def static(**kwargs):
  def wrap(f):
    for key, value in kwargs.items():
      setattr(f, key, value)
    return f
  return wrap
Run Code Online (Sandbox Code Playgroud)

用法示例:

@static(a=0)
def foo(x):
  foo.a += 1
  return x+foo.a

foo(1)  # => 2
foo(2)  # => 4
foo(14) # => 17
Run Code Online (Sandbox Code Playgroud)

这更接近于匹配python静态变量的常规方式

def foo(x):
  foo.a += 1
  return x+foo.a
foo.a = 10
Run Code Online (Sandbox Code Playgroud)