Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #29145 > unrolled thread

Re: Decorators not worth the effort

Started byJean-Michel Pichavant <jeanmichel@sequans.com>
First post2012-09-14 11:28 +0200
Last post2012-09-14 13:49 +0200
Articles 17 — 11 participants

Back to article view | Back to comp.lang.python


Contents

  Re: Decorators not worth the effort Jean-Michel Pichavant <jeanmichel@sequans.com> - 2012-09-14 11:28 +0200
    Re: Decorators not worth the effort Duncan Booth <duncan.booth@invalid.invalid> - 2012-09-14 11:26 +0000
      Re: Decorators not worth the effort andrea crotti <andrea.crotti.0@gmail.com> - 2012-09-14 15:12 +0100
      Re: Decorators not worth the effort Chris Angelico <rosuav@gmail.com> - 2012-09-15 00:41 +1000
        Re: Decorators not worth the effort 88888 Dihedral <dihedral88888@googlemail.com> - 2012-09-14 09:37 -0700
        Re: Decorators not worth the effort 88888 Dihedral <dihedral88888@googlemail.com> - 2012-09-14 09:37 -0700
      Re: Decorators not worth the effort andrea crotti <andrea.crotti.0@gmail.com> - 2012-09-14 17:15 +0100
      Re: Decorators not worth the effort Chris Angelico <rosuav@gmail.com> - 2012-09-15 03:30 +1000
        Re: Decorators not worth the effort Neil Cerutti <neilc@norwich.edu> - 2012-09-18 13:19 +0000
          Re: Decorators not worth the effort Chris Angelico <rosuav@gmail.com> - 2012-09-18 23:25 +1000
            Re: Decorators not worth the effort 88888 Dihedral <dihedral88888@googlemail.com> - 2012-09-18 08:14 -0700
            Re: Decorators not worth the effort 88888 Dihedral <dihedral88888@googlemail.com> - 2012-09-18 08:14 -0700
      RE: Decorators not worth the effort "Prasad, Ramit" <ramit.prasad@jpmorgan.com> - 2012-09-14 23:14 +0000
    Re: Decorators not worth the effort Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2012-09-14 12:01 +0000
      Re: Decorators not worth the effort Tim Chase <python.list@tim.thechases.com> - 2012-09-14 08:06 -0500
        Re: Decorators not worth the effort Steve Howell <showell30@yahoo.com> - 2012-09-14 18:13 -0700
    Re: Decorators not worth the effort Ulrich Eckhardt <ulrich.eckhardt@dominolaser.com> - 2012-09-14 13:49 +0200

#29145 — Re: Decorators not worth the effort

FromJean-Michel Pichavant <jeanmichel@sequans.com>
Date2012-09-14 11:28 +0200
SubjectRe: Decorators not worth the effort
Message-ID<mailman.690.1347614888.27098.python-list@python.org>
----- Original Message -----
> On Sep 14, 3:54 am, Jean-Michel Pichavant <jeanmic...@sequans.com>
> wrote:
> > I don't like decorators, I think they're not worth the mental
> > effort.
> 
> Because passing a function to a function is a huge cognitive burden?
> --
> http://mail.python.org/mailman/listinfo/python-list
> 

I was expecting that. Decorators are very popular so I kinda already know that the fault is mine. Now to the reason why I have troubles writing them, I don't know. Every time I did use decorators, I spent way too much time writing it (and debugging it).

I wrote the following one, used to decorate any function that access an equipment, it raises an exception when the timeout expires. The timeout is adapted to the platform, ASIC of FPGA so people don't need to specify everytime one timeout per platform.

In the end it would replace 

def boot(self, timeout=15):
    if FPGA:
        self.sendCmd("bootMe", timeout=timeout*3)
    else:
        self.sendCmd("bootMe", timeout=timeout)

with

@timeout(15)
def boot(self, timeout=None):
    self.sendCmd("bootMe", timeout)

I wrote a nice documentation with sphinx to explain this, how to use it, how it can improve code. After spending hours on the decorator + doc, feedback from my colleagues : What the F... !! 

Decorators are very python specific (probably exists in any dynamic language though, I don't know), in some environment where people need to switch from C to python everyday, decorators add python magic that not everyone is familiar with. For example everyone in the team is able to understand and debug the undecorated version of the above boot method. I'm the only one capable of reading the decorated version. And don't flame my colleagues, they're amazing people (just in case they're reading this :p) who are not python developers, more of users.

Hence my original "decorators are not worth the mental effort". Context specific I must admit.

Cheers,

JM

PS : Here's the decorator, just to give you an idea about how it looks. Small piece of code, but took me more than 2 hours to write it. I removed some sensible parts so I don't expect it to run.

class timeout(object):
        """Substitute the timeout keyword argument with the appropriate value"""
	FACTORS = {
		IcebergConfig().platform.ASIC : 1,
		IcebergConfig().platform.FPGA : 3,
			}

	def __init__(self, asic, fpga=None, palladium=None):
		self.default = asic
		self.fpga = fpga
	
	def _getTimeout(self):
		platform = config().platform
		factor = self.FACTORS[platform.value]
		timeout = {
				platform.ASIC : self.default*factor,
				platform.FPGA : self.fpga or self.default*factor,
				}[platform.value]
		return timeout

	def __call__(self, func):
		def decorated(*args, **kwargs):
			names, _, _, defaults =  inspect.getargspec(func)
			defaults = defaults or []
			if 'timeout' not in names:
				raise ValueError('A "timeout" keyword argument is required')
			if 'timeout' not in kwargs: # means the timeout keyword arg is not in the call
				index = names.index('timeout')
				argsLength = (len(names) - len(defaults))
				if index < argsLength:
					raise NotImplementedError('This decorator does not support non keyword "timeout" argument')
				if index > len(args)-1: # means the timeout has not be passed using a pos argument
					timeoutDef = defaults[index-argsLength]
					if timeoutDef is not None:
						_log.warning("Decorating a function with a default timeout value <> None")
					kwargs['timeout'] = self._getTimeout()
			else:
				_log.warning('Timeout value specified during the call, please check "%s" @timeout decorator.' % func.__name__)
			ret = func(*args, **kwargs)
			return ret
		return decorated

[toc] | [next] | [standalone]


#29155

FromDuncan Booth <duncan.booth@invalid.invalid>
Date2012-09-14 11:26 +0000
Message-ID<XnsA0CE7D6F43F18duncanbooth@127.0.0.1>
In reply to#29145
Jean-Michel Pichavant <jeanmichel@sequans.com> wrote:

> I wrote the following one, used to decorate any function that access
> an equipment, it raises an exception when the timeout expires. The
> timeout is adapted to the platform, ASIC of FPGA so people don't need
> to specify everytime one timeout per platform. 
> 
> In the end it would replace 
> 
> def boot(self, timeout=15):
>     if FPGA:
>         self.sendCmd("bootMe", timeout=timeout*3)
>     else:
>         self.sendCmd("bootMe", timeout=timeout)
> 
> with
> 
> @timeout(15)
> def boot(self, timeout=None):
>     self.sendCmd("bootMe", timeout)
> 
> I wrote a nice documentation with sphinx to explain this, how to use
> it, how it can improve code. After spending hours on the decorator +
> doc, feedback from my colleagues : What the F... !! 
> 

I'd agree with your colleagues. How are you going to ensure that all 
relevant functions are decorated and yet no decorated function ever 
calls another decorated one?

From the code you posted it would seem appropriate that the adjustment 
of the timeout parameter happen in the `sendCmd()` method itself and 
nowhere else. Alternatively use named values for different categories of 
timeouts and adjust them on startup so instead of a default of `timeout=
15` you would have a default `timeout=MEDIUM_TIMEOUT` or whatever name 
is appropriate.

-- 
Duncan Booth http://kupuguy.blogspot.com

[toc] | [prev] | [next] | [standalone]


#29167

Fromandrea crotti <andrea.crotti.0@gmail.com>
Date2012-09-14 15:12 +0100
Message-ID<mailman.704.1347631954.27098.python-list@python.org>
In reply to#29155
I think one very nice and simple example of how decorators can be used is this:

def memoize(f, cache={}, *args, **kwargs):
    def _memoize(*args, **kwargs):
        key = (args, str(kwargs))
        if not key in cache:
            cache[key] = f(*args, **kwargs)

        return cache[key]

    return _memoize

def fib(n):
    if n <= 1:
        return 1
    return fib(n-1) + fib(n-2)

@memoize
def fib_memoized(n):
    if n <= 1:
        return 1
    return fib_memoized(n-1) + fib_memoized(n-2)


The second fibonacci looks exactly the same but while the first is
very slow and would generate a stack overflow the second doesn't..

I might use this example for the presentation, before explaining what it is..

[toc] | [prev] | [next] | [standalone]


#29168

FromChris Angelico <rosuav@gmail.com>
Date2012-09-15 00:41 +1000
Message-ID<mailman.705.1347633665.27098.python-list@python.org>
In reply to#29155
On Sat, Sep 15, 2012 at 12:12 AM, andrea crotti
<andrea.crotti.0@gmail.com> wrote:
> def fib(n):
>     if n <= 1:
>         return 1
>     return fib(n-1) + fib(n-2)
>
> @memoize
> def fib_memoized(n):
>     if n <= 1:
>         return 1
>     return fib_memoized(n-1) + fib_memoized(n-2)
>
>
> The second fibonacci looks exactly the same but while the first is
> very slow and would generate a stack overflow the second doesn't..

Trouble is, you're starting with a pretty poor algorithm. It's easy to
improve on what's poor. Memoization can still help, but I would start
with a better algorithm, such as:

def fib(n):
	if n<=1: return 1
	a,b=1,1
	for i in range(1,n,2):
		a+=b
		b+=a
	return b if n%2 else a

def fib(n,cache=[1,1]):
	if n<=1: return 1
	while len(cache)<=n:
		cache.append(cache[-1] + cache[-2])
	return cache[n]

Personally, I don't mind (ab)using default arguments for caching, but
you could do the same sort of thing with a decorator if you prefer. I
think the non-decorated non-recursive version is clear and efficient
though.

ChrisA

[toc] | [prev] | [next] | [standalone]


#29180

From88888 Dihedral <dihedral88888@googlemail.com>
Date2012-09-14 09:37 -0700
Message-ID<067b0f14-0d30-49b8-a937-554a571c0ef4@googlegroups.com>
In reply to#29168
Chris Angelico於 2012年9月14日星期五UTC+8下午10時41分06秒寫道:
> On Sat, Sep 15, 2012 at 12:12 AM, andrea crotti
> 
> <andrea.crotti.0@gmail.com> wrote:
> 
> > def fib(n):
> 
> >     if n <= 1:
> 
> >         return 1
> 
> >     return fib(n-1) + fib(n-2)
> 
> >
> 
> > @memoize
> 
> > def fib_memoized(n):
> 
> >     if n <= 1:
> 
> >         return 1
> 
> >     return fib_memoized(n-1) + fib_memoized(n-2)
> 
> >
> 
> >
> 
> > The second fibonacci looks exactly the same but while the first is
> 
> > very slow and would generate a stack overflow the second doesn't..
> 
> 
> 
> Trouble is, you're starting with a pretty poor algorithm. It's easy to
> 
> improve on what's poor. Memoization can still help, but I would start
> 
> with a better algorithm, such as:
> 
> 
> 
> def fib(n):
> 
> 	if n<=1: return 1
> 
> 	a,b=1,1
> 
> 	for i in range(1,n,2):
> 
> 		a+=b
> 
> 		b+=a
> 
> 	return b if n%2 else a
> 
> 
> 
> def fib(n,cache=[1,1]):
> 
> 	if n<=1: return 1
> 
> 	while len(cache)<=n:
> 
> 		cache.append(cache[-1] + cache[-2])
> 
> 	return cache[n]
> 
> 
> 
> Personally, I don't mind (ab)using default arguments for caching, but
> 
> you could do the same sort of thing with a decorator if you prefer. I
> 
> think the non-decorated non-recursive version is clear and efficient
> 
> though.
> 
> 
> 
> ChrisA

Uhn, the decorator part is good for wrapping functions in python.

For example a decorator can be used  to add a layor of some
message handlings of  those  plain functions
to become iterators which could be used as call back functions
in a more elegant way.


[toc] | [prev] | [next] | [standalone]


#29181

From88888 Dihedral <dihedral88888@googlemail.com>
Date2012-09-14 09:37 -0700
Message-ID<mailman.714.1347640666.27098.python-list@python.org>
In reply to#29168
Chris Angelico於 2012年9月14日星期五UTC+8下午10時41分06秒寫道:
> On Sat, Sep 15, 2012 at 12:12 AM, andrea crotti
> 
> <andrea.crotti.0@gmail.com> wrote:
> 
> > def fib(n):
> 
> >     if n <= 1:
> 
> >         return 1
> 
> >     return fib(n-1) + fib(n-2)
> 
> >
> 
> > @memoize
> 
> > def fib_memoized(n):
> 
> >     if n <= 1:
> 
> >         return 1
> 
> >     return fib_memoized(n-1) + fib_memoized(n-2)
> 
> >
> 
> >
> 
> > The second fibonacci looks exactly the same but while the first is
> 
> > very slow and would generate a stack overflow the second doesn't..
> 
> 
> 
> Trouble is, you're starting with a pretty poor algorithm. It's easy to
> 
> improve on what's poor. Memoization can still help, but I would start
> 
> with a better algorithm, such as:
> 
> 
> 
> def fib(n):
> 
> 	if n<=1: return 1
> 
> 	a,b=1,1
> 
> 	for i in range(1,n,2):
> 
> 		a+=b
> 
> 		b+=a
> 
> 	return b if n%2 else a
> 
> 
> 
> def fib(n,cache=[1,1]):
> 
> 	if n<=1: return 1
> 
> 	while len(cache)<=n:
> 
> 		cache.append(cache[-1] + cache[-2])
> 
> 	return cache[n]
> 
> 
> 
> Personally, I don't mind (ab)using default arguments for caching, but
> 
> you could do the same sort of thing with a decorator if you prefer. I
> 
> think the non-decorated non-recursive version is clear and efficient
> 
> though.
> 
> 
> 
> ChrisA

Uhn, the decorator part is good for wrapping functions in python.

For example a decorator can be used  to add a layor of some
message handlings of  those  plain functions
to become iterators which could be used as call back functions
in a more elegant way.


[toc] | [prev] | [next] | [standalone]


#29178

Fromandrea crotti <andrea.crotti.0@gmail.com>
Date2012-09-14 17:15 +0100
Message-ID<mailman.712.1347639338.27098.python-list@python.org>
In reply to#29155
2012/9/14 Chris Angelico <rosuav@gmail.com>:
>
> Trouble is, you're starting with a pretty poor algorithm. It's easy to
> improve on what's poor. Memoization can still help, but I would start
> with a better algorithm, such as:
>
> def fib(n):
>         if n<=1: return 1
>         a,b=1,1
>         for i in range(1,n,2):
>                 a+=b
>                 b+=a
>         return b if n%2 else a
>
> def fib(n,cache=[1,1]):
>         if n<=1: return 1
>         while len(cache)<=n:
>                 cache.append(cache[-1] + cache[-2])
>         return cache[n]
>
> Personally, I don't mind (ab)using default arguments for caching, but
> you could do the same sort of thing with a decorator if you prefer. I
> think the non-decorated non-recursive version is clear and efficient
> though.
>
> ChrisA
> --
> http://mail.python.org/mailman/listinfo/python-list


The poor algorithm is much more close to the mathematical definition
than the smarter iterative one..  And in your second version you
include some ugly caching logic inside it, so why not using a
decorator then?

I'm not saying that with the memoization is the "good" solution, just
that I think it's a very nice example of how to use a decorator, and
maybe a good example to start with a talk on decorators..

[toc] | [prev] | [next] | [standalone]


#29182

FromChris Angelico <rosuav@gmail.com>
Date2012-09-15 03:30 +1000
Message-ID<mailman.715.1347643815.27098.python-list@python.org>
In reply to#29155
On Sat, Sep 15, 2012 at 2:15 AM, andrea crotti
<andrea.crotti.0@gmail.com> wrote:
> The poor algorithm is much more close to the mathematical definition
> than the smarter iterative one..  And in your second version you
> include some ugly caching logic inside it, so why not using a
> decorator then?

I learned Fibonacci as a sequence, not as a recursive definition. So
the algorithm I coded (the non-caching one) is pretty much how I
learned it in mathematics. But yes, you're right that the caching is
inherent to the second version; and yes, that's where a decorator can
make it a LOT cleaner.

As a demo of recursion and decorators, your original function pair is
definitely the best. But if you want to be able to calculate fib(n)
for any n without blowing your stack, my version will scale much more
safely.

But then again, who actually ever needs fibonacci numbers?

ChrisA

[toc] | [prev] | [next] | [standalone]


#29428

FromNeil Cerutti <neilc@norwich.edu>
Date2012-09-18 13:19 +0000
Message-ID<abram3Fl44sU3@mid.individual.net>
In reply to#29182
On 2012-09-14, Chris Angelico <rosuav@gmail.com> wrote:
> But then again, who actually ever needs fibonacci numbers?

If it should happen that your question is not facetious:

http://en.wikipedia.org/wiki/Fibonacci_number#Applications

-- 
Neil Cerutti

[toc] | [prev] | [next] | [standalone]


#29429

FromChris Angelico <rosuav@gmail.com>
Date2012-09-18 23:25 +1000
Message-ID<mailman.871.1347974704.27098.python-list@python.org>
In reply to#29428
On Tue, Sep 18, 2012 at 11:19 PM, Neil Cerutti <neilc@norwich.edu> wrote:
> On 2012-09-14, Chris Angelico <rosuav@gmail.com> wrote:
>> But then again, who actually ever needs fibonacci numbers?
>
> If it should happen that your question is not facetious:
>
> http://en.wikipedia.org/wiki/Fibonacci_number#Applications

It wasn't entirely facetious. I know there are a few cases where
they're needed, but I think they're calculated far more often to
demonstrate algorithms than because you actually have use of them. :)

Though it's as well to mention these sorts of things now and then. I
remember one time writing up something or other, and my dad was
looking over my shoulder and asked me why I'd written a little
Pascal's Triangle generator. He didn't know that it had direct
application to whatever-it-was. And unfortunately I don't remember
what I was even writing at the time :)

ChrisA

[toc] | [prev] | [next] | [standalone]


#29433

From88888 Dihedral <dihedral88888@googlemail.com>
Date2012-09-18 08:14 -0700
Message-ID<2c1df918-2d60-418b-9ecc-da1423a8a113@googlegroups.com>
In reply to#29429
Chris Angelico於 2012年9月18日星期二UTC+8下午9時25分04秒寫道:
> On Tue, Sep 18, 2012 at 11:19 PM, Neil Cerutti <neilc@norwich.edu> wrote:
> 
> > On 2012-09-14, Chris Angelico <rosuav@gmail.com> wrote:
> 
> >> But then again, who actually ever needs fibonacci numbers?
> 
> >
> 
> > If it should happen that your question is not facetious:
> 
> >
> 
> > http://en.wikipedia.org/wiki/Fibonacci_number#Applications
> 
> 
> 
> It wasn't entirely facetious. I know there are a few cases where
> 
> they're needed, but I think they're calculated far more often to
> 
> demonstrate algorithms than because you actually have use of them. :)
> 
> 
> 
> Though it's as well to mention these sorts of things now and then. I
> 
> remember one time writing up something or other, and my dad was
> 
> looking over my shoulder and asked me why I'd written a little
> 
> Pascal's Triangle generator. He didn't know that it had direct
> 
> application to whatever-it-was. And unfortunately I don't remember
> 
> what I was even writing at the time :)
> 
> 
> 
> ChrisA

I would suggest one should solve the Fibnaci(50000) first and fast in Python.

Then one can think about computing c(n,k) in Python for large n.


Then

[toc] | [prev] | [next] | [standalone]


#29434

From88888 Dihedral <dihedral88888@googlemail.com>
Date2012-09-18 08:14 -0700
Message-ID<mailman.876.1347981286.27098.python-list@python.org>
In reply to#29429
Chris Angelico於 2012年9月18日星期二UTC+8下午9時25分04秒寫道:
> On Tue, Sep 18, 2012 at 11:19 PM, Neil Cerutti <neilc@norwich.edu> wrote:
> 
> > On 2012-09-14, Chris Angelico <rosuav@gmail.com> wrote:
> 
> >> But then again, who actually ever needs fibonacci numbers?
> 
> >
> 
> > If it should happen that your question is not facetious:
> 
> >
> 
> > http://en.wikipedia.org/wiki/Fibonacci_number#Applications
> 
> 
> 
> It wasn't entirely facetious. I know there are a few cases where
> 
> they're needed, but I think they're calculated far more often to
> 
> demonstrate algorithms than because you actually have use of them. :)
> 
> 
> 
> Though it's as well to mention these sorts of things now and then. I
> 
> remember one time writing up something or other, and my dad was
> 
> looking over my shoulder and asked me why I'd written a little
> 
> Pascal's Triangle generator. He didn't know that it had direct
> 
> application to whatever-it-was. And unfortunately I don't remember
> 
> what I was even writing at the time :)
> 
> 
> 
> ChrisA

