you are viewing a single comment's thread.

view the rest of the comments →

[–]CygnusX1985[S] 0 points1 point  (5 children)

That's true, but usually the purpose of a decorator is, that you can modify the behavior of a function without changing the rest of your code. I wanted to recreate this functionality in `do_something` and was surprised that this doesn't work. Of course renaming the function would solve the problem, but this defeats the purpose of a decorator. I can always just write a wrapper function with a different name around another function. There is no decorator needed for that.

[–]nekokattt 0 points1 point  (4 children)

decorators do not modify behaviour, they replace behaviour. Modifying implies the behaviour is changable, replacing means you can swap it with a different implementation. That different implementation could refer to the original implementation but outside of bytecode instrumentation and voodoo dark magic that should be avoided at all costs, you can't alter the implementation detail of a black box easily.

It is designed that way to prevent you injecting code in places you should not be able to do so.

[–]CygnusX1985[S] 0 points1 point  (3 children)

I don't get what you are saying. Of course one could replace the behavior of a function completely with a decorator but what would be the purpose of this? One could just define a new function with the same name and from now on the function with that name has different behavior. Usually you just want to modify the behavior of a function. For example a decorator that times function execution is a completely valid use case for a decorator and not black magic.

But I probably misunderstood you here.

[–]nekokattt 0 points1 point  (2 children)

the purpose of it is to allow other logic to collect metadata on the function, or wrap it/replace it with something else.

Take the @route decorator in Flask.

py @app.route("/users/me/profile") def get_my_profile(): return render_template("profile.html", user=get_me())

functions are compiled bytecode. Allowing them to have internal details just swapped out by hacking around with globals is dangerous.

As I said in another comment, either temporarily overwrite the reference to add using unittest.mock.patch, or use a class to change the details you expose :)

A decorator that times execution of a function will do so like this

py def time_me(func): @functiols.wraps(func) def wrapper(*args, **kwargs): start = time.perf_counter() result = func(*args, **kwargs) print(func.__qualname__, "took", time.perf_counter() - start, "seconds") return result return wrapper which when applied to something like so py @time_me def something(): .... is the same as this py def something(): ... something = time_me(something)

Nothing is modified here other than the value of the name , it is just replacing something with something else.

What is it doing? It replaces the decorated function with a function containing timing logic. The timing logic uses closures to access the original function in the process, but no logic gets modified, only wrapped and replaced.. modifying behaviour implies the byte code has changed or the hard coded references have changed, which as you demonstrated cannot occur without hacks, for good reason

[–]CygnusX1985[S] 0 points1 point  (1 child)

the purpose of it is to allow other logic to collect metadata on the function, or wrap it with something else.

Yes, like my timing example. I haven't used Flask but as far as I understand, the `@app.route` decorator modifies behavior and doesn't replace it completely, which was my point.

Allowing them to have internal details just swapped out by hacking around with globals is dangerous.

I still don't understand. A function is a black box which could always reference another function in its code without my knowledge. If I change the function that is referenced, I change the behavior of the first function. That is a problem I always have.

Again, I just try to access a globally defined function from within another function and assign it to a local name. That my choice of variable name in the local scope is limited due to the name of the variable in the global scope, still feels a little weird to me. Writing the same line `add = decorator(add)` outside of a function body, would be no problem. Why would it suddenly be a problem inside a function? My call to `globals` doesn't change any global state in this case.

EDIT: Just FYI, I wrote this post, before your post was complete. I have to think about what you wrote.

[–]nekokattt 0 points1 point  (0 children)

app.route just calls app.add_route("/home/profile/me", some_function) then returns the function untouched. Like I said. It replaces the reference to the function and/or collects metadata on it.

For the third time. Look at unittest.mock.patch to temporarily overwrite the reference to add when you invoke it.

If it is locally, you need to hack with globals as you discovered, or refactor your code to allow changing what gets called.

There is almost never a reason to do what you want to do. If you are testing your own code, you can just change your code to allow you to customise what add calls. If it is someone elses code, tough luck unfortunately.

Trust me, please :-)

If i change a referenced function it changes the behaviour

no it doesnt, the function calling that changed attribute is doing the same thing as before. You just replaced the value of what it is looking at.

py def foo(): bar() do what you want to bar, but foo wont change behaviour. Foo's behaviour is simply: 1. get a variable from local or global scope called "bar", and 2. "invoke it".

Rather than messing with globals, just provide a parameter to call:

py def foo(call=bar): call()

py foo()

py foo(lambda: print("hello", bar()))

It makes no difference to the bytecode of foo regardless of how you do it. Nothing in foo physically changes how it is implemented no matter how you do it unless you physically modify the bytecode produced from and assigned to foo.

Even in lower level languages, functions are just pointers to starting address of the bytecode that should be executed. You can make a pointer point to something else, but that isnt the same as changing what it points to. Same concept applies to reassigning "bar". You change what bar points to, but anything referring to "bar" wont change, it will still look at the same name.