-
Notifications
You must be signed in to change notification settings - Fork 231
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PySide6 behaves differently between decorated and non-decorated function #243
Comments
The comments in the StackOverflow issue suggest to me that PySide is doing something unconventional. The comments claim for the
Although a visual inspection of the example code may suggest that, from memory the Any code inspecting the signature using the standard Python functions in the If that isn't happening, it suggests to me that PySide is not using the standard Python functions, but is dropping down and using lower level parts of the Python object model, meaning it isn't necessarily going to be friendly to situations where duck typing/monkey patching is employed. To investigate this further, it would be necessary to identify the specific code in PySide where the introspection is being done to work out what it is doing. I can't see in the StackOverflow comments that anyone has pointed into the PySide code to say where that is done. |
Related reading about really weird stuff PySide does. |
BTW, try your example again using This will cause wrapt to use the pure Python version of the wrapper object. I want to see if PySide behaves differently depending on whether that is used instead of the default C extension version. |
Actually, that probably wouldn't make a difference since the signature override code is all implemented in pure Python code. All the same, just check in case. |
Few other things which would be helpful. What Python version are you using? How the signature stuff works varies based on the version of Python used. It would also be helpful to see what you get if you add:
|
Finally, if PySide doesn't want to play well, you may be able to brute force it to work by using signature adapter function as explained in: Eg.,
Would rather we can dig into the underlying problem as to why PySide doesn't like how wrapt attempts to preserve the signature. |
Wow, thanks for your comments! I will respond to them one by one:
You are right, although not by default any more: https://github.com/micheles/decorator/blob/bd49b3d/src/decorator.py#L50
I believe you are right. I have been able to confirm that I am still looking for the same piece of code in PySide6, but it is very possible it is doing something similar.
Done - that does not seem to change anything.
3.11.4 on Windows 22H2
I may be using this wrong, but I am getting
I have summarized all my experiments in this single piece of code: import functools
import inspect
# pip install boltons==23.0.0 decorator==5.1.1 wrapt==1.15.0 PySide6-Essentials==6.5.3
import boltons.funcutils
import decorator
import shiboken6 # needed for `import shibokensupport`
import shibokensupport
import wrapt
from PySide6.QtWidgets import QApplication, QSpinBox
# region Decorator( factorie)s
def dec_functools(wrapped):
@functools.wraps(wrapped)
def wrapper(*args, **kwargs):
return wrapped(*args, **kwargs)
wrapper.__signature__ = inspect.signature(wrapped)
return wrapper
@wrapt.decorator
def dec_wrapt(wrapped, _instance, args, kwargs):
return wrapped(*args, **kwargs)
def _my_adapter_prototype():
pass
@wrapt.decorator(adapter=_my_adapter_prototype)
def dec_wrapt_adapter(wrapped, _instance, args, kwargs):
return wrapped(*args, **kwargs)
@decorator.decorator
def dec_decorator(wrapped, *args, **kwargs):
return wrapped(*args, **kwargs)
@decorator.decoratorx
def dec_decoratorx(wrapped, *args, **kwargs):
return wrapped(*args, **kwargs)
def dec_boltons(wrapped):
@boltons.funcutils.wraps(wrapped)
def wrapper(*args, **kwargs):
return wrapped(*args, **kwargs)
wrapper.__signature__ = inspect.signature(wrapped)
return wrapper
def eval_wraps(wrapped):
def eval_decorator(wrapper, wrapped=wrapped):
# Generate
name = wrapped.__name__
signature = inspect.signature(wrapped)
arguments = ", ".join(signature.parameters.keys())
source = f"def {name}{signature}:\n return wrapper({arguments})"
# Compile
code = compile(source, "<string>", "exec")
# Run
environment = {"wrapper": wrapper}
eval(code, environment)
wrapped = environment[name]
return wrapped
return eval_decorator
def dec_eval(wrapped):
@eval_wraps(wrapped)
def wrapper(*args, **kwargs):
return wrapped(*args, **kwargs)
return wrapper
# endregion
# region Decorated functions
def fun(a=0, b=1, c=2):
print("Works")
@dec_functools
def fun_functools(a=0, b=1, c=2):
print("Fails")
@dec_wrapt
def fun_wrapt(a=0, b=1, c=2):
print("Fails")
@dec_wrapt_adapter
def fun_wrapt_adapter(a=0, b=1, c=2):
print("Fails")
@dec_decorator
def fun_decorator(a=0, b=1, c=2):
print("Fails")
@dec_decoratorx
def fun_decoratorx(a=0, b=1, c=2):
print("Works!")
@dec_boltons
def fun_boltons(a=0, b=1, c=2):
print("Works!")
@dec_eval
def fun_eval(a=0, b=1, c=2):
print("Works!")
# endregion
# region Tests
for this_fun in [
fun,
fun_functools,
fun_wrapt,
fun_wrapt_adapter,
fun_decorator,
fun_decoratorx,
fun_boltons,
fun_eval,
]:
print(this_fun.__name__)
print(getattr(this_fun, "__signature__", None))
print(inspect.signature(this_fun))
print(inspect.getfullargspec(this_fun))
print(this_fun.__code__.co_argcount)
print(this_fun.__code__.co_varnames)
print(shibokensupport.signature.get_signature(this_fun))
print()
app = QApplication()
box = QSpinBox()
box.valueChanged.connect(fun) # works, but is undecorated
box.valueChanged.connect(fun_functools) # raises TypeError without `a=0, b=1, c=2`
box.valueChanged.connect(fun_wrapt) # raises TypeError without `a=0, b=1, c=2`
box.valueChanged.connect(fun_wrapt_adapter) # raises TypeError without `a=0, b=1, c=2`
box.valueChanged.connect(fun_decorator) # raises TypeError without `a=0, b=1, c=2`
box.valueChanged.connect(fun_decoratorx) # works
box.valueChanged.connect(fun_boltons) # works
box.valueChanged.connect(fun_eval) # works (but uses `eval`)
box.setValue(10)
# endregion Commented lines produces errors if you remove the Output:
Basically, all methods maintain the basic argspec, yet only some of them actually work. Currently, this is |
My fault on:
The So I meant like example in docs.
I just wasn't paying attention. So should have been:
Thinking about it a bit more, if I had to make a guess the problem may be that PySide does equivalent of |
I think what is going to be needed is to work out a standalone test app using PySide to see what it returns when using If that returns bogus results would confirm that it just doesn't cope with decorator wrappers which are implemented as a descriptor object and not a function. |
If you add:
does it work, or does the module import fail. Am not sure if that is a top level package, or whether is nested in something else and need to work out actual import path. |
Inspired by micheles/decorator#129 (comment), I thought I had found the piece of code where PySide6 differs for However,
We have that, see my initial code
It returns |
That is indeed the case. |
I am indeed blind. If |
Does I still can't understand how PySide code works. So confusing. :-( |
No, it does not. Good point!
Exactly right. I added |
I am still trying to analyze https://github.com/qtproject/pyside-pyside-setup/blob/dev/sources/pyside6/libpyside/pysidesignal.cpp to see which way they are seeing which signatures are available. They do things that look similar to that in I just don't understand yet what this |
In this code example,
fun
behaves different fromdecorated_fun
. For more context, see https://stackoverflow.com/q/76596620/. I have tried a lot of things already to fix this. What works is theFunctionBuilder
approach ofboltons
(basically someexec(compile(def fun(...)))
).wrapt
, however, is not able to fully emulatefun
:The text was updated successfully, but these errors were encountered: