Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>You can create a decorator that stores functions in a list, to be attached to another function by another decorator. That seems to be what you think is the hard part of your problem, but it's trivial:</p> <pre><code>before_funcs = [] def before(func): before_funcs.append(func) return func def attach_befores(func): @functools.wraps(func) def newfunc(*args, **kwargs): for before_func in before_funcs: before_func() return func(*args, **kwargs) return newfunc </code></pre> <hr> <p>So, now you can do this:</p> <pre><code>a = 0 @before def callback(): global a a += 1 @before def another(): global a a *= 2 @attach_befores def do_thing(i): print(i) </code></pre> <p>Notice that you need the <code>global a</code> there, because the function isn't valid otherwise.</p> <hr> <p>And now, you can call it:</p> <pre><code>do_thing(a) do_thing(a) do_thing(a) do_thing(a) </code></pre> <p>However, it's not going to give you the result you wanted—in particular, changing the global <code>a</code> will not change the argument passed to the real <code>do_thing</code> function. Why? Because function arguments are evaluated before the function is called. So, rebinding <code>a</code> after the argument has already been evaluated does you no good. It will, of course, still change the argument passed to the <em>next</em> call. So, the output will be:</p> <pre><code>0 2 6 14 </code></pre> <hr> <p>If you just want to modify the argument passed to the function, you don't need all this mucking about with globals. Just have the <code>before</code> function modify the argument, and have the decorator-applier pass the arguments through each <code>before</code> function before passing them to the real function.</p> <p>Or, alternatively, if you want to modify the globals used by the function, have the function actually use those globals instead of taking parameters.</p> <p>Or, alternatively, if you want to mutate the value in place, make it something mutable, like a list, and make the <code>before</code> functions mutate the value instead of just rebinding the global to a different value.</p> <p>But what you're asking for is a decorator that can reach up to the calling frame, figure out what expressions were evaluated to get the arguments, and force them to be re-evaluated. That's just silly.</p> <hr> <p>If you really, really wanted to do that, the only way to do it would be to capture and interpret the bytecode in <code>sys._getframe(1).f_code</code>.</p> <p>At least in CPython 2.7, you will get some sequence of codes that pushes your decorated function onto the stack (a simple <code>LOAD_NAME</code> or <code>LOAD_NAME</code> in the typical case, but not necessarily), then a sequence of codes to evaluate the expressions, then a <code>CALL_FUNCTION</code>/<code>CALL_FUNCTION_VAR</code>/etc. So, you can walk backward, simulating the operations, until you've found the one that pushed your function onto the stack. (I'm not sure how to do this in a foolproof way, but it ought to be doable. Then, build a new <code>code</code> object that just pushes your function with a <code>LOAD_CONST</code> and repeats all the operations after it (and then returns the value). Then wrap that <code>code</code> in a <code>function</code> with the exact same environment as the caller, then call that new function and return its value, instead of calling the wrapped function directly.</p> <p>Here's an example:</p> <pre><code>def call_do_thing(b): global a b += a return do_thing(a * b) </code></pre> <p>The psuedo-bytecode is:</p> <pre><code>LOAD_FAST b LOAD_GLOBAL a INPLACE_ADD STORE_FAST b LOAD_GLOBAL do_thing LOAD_GLOBAL a LOAD_FAST b BINARY_MULTIPLY CALL_FUNCTION 1 RETURN_VALUE </code></pre> <p>In this case, finding the function call is easy, because it used a <code>LOAD_GLOBAL</code>. So, we just need to take all the ops from there to the <code>RETURN_VALUE</code> and wrap them up in a new function to call instead of the one we've been given, and <code>a</code> will be re-evaluated in the new globals.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload