Is it possible to use the result property of a function as a decorator?

I made myself a simple event system in python, and I found that the way I fire events was pretty much the same every time: either at the end of the call, or in front of it. It seems like it would be nice to have as a decorator. Here is the code I'm using:

from functools import wraps def fires(event): """ Returns a decorater that causes an `Event` to fire immediately before the decorated function is called """ def beforeDecorator(f): """Fires the event before the function executes""" @wraps(f) def wrapped(*args, **kargs): event.fire(*args, **kargs) return f(*args, **kargs) return wrapped def afterDecorator(f): """Fires the event after the function executes""" @wraps(f) def wrapped(*args, **kargs): result = f(*args, **kargs) event.fire(*args, **kargs) return result return wrapped # Should allow more explicit `@fires(event).uponCompletion` and # `@fires(event).whenCalled` afterDecorator.onceComplete = afterDecorator afterDecorator.whenCalled = afterDecorator return afterDecorator 

With this code, I can successfully write this:

 @fires(myEvent) def foo(y): return y*y print func(2) 

And everything works. The problem occurs when I try to write this:

 @fires(myEvent).onceComplete def foo(y): return y*y print func(2) 

This gives me a syntax error. Is there any special syntax for complex decorators? Does the parser remain after the first set of parentheses?

+4
source share
3 answers

No, according to the grammatical specification , it is impossible:

  funcdef :: = [decorators] "def" funcname "(" [parameter_list] ")" ["->" expression] ":" suite
 decorators :: = decorator +
 decorator :: = "@" dotted_name ["(" [argument_list [","]] ")"] NEWLINE
 dotted_name :: = identifier ("." identifier) ​​*
 parameter_list :: = (defparameter ",") *
                     ("*" [parameter] ("," defparameter) *
                     [, "**" parameter]
                     |  "**" parameter
                     |  defparameter [","])
 parameter :: = identifier [":" expression]
 defparameter :: = parameter ["=" expression]
 funcname :: = identifier

Decorators must have their brackets at the end

+3
source

I added preliminary calculations of your options before and after (thanks to the invoke trick, all locks are created upon import and simply used whenever a decorator is used), made the choice dependent on an optional argument for meta-decorator, and put a try / finally block to make sure that your subsequent events always fire. With this approach, the issue of function attributes becomes controversial.

 invoke = lambda f: f() # trick used in JavaScript frameworks all the time @invoke # closure becomes fires def fires(): def beforeDecorator(f, event): """Fires the event before the function executes""" @wraps(f) def wrapped(*args, **kargs): event.fire(*args, **kargs) return f(*args, **kargs) return wrapped def afterDecorator(f, event): """Fires the event after the function executes""" @wraps(f) def wrapped(*args, **kargs): try: result = f(*args, **kargs) finally: event.fire(*args, **kargs) return result return wrapped def closure(event, after=False): # becomes fires def decorator(function): if after: return afterDecorator(function, event) else: return beforeDecorator(function, event) return decorator return closure 
+2
source

I'm not sure if there is a way to get the syntax you want to use, but here is an alternative.

Just add an extra argument to your fires() decorator to determine if it should occur before or after:

 def fires(event, before=True): """ Returns a decorater that causes an `Event` to fire immediately before or after the decorated function is called """ if before: def decorator(f): """Fires the event before the function executes""" @wraps(f) def wrapped(*args, **kargs): event.fire(*args, **kargs) return f(*args, **kargs) return wrapped else: def decorator(f): """Fires the event after the function executes""" @wraps(f) def wrapped(*args, **kargs): result = f(*args, **kargs) event.fire(*args, **kargs) return result return wrapped return decorator 

And then use it like this:

 @fires(myEvent, before=False) # or before=True, defaults to True def foo(y): return y*y 
+1
source

Source: https://habr.com/ru/post/1403154/


All Articles