I would suggest one should solve the Fibnaci(50000) first and fast in Python.

Then one can think about computing c(n,k) in Python for large n.


Then

[toc] | [prev] | [next] | [standalone]


#29194

From"Prasad, Ramit" <ramit.prasad@jpmorgan.com>
Date2012-09-14 23:14 +0000
Message-ID<mailman.727.1347664449.27098.python-list@python.org>
In reply to#29155
Jean-Michel Pichavant wrote:

[snip]

> Ultimately, the goal is to have something like
> 
> @timeout(2)
> def doAction1
> 
> @timeout(4)
> def doAction2

[snip]

> Here's Steven example:
> 
> # Untested!
> def timeout(t=15):
>     # Decorator factory. Return a decorator to actually do the work.
>     if FPGA:
>         t *= 3
>     def decorator(func):
>         @functools.wraps(func)
>         def inner(self, timeout):
>             self.sendCmd("bootMe", timeout=t)
>         return inner
>     return decorator
> 
> I can assure you, that for some python users, it's is not easy to understand
> what it does, this function returning a function which returns another
> (wrapped) function. It requires some effort.
> 

I think it would help if it was renamed to set_timeout. And I would 
not expect the Python user to need to understand how it *works*, just 
to recognize what it *does* when it is used. I may not understand list's 
sort method internals (beyond the use of timsort), but I know how to 
use it to sort a list as I want. That is usually all I need.
 

