Skip to content

Commit

Permalink
Update documentation refcounting example (#6180)
Browse files Browse the repository at this point in the history
I think this makes some of the different behaviour clearer. The
initial example was somewhat complicated by things like
"is sys.getrefcount creating an extra reference?", and reading
global variables inside the functions which isn't
hugely relevant to much.

This tries to show the effect of creating new references inside
a function, which is more direct and probably shows what
we actually want.
  • Loading branch information
da-woods committed May 11, 2024
1 parent 72221fa commit bc59042
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 25 deletions.
35 changes: 23 additions & 12 deletions docs/examples/userguide/language_basics/parameter_refcount.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,34 @@
from cython.cimports.cpython.ref import PyObject, _Py_REFCNT
# Py_REFCNT and _Py_REFCNT are the same, except _Py_REFCNT takes
# a raw pointer and Py_REFCNT takes a normal Python object
from cython.cimports.cpython.ref import PyObject, _Py_REFCNT, Py_REFCNT

import sys

python_dict = {"abc": 123}
python_dict_refcount = sys.getrefcount(python_dict)
python_dict_refcount = Py_REFCNT(python_dict)

@cython.cfunc
def owned_reference(obj: object):
refcount = sys.getrefcount(python_dict)
print('Inside owned_reference: {refcount}'.format(refcount=refcount))
refcount1 = Py_REFCNT(obj)
print(f'Inside owned_reference initially: {refcount1}')
another_ref_to_object = obj
refcount2 = Py_REFCNT(obj)
print(f'Inside owned_reference after new ref: {refcount2}')

@cython.cfunc
def borrowed_reference(obj: cython.pointer(PyObject)):
# use _Py_REFCNT instead of Py_REFCNT to avoid creating a new owned
# reference just to get the reference count
refcount = _Py_REFCNT(obj)
print('Inside borrowed_reference: {refcount}'.format(refcount=refcount))
refcount1 = _Py_REFCNT(obj)
print(f'Inside borrowed_reference initially: {refcount1}')
another_ptr_to_object = obj
refcount2 = _Py_REFCNT(obj)
print(f'Inside borrowed_reference after new pointer: {refcount2}')
# Casting to a managed reference to call a cdef function doesn't increase the count
refcount3 = Py_REFCNT(cython.cast(object, obj))
print(f'Inside borrowed_reference with temporary managed reference: {refcount3}')
# However calling a Python function may depending on the Python version and the number
# of arguments.

def main():
print('Initial refcount: {refcount}'.format(refcount=python_dict_refcount))
owned_reference(python_dict)
borrowed_reference(cython.cast(cython.pointer(PyObject), python_dict))

print(f'Initial refcount: {python_dict_refcount}')
owned_reference(python_dict)
borrowed_reference(cython.cast(cython.pointer(PyObject), python_dict))
33 changes: 22 additions & 11 deletions docs/examples/userguide/language_basics/parameter_refcount.pyx
Original file line number Diff line number Diff line change
@@ -1,23 +1,34 @@
from cpython.ref cimport PyObject, _Py_REFCNT
# Py_REFCNT and _Py_REFCNT are the same, except _Py_REFCNT takes
# a raw pointer and Py_REFCNT takes a normal Python object
from cpython.ref cimport PyObject, _Py_REFCNT, Py_REFCNT

import sys

python_dict = {"abc": 123}
python_dict_refcount = sys.getrefcount(python_dict)
python_dict_refcount = Py_REFCNT(python_dict)


cdef owned_reference(object obj):
refcount = sys.getrefcount(python_dict)
print('Inside owned_reference: {refcount}'.format(refcount=refcount))
refcount1 = Py_REFCNT(obj)
print(f'Inside owned_reference initially: {refcount1}')
another_ref_to_object = obj
refcount2 = Py_REFCNT(obj)
print(f'Inside owned_reference after new ref: {refcount2}')


cdef borrowed_reference(PyObject * obj):
# use _Py_REFCNT instead of Py_REFCNT to avoid creating a new owned
# reference just to get the reference count
refcount = _Py_REFCNT(obj)
print('Inside borrowed_reference: {refcount}'.format(refcount=refcount))


print('Initial refcount: {refcount}'.format(refcount=python_dict_refcount))
refcount1 = _Py_REFCNT(obj)
print(f'Inside borrowed_reference initially: {refcount1}')
another_ptr_to_object = obj
refcount2 = _Py_REFCNT(obj)
print(f'Inside borrowed_reference after new pointer: {refcount2}')
# Casting to a managed reference to call a cdef function doesn't increase the count
refcount3 = Py_REFCNT(<object>obj)
print(f'Inside borrowed_reference with temporary managed reference: {refcount3}')
# However calling a Python function may depending on the Python version and the number
# of arguments.


print(f'Initial refcount: {python_dict_refcount}')
owned_reference(python_dict)
borrowed_reference(<PyObject *>python_dict)
7 changes: 5 additions & 2 deletions docs/src/userguide/language_basics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -710,8 +710,11 @@ Cython won't perform automatic :c:func:`Py_INCREF`, or :c:func:`Py_DECREF`, e.g.
will display::

Initial refcount: 2
Inside owned_reference: 3
Inside borrowed_reference: 2
Inside owned_reference initially: 2
Inside owned_reference after new ref: 3
Inside borrowed_reference initially: 2
Inside borrowed_reference after new pointer: 2
Inside borrowed_reference with temporary managed reference: 2


.. _optional_arguments:
Expand Down

0 comments on commit bc59042

Please sign in to comment.