Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.python > #29145 > unrolled thread
| Started by | Jean-Michel Pichavant <jeanmichel@sequans.com> |
|---|---|
| First post | 2012-09-14 11:28 +0200 |
| Last post | 2012-09-14 13:49 +0200 |
| Articles | 17 — 11 participants |
Back to article view | Back to comp.lang.python
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
| From | Jean-Michel Pichavant <jeanmichel@sequans.com> |
|---|---|
| Date | 2012-09-14 11:28 +0200 |
| Subject | Re: 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]
| From | Duncan Booth <duncan.booth@invalid.invalid> |
|---|---|
| Date | 2012-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]
| From | andrea crotti <andrea.crotti.0@gmail.com> |
|---|---|
| Date | 2012-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]
| From | Chris Angelico <rosuav@gmail.com> |
|---|---|
| Date | 2012-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]
| From | 88888 Dihedral <dihedral88888@googlemail.com> |
|---|---|
| Date | 2012-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]
| From | 88888 Dihedral <dihedral88888@googlemail.com> |
|---|---|
| Date | 2012-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]
| From | andrea crotti <andrea.crotti.0@gmail.com> |
|---|---|
| Date | 2012-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]
| From | Chris Angelico <rosuav@gmail.com> |
|---|---|
| Date | 2012-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]
| From | Neil Cerutti <neilc@norwich.edu> |
|---|---|
| Date | 2012-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]
| From | Chris Angelico <rosuav@gmail.com> |
|---|---|
| Date | 2012-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]
| From | 88888 Dihedral <dihedral88888@googlemail.com> |
|---|---|
| Date | 2012-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]
| From | 88888 Dihedral <dihedral88888@googlemail.com> |
|---|---|
| Date | 2012-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]
| From | "Prasad, Ramit" <ramit.prasad@jpmorgan.com> |
|---|---|
| Date | 2012-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]
| From | Steven D'Aprano <steve+comp.lang.python@pearwood.info> |
|---|---|
| Date | 2012-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]
| From | Tim Chase <python.list@tim.thechases.com> |
|---|---|
| Date | 2012-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]
| From | Steve Howell <showell30@yahoo.com> |
|---|---|
| Date | 2012-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]
| From | Ulrich Eckhardt <ulrich.eckhardt@dominolaser.com> |
|---|---|
| Date | 2012-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