Search code examples
type-conversionpython-c-api

How does PyNumber_Float handle an argument that is already a float?


Documentation for PyNumber_Float (here) doesn't specify what happens if you pass in a PyObject* that points to another float.

e.g. PyObject* l = PyLong_FromLong( 101 ); PyObject* outA = PyNumber_Float(l);

outA will point to a newly created float PyObject (or if there already exists one with that value, I think it will point to that and just increment the reference counter)

However,

PyObject* f = PyFloat_FromDouble( 1.1 );
PyObject* outB = PyNumber_Float(f);

What happens here?

  • Does it simply return the same pointer?
  • Does it first increment the reference count and then return the same pointer?
  • Or does it return a pointer to a new PyObject?

Is the behaviour guaranteed to be identical for the equivalent C-API calls for generating other primitives, such as Long, String, List, Dict, etc?

Finally, should the documentation clarify this situation? Would it be reasonable to file a doc-bug?


Solution

  • Thanks to haypo on the dev IRC channel, the following test shows that it returns the same object, with the reference counter incremented:

    >>> x=1.1
    >>> y=float(x)
    >>> y is x, sys.getrefcount(x)-1, sys.getrefcount(y)-1
    (True, 2, 2)
    
    >>> y+=1
    >>> y is x, sys.getrefcount(x)-1, sys.getrefcount(y)-1
    (False, 1, 1)
    

    Note: explanation of why refcount is one-too-high here
    Note: x is y compares the memory address, "x is y" is the same as "id(x) == id(y)"

    Of course it is possible that some assignment-operator optimisation is bypassing the application of float()