0

I'm working in Python. I have the following function:

def f(list_value: list):
    a = numpy.zeros(5)
    b = numpy.zeros(4)
    list_value.append(a)
    list_value.append(b)

and I use it as follows:

list_value[]
f(list_value)
a = list_value[0]
b = list_value[1]

My question is: is it safe to use a and b?

If something equivalent to this code was written in C it would be unsafe because a and b would be pointer to the memory on the stack of the f function and it would be unsafe to use them because the content of that memory would change. But I'm in Python now, and I don't know if Python offers some mechanism against this or it is unsafe even in Python.

user2390182
  • 72,016
  • 6
  • 67
  • 89
fabianod
  • 501
  • 4
  • 17

1 Answers1

1

Python is coded mostly in C (for the cpython version), not C++. numpy also has a lot of underlying C code. But Python programmers usually think in terms of Python objects and refereces ("pointers"), without trying to define the underlying C or machine level equivalents. Everthing is an object, and stored "somewhere" in memory. As long as something has a "reference" to that object if percists. When the reference count drops to 0, it is a candidate for garbage collection. So the key ideas are "what creates an object", and "what contains a reference to it"

It's also important to desinguish between objects that are "mutuable", whose values or properties can change, and "immutable" objects.

Variables just contain references. The identity of a variable is determined by what is references. A variable is created by assignment.

Your code:

list_value = []
f(list_value)
a = list_value[0]
b = list_value[1]

list_value is a variable that points to a "empty" list. A list is a object class that can "store" references to other objects. That store is probably a C array of pointers. It also has "growth" room so it efficient to "append" objects to the list. But those are implementation details. We talk about a list containing objects, though in detail the list's array just has pointers, with the actual objects store somewhere else in memory (that confuses people who try to get the "memory footprint" of a list, with a naive use of sys.getsizeof.)

The next line calls your function, passing it a reference to the list that you just created.

def f(list_value: list):
    a = numpy.zeros(5)
    b = numpy.zeros(4)
    list_value.append(a)
    list_value.append(b)

The function 'locally' creates 2 numpy arrays (also objects). They are referenced locally by variables 'a' and 'b', but with append, the arrays are "placed" in the list.

Usually a function returns something, such as return list_value. But here the return is None, and list_value remains accessible since it was just passed by reference, and is mutable.

On return, the function local variables disappear, but the arrays still "exist" as elements of the list.

So they can be accessed in various ways.

a = list_value[0]
c, d = list_value    
new_list = list_value[:]
back_list = list_value[::-1]
print(list_value)
list_value[1][::2] = [10.0, 20.0]

list_value has no indication that they were originally assigned to local variables 'a' and 'b'. The arrays in the list are, in that sense, "nameless". But we can do anything with these arrays that is consistent with their shape and dtype.

hpaulj
  • 221,503
  • 14
  • 230
  • 353