Skip to content
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

Add support for __getattr__ (#36) #37

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

stevenfontanella
Copy link

@stevenfontanella stevenfontanella commented Nov 19, 2019

I was working on __setattr__ as well, but for some reason I get

  File "/home/steven/code/fypy/forbiddenfruit/tests/unit/test_forbidden_fruit.py", line 357, in test_overriding_setattr
    my_dict.abc = "xyz"
SystemError: error return without exception set

when I run the following test:

@skip_legacy
def test_overriding_setattr():
    """TODO: docstring"""
    def setter(self, x, val):
        super(tuple, self).__setattr__(x, val)
    curse(tuple, "__setattr__", setter)

    tup = ()
    tup.abc = "xyz"
    assert(tup.abc == "xyz")

The code I added for that was:

PyTypeObject._fields_ = [
# ...
    ('tp_setattr', ctypes.CFUNCTYPE(PyObject_p, PyObject_p, PyObject_p, PyObject_p))
]
# ...
override_dict['__setattr__'] = ('tp_setattr', "tp_setattr")

@alexchandel
Copy link

You're using the deprecated name, use tp_setattro.

And your signature is wrong. The signature documented here conflicts with here. The question is resolved by the cpython header, where an int (ctypes.c_int) is returned. Never assume, always check the headers.

@stevenfontanella
Copy link
Author

Gotcha, I'll give it another look. Thanks for the quick answer!

@stevenfontanella
Copy link
Author

Even after changing the name and signature, I get the same error. Is it possible that there's not a good way to implement __setattr__ patching? __setattro__ should return a c_int, but all we can do from Python is return a pointer to c_int (or maybe just a PyObject_p, I'm not entirely sure). Because I'm always returning a non-null pointer, the value is always non-zero, so we get SystemError: error return without exception set when __setattr__ returns. Does that sound plausible?

@alexchandel
Copy link

but all we can do from Python is return a pointer to c_int

No. You should return a c_int, not a pointer to anything. Look a few lines up at the types of printfunc and setattrfunc. It's throwing SystemError because (at the very least) your signature is wrong. Instead:

    ('tp_str', ctypes.CFUNCTYPE(PyObject_p, PyObject_p)),
    ('tp_getattro', ctypes.CFUNCTYPE(PyObject_p, PyObject_p, PyObject_p)),
    ('tp_setattro', ctypes.CFUNCTYPE(ctypes.c_int, PyObject_p, PyObject_p, PyObject_p)),
    ('tp_as_buffer', ctypes.c_void_p),
    ('tp_flags', ctypes.c_ulong),
    # ...

@stevenfontanella
Copy link
Author

Right, I changed the signature. My point was that when I would like to override __setattr__ in python, I would need to write a function that matches the signature, i.e. returning a c_int. If you look at my commit here, I've done that, however I still get the same error in test_overriding_setattr. I think it's because even though the setter function returns a c_int, this is still a boxed type in python, so it's not the same as returning an int from a C function. In other words it looks like it's returning a c_int but it's really returning a PyObject_p. My question is: is there a way to write a python function that satisfies this signature and returns a c_int instead?

@7UR7L3
Copy link

7UR7L3 commented Feb 17, 2024

I got the same errors @stevenfontanella mentioned with c_int, but I found that ('tp_setattro', ctypes.CFUNCTYPE(ctypes.c_char_p, PyObject_p, PyObject_p, PyObject_p)) worked for at least dict.__setattr__ without even bothering with a return somehow, in case anybody stumbles across this like me. It was only happy when I replaced __setattr__ before replacing __getattr__ if that means anything.

@ctypes.CFUNCTYPE(ctypes.c_char_p, PyObject_p, PyObject_p, PyObject_p)
def dict_setattr_cfunc(dict, field, val):
    dict[field] = val;

tp_func_dict[(dict, "__setattr__")] = dict_setattr_cfunc;
PyTypeObject.from_address(id(dict)).tp_setattro = dict_setattr_cfunc;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants