setting function properties with a decorator syntax

Decorators are great, they make this: def some_func(*args, **kwargs): return "sup" some_func = decorator(some_func) into this: @decorator def some_func(*args, **kwargs): return "sup" which makes the code look more structured. The line at the bottom looks more like it 'belongs' to the function when it's at the top of the def block, as opposed to the bottom. But when it comes to function properties, it'd be nice if that same pattern was availiable: def some_func(*args, **kwargs): return "sup" some_func.printable = True becomes: @printable=True def some_func(*args, **kwargs): return "sup" Any thoughts?

On Wed, Mar 17, 2010 at 11:49 PM, nbv4 <nbvfour@gmail.com> wrote:
Are function attributes used *that* much? Cheers, Chris -- http://blog.rebertia.com

On Thu, 18 Mar 2010 00:19:40 -0700 Chris Rebert <pyideas@rebertia.com> wrote:
Are function attributes used *that* much?
I use them as a nice (& explicit, & clear) alternative to closures, esp. for func factories, eg: def powerN(n): def f(x): return x ** f.n f.n = n return f power3 = powerN(3) for i in range(1,10): print "%s:%s" %(i,power3(i)), # ==> 1:1 2:8 3:27 4:64 5:125 6:216 7:343 8:512 9:729 I find this conceptually much more satisfying than the following, since the parameter really is an attribute of the function (also, like a real upvalue, it can be explicetely updated). def powerN(n): def f(x,n=n): # ugly ;-) return x ** n return f (Let us use the opportunity that python funcs are real objects :-) A "parameterisable" generator factory: def powers(n, start=1, stop=None): def f(): while True: f.i += 1 if f.stop and f.i >= f.stop: raise StopIteration yield (f.i, f.i ** f.n) f.n = n f.i = start-1 f.stop = stop return f for (i,x) in powers(3, 1,10)(): print "%s:%s" %(i,x), # ==> 1:1 2:8 3:27 4:64 5:125 6:216 7:343 8:512 9:729 Denis ________________________________ vit e estrany spir.wikidot.com

spir wrote:
A "parameterisable" generator factory: [...]
Good lord, no! Nooo! "power" is one thing with one semantic meaning "range" is another thing with another semantic meaning There is absolutely no excuse for mixing the two implementation into a "power-range" monstrosity. Implement the two separately and make some custom function composition like: def pow(x, n): return x**n def pow_o_range(n, a, b): return [(x, pow(x, n)) for x in range(a,b)] for i, x in pow_o_range(3, 1, 10): print "%s:%s" % (i,x), # prints "1:1 2:8 3:27 4:64 5:125 6:216 7:343 8:512 9:729"

For anyone finding this on a later search, note that he still uses a closure; just a slightly neater one. Within a function, there is no way to refer to the function itself, except by name -- and that name may well have been reassigned to something else. A more local enclosure protects against that reassignment. -jJ On 3/18/10, spir <denis.spir@gmail.com> wrote:

On Thu, Mar 18, 2010 at 2:49 AM, nbv4 <nbvfour@gmail.com> wrote:
If you have that many properties, there's no need to extend the syntax, just do something like: def prop(**props): def wee(f): f.__dict__.update(props) return f return wee @prop(printable=True, other_prop="yo!") def some_func(): return "sup, %s" % some_func.other_prop print some_func() # prints "sup, yo!"

On Thu, Mar 18, 2010 at 2:49 AM, nbv4 <nbvfour@gmail.com> wrote:
I don't remember seeing much code using function properties. I would probably just create a new decorator for this. >>> def add_props(**kwargs): ... def _(func): ... for key in kwargs: ... setattr(func, key, kwargs[key]) ... return func ... return _ ... >>> @add_props(printable=True) ... def f(): ... """Example function""" ... >>> f.printable True >>> -- David blog: http://www.traceback.org twitter: http://twitter.com/dstanek

On Thu, Mar 18, 2010 at 10:28 PM, David Stanek <dstanek@dstanek.com> wrote:
I don't remember seeing much code using function properties. I would probably just create a new decorator for this.
I've not either, but if we determine that this is a useful facility to include in the standard library, I'd be in favor of something like this: @setattr(printable=True) def f(): """Example function""" -Fred -- Fred L. Drake, Jr. <fdrake at gmail.com> "Chaos is the score upon which reality is written." --Henry Miller

On 3/18/2010 10:33 PM, Fred Drake wrote:
For the sake of a head count, I have used function attributes for the purpose of memoizing computations (both return values and intermediate calculations). A representative example: @setattr(primes=set(), composites=set()) def is_prime(n): if n <= 0: raise ValueError(n) elif n == 1: return True elif n in is_prime.primes: return True elif n in is_prime.composites: return False for p in is_prime.primes: if n % p == 0: is_prime.composites.add(n) return False is_prime.primes.add(n) return True But honestly, the biggest problem I have with this pattern is that you explicitly have to say the name of the function since there is no "self" equivalent. I think it would be more maintenance-friendly to do: def _(n): primes = set() composites = set() def is_prime(n): if n <= 0: raise ValueError(n) elif n == 1: return True elif n in primes: return True elif n in composites: return False for p in primes: if n % p == 0: composites.add(n) return False primes.add(n) return True return is_prime is_prime = _() Which is to say, I would be more amenable to adding such a feature if it was building closure. And in that usage, it would seem to be more generally useful and well-behaved.. outsiders don't normally modify closures (if you want this sort of interface, you probably should define a class).. it allows you to avoid naming the function in the function.. and it is a much closer substitute for the default arguments kludge that is so commonly abused. @closure(primes=set(), composites=set()) def is_prime(n): if n <= 0: raise ValueError(n) elif n == 1: return True elif n in primes: return True elif n in composites: return False for p in primes: if n % p == 0: composites.add(n) return False primes.add(n) return True But, that's just my two cents. -- Scott Dial scott@scottdial.com scodial@cs.indiana.edu

On 3/19/2010 4:25 AM, Arnaud Delobelle wrote:
My problem with this particular kludge is that rarely are these arguments meaningful as *arguments*. They become a part of the function definition that shows up in documentation. Worse to me is that the keyword names block those names from appearing in **kwds, which is problematic for creating a transparent function (e.g., when writing a decorator that takes an unknown set of keyword arguments) and evokes dynamic-scoping madness. Additionally, they are a source of subtle errors when extra arguments are passed to functions (although this is mitigated mostly by Py3's keyword-only arguments syntax). All of those problems go away with a magical @closure() decorator, but I realize that creating such a decorator would be quite a kludge in-and-of-itself. Perhaps it's a bit too clever. -- Scott Dial scott@scottdial.com scodial@cs.indiana.edu

On 19 March 2010 09:07, Scott Dial <scott+python-ideas@scottdial.com> wrote:
I wrote such a decorator a while ago, I called it 'bind'. It was indeed quite a kludge, but if you're interested I can post it. It worked by rewriting the bytecode of the function it decorated. If I remember correctly, it has some limitations, e.g. it didn't work with local functions i.e. @bind(x=3) def f(): def g(): return x return g f()() would return the current value of the global variable 'x' rather than 3. I don't think I got round to solving this (it would require looking at the constants in the code which are themselves code objects an apply the same bytecode transformation recursively). Also, it was for Python 2.X so I don't know if it would work as-is in Python 3. -- Arnaud

On Wed, Mar 17, 2010 at 11:49 PM, nbv4 <nbvfour@gmail.com> wrote:
Are function attributes used *that* much? Cheers, Chris -- http://blog.rebertia.com

On Thu, 18 Mar 2010 00:19:40 -0700 Chris Rebert <pyideas@rebertia.com> wrote:
Are function attributes used *that* much?
I use them as a nice (& explicit, & clear) alternative to closures, esp. for func factories, eg: def powerN(n): def f(x): return x ** f.n f.n = n return f power3 = powerN(3) for i in range(1,10): print "%s:%s" %(i,power3(i)), # ==> 1:1 2:8 3:27 4:64 5:125 6:216 7:343 8:512 9:729 I find this conceptually much more satisfying than the following, since the parameter really is an attribute of the function (also, like a real upvalue, it can be explicetely updated). def powerN(n): def f(x,n=n): # ugly ;-) return x ** n return f (Let us use the opportunity that python funcs are real objects :-) A "parameterisable" generator factory: def powers(n, start=1, stop=None): def f(): while True: f.i += 1 if f.stop and f.i >= f.stop: raise StopIteration yield (f.i, f.i ** f.n) f.n = n f.i = start-1 f.stop = stop return f for (i,x) in powers(3, 1,10)(): print "%s:%s" %(i,x), # ==> 1:1 2:8 3:27 4:64 5:125 6:216 7:343 8:512 9:729 Denis ________________________________ vit e estrany spir.wikidot.com

spir wrote:
A "parameterisable" generator factory: [...]
Good lord, no! Nooo! "power" is one thing with one semantic meaning "range" is another thing with another semantic meaning There is absolutely no excuse for mixing the two implementation into a "power-range" monstrosity. Implement the two separately and make some custom function composition like: def pow(x, n): return x**n def pow_o_range(n, a, b): return [(x, pow(x, n)) for x in range(a,b)] for i, x in pow_o_range(3, 1, 10): print "%s:%s" % (i,x), # prints "1:1 2:8 3:27 4:64 5:125 6:216 7:343 8:512 9:729"

For anyone finding this on a later search, note that he still uses a closure; just a slightly neater one. Within a function, there is no way to refer to the function itself, except by name -- and that name may well have been reassigned to something else. A more local enclosure protects against that reassignment. -jJ On 3/18/10, spir <denis.spir@gmail.com> wrote:

On Thu, Mar 18, 2010 at 2:49 AM, nbv4 <nbvfour@gmail.com> wrote:
If you have that many properties, there's no need to extend the syntax, just do something like: def prop(**props): def wee(f): f.__dict__.update(props) return f return wee @prop(printable=True, other_prop="yo!") def some_func(): return "sup, %s" % some_func.other_prop print some_func() # prints "sup, yo!"

On Thu, Mar 18, 2010 at 2:49 AM, nbv4 <nbvfour@gmail.com> wrote:
I don't remember seeing much code using function properties. I would probably just create a new decorator for this. >>> def add_props(**kwargs): ... def _(func): ... for key in kwargs: ... setattr(func, key, kwargs[key]) ... return func ... return _ ... >>> @add_props(printable=True) ... def f(): ... """Example function""" ... >>> f.printable True >>> -- David blog: http://www.traceback.org twitter: http://twitter.com/dstanek

On Thu, Mar 18, 2010 at 10:28 PM, David Stanek <dstanek@dstanek.com> wrote:
I don't remember seeing much code using function properties. I would probably just create a new decorator for this.
I've not either, but if we determine that this is a useful facility to include in the standard library, I'd be in favor of something like this: @setattr(printable=True) def f(): """Example function""" -Fred -- Fred L. Drake, Jr. <fdrake at gmail.com> "Chaos is the score upon which reality is written." --Henry Miller

On 3/18/2010 10:33 PM, Fred Drake wrote:
For the sake of a head count, I have used function attributes for the purpose of memoizing computations (both return values and intermediate calculations). A representative example: @setattr(primes=set(), composites=set()) def is_prime(n): if n <= 0: raise ValueError(n) elif n == 1: return True elif n in is_prime.primes: return True elif n in is_prime.composites: return False for p in is_prime.primes: if n % p == 0: is_prime.composites.add(n) return False is_prime.primes.add(n) return True But honestly, the biggest problem I have with this pattern is that you explicitly have to say the name of the function since there is no "self" equivalent. I think it would be more maintenance-friendly to do: def _(n): primes = set() composites = set() def is_prime(n): if n <= 0: raise ValueError(n) elif n == 1: return True elif n in primes: return True elif n in composites: return False for p in primes: if n % p == 0: composites.add(n) return False primes.add(n) return True return is_prime is_prime = _() Which is to say, I would be more amenable to adding such a feature if it was building closure. And in that usage, it would seem to be more generally useful and well-behaved.. outsiders don't normally modify closures (if you want this sort of interface, you probably should define a class).. it allows you to avoid naming the function in the function.. and it is a much closer substitute for the default arguments kludge that is so commonly abused. @closure(primes=set(), composites=set()) def is_prime(n): if n <= 0: raise ValueError(n) elif n == 1: return True elif n in primes: return True elif n in composites: return False for p in primes: if n % p == 0: composites.add(n) return False primes.add(n) return True But, that's just my two cents. -- Scott Dial scott@scottdial.com scodial@cs.indiana.edu

On 3/19/2010 4:25 AM, Arnaud Delobelle wrote:
My problem with this particular kludge is that rarely are these arguments meaningful as *arguments*. They become a part of the function definition that shows up in documentation. Worse to me is that the keyword names block those names from appearing in **kwds, which is problematic for creating a transparent function (e.g., when writing a decorator that takes an unknown set of keyword arguments) and evokes dynamic-scoping madness. Additionally, they are a source of subtle errors when extra arguments are passed to functions (although this is mitigated mostly by Py3's keyword-only arguments syntax). All of those problems go away with a magical @closure() decorator, but I realize that creating such a decorator would be quite a kludge in-and-of-itself. Perhaps it's a bit too clever. -- Scott Dial scott@scottdial.com scodial@cs.indiana.edu

On 19 March 2010 09:07, Scott Dial <scott+python-ideas@scottdial.com> wrote:
I wrote such a decorator a while ago, I called it 'bind'. It was indeed quite a kludge, but if you're interested I can post it. It worked by rewriting the bytecode of the function it decorated. If I remember correctly, it has some limitations, e.g. it didn't work with local functions i.e. @bind(x=3) def f(): def g(): return x return g f()() would return the current value of the global variable 'x' rather than 3. I don't think I got round to solving this (it would require looking at the constants in the code which are themselves code objects an apply the same bytecode transformation recursively). Also, it was for Python 2.X so I don't know if it would work as-is in Python 3. -- Arnaud
participants (11)
-
Andrey Fedorov
-
Arnaud Delobelle
-
Chris Rebert
-
David Stanek
-
Fred Drake
-
Greg Ewing
-
Jim Jewett
-
MRAB
-
nbv4
-
Scott Dial
-
spir