For example, your colleagues just need to understand that the below
decorator is setting a timeout for the function.

@set_timeout(min=15)
def some_function():
    '''blah'''
      <code> 


One minor note, the style of decorator you are using loses the docstring
(at least) of the original function. I would add the @functools.wraps(func) 
decorator inside your decorator.

This email is confidential and subject to important disclaimers and
conditions including on offers for the purchase or sale of
securities, accuracy and completeness of information, viruses,
confidentiality, legal privilege, and legal entity disclaimers,
available at http://www.jpmorgan.com/pages/disclosures/email.  

[toc] | [prev] | [next] | [standalone]


#29159

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2012-09-14 12:01 +0000
Message-ID<50531cb5$0$29981$c3e8da3$5496439d@news.astraweb.com>
In reply to#29145
On Fri, 14 Sep 2012 11:28:22 +0200, Jean-Michel Pichavant wrote:

> PS : Here's the decorator, just to give you an idea about how it looks.
> Small piece of code, but took me more than 2 hours to write it. I
> removed some sensible parts so I don't expect it to run.

[snip timeout class]

Holy over-engineering Batman!!!

No wonder you don't think much of decorators, if this example of overkill 
is what you consider typical of them. It does much, much more than the 
simple code you were replacing:

def boot(self, timeout=15):
    if FPGA:
        self.sendCmd("bootMe", timeout=timeout*3)
    else:
        self.sendCmd("bootMe", timeout=timeout)

