Search code examples
pythonccpythonpython-c-api

How to modify the value of a Python variable(like a refrence) in python


I am writing a syscall wrapper for python(just as a fun project to get me used to the API),
and when I came upon implementing read(), I am perplexed as to how I can modify a python buffer that is sent to my function.

The function is a simple cpython wrapper around the read() syscall.
It takes an integer (the file descriptor), a buffer, and the maximum you want to read, then returns the amount read.

I have everything working except for the modification of the buffer:

py_obj py_read(py_obj self, py_obj args){
    char* buff;
    int fd;
    int len;

    if(!PyArg_ParseTuple(args, "isi", &fd, &buff, &len)){
        return NULL;
    }

    return Py_BuildValue("i", read(fd, buff, len));
}

After loading the module, then calling read:

>> from syscalls import read
>> STDIN = 1
>> s = ""
>> read(STDIN,s, 256)
napkin
7
>> s
""

Though this is what I expected (and is what should happen, since I did not have an actual reference to the argument), I would like to know how to get a reference to the parameter.

EDIT: After using @user2357112, it still does not modify the value

>>> b = memoryview(b"")
>>> from syscalls import *
>>> read(1, b, 10)
test
5
>>> b
<memory at 0x7fa060628408>
>>> b.tolist()
[]
>>>

EDIT 2: But it does work with bytearray, if I size it correctly Thank you @user2357112


Solution

  • You have a reference to the argument. You may have just corrupted the argument object or the memory surrounding it, in fact. You don't have a reference to the caller's s variable, but variables and references don't work like that in Python anyway; references always refer to objects.

    Python string objects aren't appropriate for use as mutable buffers. They're supposed to be immutable, after all. Also, they're Unicode, and read reads bytes. Instead, use an appropriately-sized bytearray and view its contents through a Py_buffer structure with the y* format code.

    Also, since read returns ssize_t rather than int, you should use the n format code rather than i. n corresponds to Py_ssize_t, which is intended to match ssize_t when ssize_t exists.

    PyObject *my_read(PyObject *self, PyObject *args){
        Py_buffer buff;
        int fd;
        int len;
    
        if(!PyArg_ParseTuple(args, "iy*i", &fd, &buff, &len)){
            return NULL;
        }
    
        ssize_t read_count = read(fd, buff.buf, len);
        PyBuffer_Release(&buff);
    
        return Py_BuildValue("n", read_count);
    }