Python:静态变量装饰器
-
05-07-2019 - |
题
我想创建一个像下面这样的装饰器,但我似乎无法想到一个有效的实现。我开始认为这不可能,但我想我先问你们。
我意识到在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
我想到的各种事情却无法开展工作:
- 将f(装饰函数)更改为可调用类,并以某种方式将静态变量存储在
self
中。 - 在装饰器内修改f()的全局变量,并以某种方式将'global x'语句插入到f的代码中。
- 将f更改为生成器,我们手动绑定变量,然后直接执行f代码。 醇>
解决方案
这是一个似乎有用的装饰器。 请注意,这需要在函数末尾返回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/找到了一些东西。 a>并决定尝试将其添加到此。它现在没有回报。
再次编辑:更改为使其快几秒。 编辑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可以大大减慢它的速度。
其他提示
当你的装饰者得到函数对象f
时,它已经被编译了 - 具体来说,它是在知道x
是本地的(因为它被分配了+=
赋值)的情况下编译的,正常的优化(在2.*
中,您可以以惊人的性价格打败优化,通过exec ''
开始f.x
;在global
中,您无法击败优化)。从本质上讲,要使用您渴望的语法,您必须重新编译nonlocal
(通过恢复其源,如果您知道它们将在运行时可用,或者更难以通过字节码黑客攻击)以及某些修改过的源 - 一次你决定采用这种方式,最简单的方法可能是将__builtin__
改为with_static
整个globvar
。
就个人而言,如果我发现自己在语言(或其他技术)上如此努力地反对我试图屈服于强加我的欲望,我承认我要么使用错误的语言(或其他技术),如果这些欲望绝对至关重要,那么解决方案必须是改变技术;或者,如果这些欲望不那么重要,就放弃它们。
无论哪种方式,我放弃试图将语言歪曲远离其明显的设计意图:即使我确实想出了一些hacky,脆弱的kludge,它无疑将无法维持。在这种情况下,Python的愿望非常明确:在函数内重新绑定的barenames是该函数的本地,除非明确指定为globals - period。因此,您尝试制作裸名(在函数中重新绑定)意味着与<!>“locals <!>”完全不同的东西。正是这种斗争。
修改:如果您愿意放弃坚持使用裸名>> <!>“静态<!>”,那么突然之间“不再与Python作斗争,而是<!>”与谷物一起使用<!>该语言(尽管static
[和g
]的设计故障,但是,这是一个单独的咆哮;-)。所以,例如:
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()
这就像你想要的那样,打印1然后打印2.(我正在使用<=>使其最简单地使用<=>来装饰生活在任何模块中的功能,当然)。您可以有几种不同的实现,但任何良好实现的关键点是<!>“静态变量<!>”;将是合格的名称,不裸名 - 明确表示它们不是局部变量,使用语言的粒度,等等。 (类似的内置容器和基于它们的限定名称,应该已经在Python的设计中使用,而不是<=>和<=>设计故障,以指示其他类型的变量本地的,因此不应该使用裸名...啊,你可以在上面<=>的同一行上实现一个<=>特殊的容器,甚至不需要装饰,虽然我不是这样的确定这对<=>案例完全可行[也许带一些装饰和最小量的黑魔法......; =)]。
编辑:注释指出当你只修饰一个返回闭包的函数(而不是装饰闭包本身)时,给定的代码不起作用。这是正确的:当然,你必须装饰使用<=>的特定函数(并且只能有一个,通过函数的定义 - <=>变量!),而不是实际上不使用的随机函数<=>但恰好恰好与做的那个词汇连接。例如:
def f():
@with_static(x=0)
def g():
static.x += 1
print static.x
return g
x = f()
x()
x()
这是有效的,同时将装饰器移动到<=>而不是<=>不会(并且不可能)。
如果实际的需求不是关于静态变量(仅在单个函数中可见和可用),而是某些混合物可以在某个特定的bu中使用需要非常精确地指定的功能空间(并且毫无疑问,根据实际规格 的具体情况,实施方式有所不同) - 理想情况下需要在新的和单独的SO问题中进行,因为这个(特别是关于静态)而且这个特定问题的答案已经足够大了。
这是一个非常简单的解决方案,就像普通的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
你可以做类似的事情(但我没有对此进行过广泛的测试;使用过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
它需要将这些变量声明为全局变量和阴影顶级全局变量,否则应该起作用。但不确定性能。
如果没有装饰者,这个怎么样?
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
这可能会更加清晰。它不涉及任何装饰者或黑客攻击。
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
当您需要在函数调用之间保存状态时,使用生成器/协同程序或对象几乎总是更好。因为你想使用<!> quot; bare <!> quot;变量名,然后你会想要协程版。
# 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))
结果是一个函数,它累积传递给它的三个值的总和,就像它有静态变量一样,但是累加器是本质上是无限生成器的普通旧局部变量。
稍微调整一下另一个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
声明。只要你没有重新绑定变量就行了,即删除let
实际上使指针(但不是对象)成为只读。如果你以这种方式使用它,那么static
对于这样引入的绑定来说可能比<=>更好。