33 ms·
Maybe writing lots of decorators helps you applying this concept when you need to solve some sort of complex problems. However, I fail to understand the usefuln
by cJ0th 11y ago
Maybe writing lots of decorators helps you applying this concept when you need to solve some sort of complex problems. However, I fail to understand the usefulnesses of the examples given in all the tutorials I read about decorators. Sure, it helps to D.R.Y when you want to log something or deal with authentication. Yet I find they infringe the "explicit is better than implicit" rule.
When I read code not written by myself and see this:
@something
def foo(bar):
...
It is not immediately clear to me what foo actually does whereas
def foo(bar):
# some function calls here
# next: what foo does with these results
# some more function calls on what has been done before
gives me a hint without scrolling through the code.
- itp 11y agoWhen I worked at my last company, we had to expose an easy-to-use API for an optimistic-concurrency database that required cooperating clients to retry operations in a variety of situations. Rather than asking every client to implement correct retry behavior, we tried to expose a simple retry loop in an idiomatic way in each client language. Python was far and away my favorite, since we could take advantage of decorators to make everything relatively seamless. Consider a function something like this: def do_something_transactionally(db, *args, **kwargs): tr = db.create_transaction() # do some things with the transaction object tr.commit().get() This code was broken in a number of ways -- there's no retry, or handling of errors from commit, etc. So we exposed a decorator in Python to handle all of these issues for the common case: @transactional def do_something_transactionally(db, *args, **kwargs): # do some things with the transaction object This was more or less equivalent to: def do_something_transactionally(db, *args, **kwargs): tr = db.create_transaction() while True: try: # do some things with the transaction object tr.commit().wait() break except Error as e: tr.on_error(e).get() It was actually even niftier than that, though, because the decorator allowed you to pass in either a transaction object or a database object. If the caller provided a transaction rather than a database, then the decorator did not create a new transaction or commit it; this allowed composition of more complicated transactional calls of decorated functions. So I will definitely grant you that if you encountered this code, you would need to understand what @transactional meant, but I would definitely argue that this was cleaner and easier to deal with than asking every function to explicitly implement the retry logic.
- matthiasv 11y agoThat looks actually more like a use case for a context manager, i.e. with transactional as transaction: # do something It's pretty clear at the call site what is and what isn't transactional and most importantly is practically the pythonic way of writing code with guaranteed cleanup.
- itp 11y agoWe really wanted to support a context manager there, believe me. IIRC the problem was there is/was no facility for retry with a context manager, so there would still need to be structure around it. I could be wrong, though -- I only remember being seduced by the context manager option a couple times before running into the same wall. I don't think the context manager, even if it had been doable, would have supported composition in the style of: @transactional def thing_one(tr, *args): # stuff @transactional def thing_two(tr, *args): # other stuff @transactional def both_things(tr, *args): thing_one(tr, args) thing_two(tr, args) If it's not clear, in this case a caller could provide a database object to thing_one or thing_two and they would be executed as a single atomic transaction, but a call to both_things with a database object would execute both thing_one and thing_two inside of a single atomic transaction.
- icebraining 11y agoYou're not wrong, it was an explicit decision: https://mail.python.org/pipermail/python-ideas/2013-May/020633.html https://mail.python.org/pipermail/python-ideas/2013-May/0206...
- chucksmash 11y ago@something is a function call and if it is properly named, it should give you a hint as to what it does without scrolling through the source code.
- cJ0th 11y agoI don't disagree but it is just one function call. On the other hand: foo1() stuff foo2() would be two function calls. If every function has a helpful name then two function calls are more telling than one, no? Of course the level of desired detail varies with what one does. @logging might be enough of a hint if the function really does nothing else but write a log entry.
- icebraining 11y agoMore function calls can also clutter the function, rendering the intent harder to understand. The advantage of the decorator syntax is that it stays out of the main logic of the function, which is useful for "side" concerns (like logging, but also authorization, contracts, etc).
- andybak 11y agoSo you're saying: def baz(): bar1() foo() bar2() is hugely better than: @bar def baz(): foo() I think it massively depends on naming and the general context. I can see myself choosing either depending on the precise situation. As with all things in Python (and coding in general) taste and discretion are key: use decorators when they improve legibility and stop using them when they don't.
- tsmarsh 11y agoI think they're saying: def calling_context: bar(foo) is cleaner. The problem with decorators is that they don't know the calling context, or provide a mechanism to adapt to that context short of monkey patching. That said, they are really, really convenient.
- deleted 11y ago[deleted]