views:

285

answers:

8

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.

+4  A: 

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"

John Montgomery
Though obviously this might be a bit much like "magic".
John Montgomery
wouldn't this freeze the state of my_globals to the state of globals at the time func is defined?
hop
not that it matters much...
hop
Nope, my_globals gets re-created each time decorated gets called (which the actual function returned by the decorated). That should mean that you always have the latest globals.
John Montgomery
oh, right, of course. saw but ignored the nested functions. demonstrates why this should be avoided, though.
hop
Well it's just a fairly standard decorator at the end of the day, but yeah messing with globals like this should probably be avoided *normally*. However I could imagine there might be occasions where knowing *how* to would be useful (even if you pursue a better approach later).
John Montgomery
at least the decorator should have a more descriptive name than "my_decorator".
hop
hey - it was that way when I found it ;^)
John Montgomery
+5  A: 

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???

hop
+1: If you want a function that has a "memory" or "cache", define a callable object, use the __call___ method.
S.Lott
Seriously, why would you want to? You want Common Lisp and it's ability to extend the language. Or maybe Ruby can do this. Python is good for many things, but not this.
Aaron
A: 

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.

Toby White
Sounds like you should make a little struct for your 3-tuples.
recursive
Indeed - but I'd still be left with: z_struct = calc(x_struct) for every function, which is more fragile than @precalculate_z
Toby White
btw, this belongs to the question, not a separate answer
hop
Yes, please delete this answer and append it as an EDIT-ADD to your question so other people have a chance to answer your question other than in comments.
Aaron
+1  A: 

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.

kender
+1  A: 

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.

eduffy
+4  A: 

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'.

Aaron
Thanks - this would do what I wanted; but I certainly won't be using it. But at least my curiosity is satisfied now!I'd still like a way to get at some sort of avoiding my copy/paste issue, but I'm not sure how yet - I'll play around some more.
Toby White
A: 

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

cthulahoops
That's where I started, roughly - but if I do this across ten functions, (each of which has a different name) then my foo is named g.foo, h.foo, i.foo, ... in each function. I just want to talk about foo.
Toby White
A: 

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.

Mr Fooz