8

I would like to know why this code prints 4 instead of 3. Where is the fourth reference?

import sys

def f(a):
    print(sys.getrefcount(a))

a = [1, 2, 3]
f(a)
ruohola
  • 21,987
  • 6
  • 62
  • 97
ThunderPhoenix
  • 1,649
  • 4
  • 20
  • 47
  • Does this answer your question? [Why does sys.getrefcount() return 2?](https://stackoverflow.com/questions/10302133/why-does-sys-getrefcount-return-2) – B. Go Dec 12 '19 at 20:04
  • If you get this as an audit question, put it as OK (if the wote count is still positive), not as the duplicate of https://stackoverflow.com/questions/10302133/why-does-sys-getrefcount-return-2 as I just did (even if it is almost a perfect duplicate...) – B. Go Dec 12 '19 at 20:05
  • Interestingly, if you run it with Python 3.11, it prints `3`. – S.B Aug 18 '23 at 12:47

2 Answers2

6

We can make sense of this in steps:

import sys

print(sys.getrefcount([1, 2, 3]))
# output: 1
import sys

a = [1, 2, 3]
print(sys.getrefcount(a))
# output: 2
import sys

def f(a):
    print(sys.getrefcount(a))

f([1, 2, 3])
# output: 3
import sys

def f(a):
    print(sys.getrefcount(a))

a = [1, 2, 3]
f(a)
# output: 4

So to reiterate:

  • First reference is the global variable a
  • Second reference is the argument passed to the f function
  • Third reference is the parameter that the f takes
  • Fourth reference is the argument passed to the getrefcount function

The reason for no fifth reference, from the parameter getrefcount takes, is that it's implemented in C, and those don't increase the reference count in the same way. The first example proves this, since the count is only 1 in that.

ruohola
  • 21,987
  • 6
  • 62
  • 97
1

Update:

Python 3.11+ works differently now. You'll see 2 instead of 3 with this function:

import ctypes

def get_ref_count(id_: int, un_used):   #  <----------
    return ctypes.c_long.from_address(id_).value

a = [1, 2, 3]
a_id = id(a)
print(get_ref_count(a_id, a))   # prints `2` instead of `3`.

I wanted to explain it a bit more. First I'm gonna use another method to get the number of references using it's id:

import ctypes

def get_ref_count(id_: int):
    return ctypes.c_long.from_address(id_).value

a = [1, 2, 3]
a_id = id(a)
print(get_ref_count(a_id))   # 1

by just passing a as the argument of the get_ref_count, the count goes to 3.

import ctypes

def get_ref_count(id_: int, un_used):   #  <----------
    return ctypes.c_long.from_address(id_).value

a = [1, 2, 3]
a_id = id(a)
print(get_ref_count(a_id, a))   # 3

See, I didn't actually do anything with a. But what happens?

When you call a funcion fn(a), Python will load the fn and a and puts them on the stack. this is LOAD_NAME instruction. This instruction calls Py_INCREF() which increments the number of references by one. Then in order to call that function, it creates a Frame object and pass that reference as the frame's local attribute (Function's parameters are local variables of that function):

from inspect import currentframe

def f(var):
    print(currentframe().f_locals)   # {'var': [1, 2, 3]}

a = [1, 2, 3]
f(a)

f_locals is exactly functions's local scope returned by locals().

So that's why it incremented by 2.

S.B
  • 13,077
  • 10
  • 22
  • 49