Can I use a decorator to mutate the local scope of a function in Python?

怎甘沉沦 提交于 2019-12-21 04:13:32

问题


Is there any way of writing a decorator such that the following would work?

assert 'z' not in globals()

@my_decorator
def func(x, y):
   print z

EDIT: moved from anwser

In answer to hop's "why?": syntax sugar / DRY.

It's not about caching, it's about calculating z (and z1, z2, z3, ...) based upon the values of x & y.

I have lots of functions which do related things, and I don't want to do have to write

z1, z2, z3=calculate_from(x, y)

at the beginning of every single function - I'll get it wrong somewhere. If this were c I'd do this with cpp (if this were lisp, I'd do this with macros ...), but I wanted to see if decorators could do the same thing.

If it helps, I'd almost certainly call the decorator "precalculate_z", and it certainly wouldn't be part of any public API.

I could probably get a similar effect from using the class infrastructure as well, but I wanted to see if it was doable with raw functions.


回答1:


Echoing Hop's answer

  1. Don't do it.
  2. Seriously, don't do this. Lisp and Ruby are more appropriate languages for writing your own custom syntax. Use one of those. Or find a cleaner way to do this
  3. If you must, you want dynamic scoped variables, not lexically scoped.

Python doesn't have dynamically scoped variables, but you can simulate it. Here's an example that simulates it by creating a global binding, but restores the previous value on exit:

http://codepad.org/6vAY8Leh

def adds_dynamic_z_decorator(f):
  def replacement(*arg,**karg):
    # create a new 'z' binding in globals, saving previous
    if 'z' in globals():
      oldZ = (globals()['z'],)
    else:
      oldZ = None
    try:
      globals()['z'] = None
      #invoke the original function
      res = f(*arg, **karg)
    finally:
      #restore any old bindings
      if oldZ:
        globals()['z'] = oldZ[0]
      else:
        del(globals()['z'])
    return res
  return replacement

@adds_dynamic_z_decorator
def func(x,y):
  print z

def other_recurse(x):
  global z
  print 'x=%s, z=%s' %(x,z)
  recurse(x+1)
  print 'x=%s, z=%s' %(x,z)

@adds_dynamic_z_decorator
def recurse(x=0):
  global z
  z = x
  if x < 3:
    other_recurse(x)

print 'calling func(1,2)'
func(1,2)

print 'calling recurse()'
recurse()

I make no warranties on the utility or sanity of the above code. Actually, I warrant that it is insane, and you should avoid using it unless you want a flogging from your Python peers.

This code is similar to both eduffy's and John Montgomery's code, but ensures that 'z' is created and properly restored "like" a local variable would be -- for instance, note how 'other_recurse' is able to see the binding for 'z' specified in the body of 'recurse'.




回答2:


I don't know about the local scope, but you could provide an alternative global name space temporarily. Something like:



import types

def my_decorator(fn):
    def decorated(*args,**kw):
        my_globals={}
        my_globals.update(globals())
        my_globals['z']='value of z'
        call_fn=types.FunctionType(fn.func_code,my_globals)
        return call_fn(*args,**kw)
    return decorated

@my_decorator
def func(x, y):
    print z

func(0,1)


Which should print "value of z"




回答3:


a) don't do it.

b) seriously, why would you do that?

c) you could declare z as global within your decorator, so z will not be in globals() until after the decorator has been called for the first time, so the assert won't bark.

d) why???




回答4:


I'll first echo the "please don't", but that's your choice. Here's a solution for you:

assert 'z' not in globals ()

class my_dec:
    def __init__ (self, f):
        self.f = f
    def __call__ (self,x,y):
        z = x+y
        self.f(x,y,z)

@my_dec
def func (x,y,z):
    print z

func (1,3)

It does require z in the formal parameters, but not the actual.




回答5:


I could probably get a similar effect from using the class infrastructure as well, but I wanted to see if it was doable with raw functions.

Well, Python is an object-oriented language. You should do this in a class, in my opinion. Making a nice class interface would surely simplify your problem. This isn't what decorators were made for.




回答6:


Explicit is better than implicit.

Is this good enough?

def provide_value(f):
    f.foo = "Bar"
    return f

@provide_value
def g(x):
    print g.foo

(If you really want evil, assigning to f.func_globals seems fun.)




回答7:


Others have given a few ways of making a working decorator, many have advised against doing so because it's so stylistically different from normal python behavior that it'll really confuse anyone trying to understand the code.

If you're needing to recalculate things a lot, would it make sense to group them together in an object? Compute z1...zN in the constructor, then the functions that use these values can access the pre-computed answers as part of the instance.



来源:https://stackoverflow.com/questions/591200/can-i-use-a-decorator-to-mutate-the-local-scope-of-a-function-in-python

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!