Python:静态变量装饰器

时间:2009-10-23 01:51:09

标签: python variables static namespaces decorator

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

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

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

f() #prints 1
f() #prints 2

我不关心static的实施是长期还是黑客,只要它的效果如上所述。

我创建了这个版本,但它只允许使用<function>.<varname>语法,使用更长的函数和变量名称会很快变得很麻烦。

def static(**assignments):
    def decorate(func):
        for var, val in assignments.items():
            setattr(func, var, val)
        return func
    return decorate

我想到的各种事情却无法开展工作:

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

8 个答案:

答案 0 :(得分:8)

当你的装饰者得到函数对象f时,它已经被编译了 - 具体来说,它是在x是本地的知识的情况下编译的(因为它被分配了+= })),正常优化(在2.*中,您可以以惊人的性价格打败优化,通过f开始exec '';在2.*,您不能打败优化)。从本质上讲,要使用您渴望的语法,您必须重新编译f(通过恢复其源,如果您知道它们将在运行时可用,或者更难以通过字节码黑客攻击)以某种方式修改源 - - 一旦你决定采用这种方式,最简单的方法可能是在x的整个f.x中将f更改为global

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

无论哪种方式,我放弃试图将语言歪曲远离其明显的设计意图:即使我确实想出了一些hacky,脆弱的kludge,它无疑将无法维持。在这种情况下,Python的愿望非常明确:在函数内重新绑定的barenames是该函数的本地,除非明确指定为globals - period。所以,你试图制作半号(在一个函数中重新绑定)意味着与“本地人”完全不同的东西正是这种斗争。

编辑:如果你愿意放弃坚持使用 barenames 作为你的“静态”,那么突然之间你就不再与Python作斗争了,而不是语言的“顺其自然”(尽管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() ]的设计故障,但是,这是一个单独的咆哮;-)。所以,例如:

__builtin__

这就像你想要的那样工作,打印1然后打印2.(我正在使用with_static使global最简单地装饰生活在任何模块中的函数,当然)。您可以有几种不同的实现,但任何良好实现的关键点是“静态变量”将是限定名称,不是裸名 - - 明确表示它们不是局部变量,使用语言的粒度,等等。 (类似的内置容器和基于它们的限定名称,已在Python的设计中使用,而不是nonlocalglobvar设计故障,以指示其他类型不是本地变量的变量因此不应该使用裸名...啊,你可以在上面static的同一行上实现一个nonlocal特殊容器,甚至不需要装饰,虽然我不太确定static案例完全可行[也许一些装饰和最小量的黑魔法......; =)]。< / p>

编辑:注释指出当你只修饰一个返回闭包的函数(而不是装饰闭包本身)时,给定的代码不起作用。这是正确的:当然,你必须修饰使用static的特定函数(并且根据函数的定义,只能有一个函数 - static变量!),而不是随机函数。实际上使用def f(): @with_static(x=0) def g(): static.x += 1 print static.x return g x = f() x() x() 但恰好恰好与的那个词汇连接。例如:

f

这有效,而将装饰器移动到g而不是{{1}}则不会(并且不可能)。

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

答案 1 :(得分: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()

输出结果为:

>>> f()
1 10 ...
>>> f()
2 15 ...
>>> f()
3 20 ...

编辑:

我在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

测试:

@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)

输出:

Timeit static function: 5.10412869535
Timeit global variable: 0.242917510783

使用settrace可以大大减慢它的速度。

答案 2 :(得分:6)

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

def static(**kwargs):
  def wrap(f):
    for key, value in kwargs.items():
      setattr(f, key, value)
    return f
  return wrap

使用示例:

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

foo(1)  # => 2
foo(2)  # => 4
foo(14) # => 17

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

def foo(x):
  foo.a += 1
  return x+foo.a
foo.a = 10

答案 3 :(得分:3)

你可以做类似的事情(但我没有对此进行过广泛的测试;使用过CPython 2.6):

import types

def static(**dict):
    def doWrap(func):
        scope = func.func_globals
        scope.update(dict)
        return types.FunctionType(func.func_code, scope)
    return doWrap

# if foo() prints 43, then it's wrong
x = 42

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

foo() # => 1
foo() # => 2

它需要将这些变量声明为全局变量和阴影顶级全局变量,否则应该起作用。但不确定性能。

答案 4 :(得分:1)

如果没有装饰者,这个怎么样?

class State(dict):
    """Object interface to dict."""
    def __getattr__(self, name):
        try:
            return self[name]
        except KeyError:
            raise AttributeError, name

def f(d=State(x=0)):
    d.x += 1
    return d.x

这就是它的实际效果:

>>> f()
1
>>> f()
2
>>> f()
3

答案 5 :(得分:0)

这可能会更加清晰。它不涉及任何装饰者或黑客攻击。

class F( object ):
    def __init__( self ):
        self.x= 0
    def __call__( self ):
        self.x += 1
        print self.x

f= F()

现在你的函数f带有一个静态变量。

f() #prints 1
f() #prints 2

答案 6 :(得分:0)

当您需要在函数调用之间保存状态时,使用生成器/协同程序或对象几乎总是更好。由于你想使用“裸”变量名,所以你需要协程版。

# the coroutine holds the state and yields rather than returns values
def totalgen(x=0, y=0, z=0):
    while True:
       a, b, c = (yield x, y, z)
       x += a
       y += b
       z += c

# the function provides the expected interface to callers
def runningtotal(a, b, c, totalgen=totalgen()):
    try:
        return totalgen.send((a, b, c))    # conveniently gives TypeError 1st time
    except TypeError:
        totalgen.next()                    # initialize, discard results
        return totalgen.send((a, b, c))

结果是一个函数,它累积传递给它的三个值的总和,就像它有静态变量一样,但是累加器是本质上是无限生成器的普通旧局部变量。

答案 7 :(得分:0)

略微调整another anwser

def static(**kwargs):
    def decorator(func):
        return type(func)(func.func_code, dict(func.func_globals, **kwargs))
    return decorator

message = "goodbye, world!"
@static(message="hello, world!")
def hello(): print message

hello()

我发现用函数参数名称覆盖内置名称很蠢,所以我将**dict更改为更规范的**kwargs。我还保存了几行,IMO通过使用dict(the_original, **the_updates)构建一个新的dict来使代码更清晰。最后我通过type(func)访问函数构造函数而不是import ---类型和类对象是工厂方法来保存几行,所以使用它们!

我还删除了global声明。只要你不重新绑定变量,这就有效,即删除global实际上使指针(但不是对象)成为只读。如果您以这种方式使用它,对于这样引入的绑定,let可能比static更好。