# becomes:

@timeout(15)
def boot(self, timeout=None):
    self.sendCmd("bootMe", timeout)


Most of my decorator functions are under a dozen lines. And that's the 
complicated ones!

Here's my solution to the example you gave:




# Untested!
def timeout(t=15):
    # Decorator factory. Return a decorator to actually do the work.
    if FPGA:
        t *= 3
    def decorator(func):
        @functools.wraps(func)
        def inner(self, timeout):
            self.sendCmd("bootMe", timeout=t)
        return inner
    return decorator
    

I reckon that will pretty much do what your example showed. Of course, 
once you start adding more and more functionality above the simple code 
shown above (arbitrary platforms, argument checking of the decorated 
function, logging, etc.) you're going to get a much more complex 
decorator. On the other hand, YAGNI.

http://en.wikipedia.org/wiki/You_ain%27t_gonna_need_it

 
-- 
Steven

[toc] | [prev] | [next] | [standalone]


#29163

FromTim Chase <python.list@tim.thechases.com>
Date2012-09-14 08:06 -0500
Message-ID<mailman.700.1347627951.27098.python-list@python.org>
In reply to#29159
On 09/14/12 07:01, Steven D'Aprano wrote:
> [snip timeout class]
> 
> Holy over-engineering Batman!!!
> 
> No wonder you don't think much of decorators,
[snip]

> Most of my decorator functions are under a dozen lines. And that's the 
> complicated ones!


As are mine, and a sizable chunk of those under-a-dozen-lines are
somewhat boilerplate like using @functools.wraps inside, actual def
of the function, and returning that function. :-)

-tkc


[toc] | [prev] | [next] | [standalone]


#29197

FromSteve Howell <showell30@yahoo.com>
Date2012-09-14 18:13 -0700
Message-ID<b61e0de5-4b72-4053-85c9-a91e689e2c35@oz6g2000pbc.googlegroups.com>
In reply to#29163
On Sep 14, 6:05 am, Tim Chase <python.l...@tim.thechases.com> wrote:
> On 09/14/12 07:01, Steven D'Aprano wrote:> [snip timeout class]
>
> > Holy over-engineering Batman!!!
>
> > No wonder you don't think much of decorators,
>
> [snip]
>
> > Most of my decorator functions are under a dozen lines. And that's the
> > complicated ones!
>
> As are mine, and a sizable chunk of those under-a-dozen-lines are
> somewhat boilerplate like using @functools.wraps inside, actual def
> of the function, and returning that function. :-)
>
> -tkc

For parameterized decorators, I've usually seen the pattern below.
Basically, you have 6 lines of boilerplate, and 2 lines of signal.
The amount of boilerplate is fairly daunting, but I like the
explicitness, and the nature of decorators is that they tend to get a
lot of reuse, so you can amortize the pain of all the boilerplate.

    import functools

    def hello_world(name): # non-boilerplate signature
        def decorator(f):
            @functools.wraps(f)
            def wrapped(*args, **kw):
                print 'hello', name # non-boilerplate value-add
                f(*args, **kw)
            return wrapped
        return decorator

    @hello_world('earth')
    def add(x, y):
        print x + y

    add(2, 2)

[toc] | [prev] | [next] | [standalone]


#29160

FromUlrich Eckhardt <ulrich.eckhardt@dominolaser.com>
Date2012-09-14 13:49 +0200
Message-ID<c6ibi9-0n3.ln1@satorlaser.homedns.org>
In reply to#29145
Am 14.09.2012 11:28, schrieb Jean-Michel Pichavant:
> Decorators are very popular so I kinda already know that the
> fault is mine. Now to the reason why I have troubles writing
> them, I don't know. Every time I did use decorators, I spent
> way too much time writing it (and debugging it).
>
> I wrote the following one, used to decorate any function that access
> an equipment, it raises an exception when the timeout expires. The
> timeout is adapted to the platform, ASIC of FPGA so people don't need
> to specify everytime one timeout per platform.
>
> In the end it would replace
>
> def boot(self, timeout=15):
>      if FPGA:
>          self.sendCmd("bootMe", timeout=timeout*3)
>      else:
>          self.sendCmd("bootMe", timeout=timeout)
>
> with
>
> @timeout(15)
> def boot(self, timeout=None):
>      self.sendCmd("bootMe", timeout)
>
> I wrote a nice documentation with sphinx to explain this, how to use
> it, how it can improve code. After spending hours on the decorator +
> doc, feedback from my colleagues : What the F... !!

Quite honestly: I think like your colleagues in this case and that in 
this case the decorator doesn't improve the code. Instead, I would 
probably have added a _get_timeout() function that takes care of 
adjusting the argument passed to the function according to the 
underlying hardware target.

To be less abstract, the particular problem I have with your approach is 
that I can't even guess what your code means, let alone what parameters 
it actually takes. If you had written

   @default_timeout(15)
   def boot(self, timeout=None):

instead, I would have been able to guess. OTOH, then again I would have 
wondered why you used a decorator to create a default argument when 
there is builtin support for specifying default arguments for functions.

Maybe you could get away with a decorator like this:

   @adjust_timeout
   def boot(self, timeout=2.5):

The idea is that the decorator modifies the timeout value passed to the 
function (or maybe just modifies the default value?) according to the 
underlying hardware.


> Decorators are very python specific (probably exists in any dynamic
> language though, I don't know), in some environment where people need
> to switch from C to python everyday, decorators add python magic that
> not everyone is familiar with.

The same could be said for classes, iterators, significant whitespace, 
docstrings, lambdas. I think that this was just a bad example but it 
doesn't prove that decorators are worthless. Decorators are useful tools 
if they do something to a function, like doing something before or after 
the actual code, or modifying the context in which the code is called. 
Just setting a default parameter is possible as you have proved, but 
it's IMHO not a good use case.

A bit more specific to your case, adding a timeout decorator would 
actually make much more sense if it transparently invoked the actual 
function in a second thread and the calling thread stops waiting for 
completion and raises an error after that timeout. This has the distinct 
advantage that the code doing the actual communication doesn't have any 
timeout handling code inside.

I'm currently doing something similar here though I only monitor a TCP 
connection that is used for some telnet-style requests. Every function 
making a request over TCP is decorated with @_check_connection. That 
decorator does two things:
1. It checks for an existing fatal connection error.
2. It runs the request and filters resulting errors for fatal connection 
errors.

The decorator looks like this:

def _check_connection(fn):
     @functools.wraps(fn)
     def wrapper(self, *args, **kwargs):
         # check for sticky connection errors
         if self._connection_error:
             raise self._connection_error
         # run actual function
         try:
             return fn(self, *args, **kwargs)
         catch RequestFailed:
             # The other side signalled a failure, but
             # further requests can still succeed.
             raise
         catch ConnectionError, e:
             # The connection is broken beyond repair.
             # Store sticky connection and forward.
             self._connection_error = e
             raise
     return wrapper

I have had other programmers here write such requests and they blindly 
copied the decorator from existing code. This works because the code 
inside that converts/formats/parses the inputs and outputs is completely 
unaware of the connection monitoring. Otherwise, I don't think anyone 
could explain what this decorator does, but they don't have to 
understand it either. It just works.

I wish you a nice weekend!

Uli

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.python


csiph-web