Invoking Variables...?

Hello!

I am not really understanding the mechanics of treating functions as objects.
For instance, if I were to set a variable equal to some function, how is it that I can then invoke that variable?

Here is an example of what I mean:

varaible = exisiting_function(arg)

#prints the result as would calling the existing_function()
print(variable(arg))

It seems strange to me that we could pass a variable an argument…

1 Like

The concept of variables is a little wobbly here. It’s much easier and more accurate to start thinking of objects and names.

Our function definition def func: pass creates a function object and assigns it to the name func. But the name is more like a label, the object is the important part. We can add additional labels to the same object, label2 = func. In this way we can even remove the original func name from our namespace del(func) but we still have the original function object. It can be referenced by label2.

Here’s a super simple example of how this can work-

a = 3 b = a b = 4 print(a)

You’ll find various description of Python’s naming system but sticky labels you add to your objects is a decent intro (you can create, move around or throw away the sticky labels; the objects you stuck the labels to are still there).

1 Like

Okay, cool. This is all super fascinating! Thank you for explaining.
Just to ensure that I am properly understanding, in your example with label2 = func and def func: pass , when you reassign func to label2 , is it that you are instantiating another instance of func in a way? So if you delete the original you would still have the other instance, which was protected by the del operation by label2… let me know if I am correct in my understanding.

Perhaps I chose the wrong example for names before, a second example might make things clearer. You need to separate between immutable objects like integer numbers and mutable objects like lists.

a = ["red"] b = a a[0] = 5 print(b)

If you combine this with the above example the overall view is better. Names are just references to objects. Some objects are mutable (can be changed) others are immutable (integers, floats, tuples etc.).

Back to the function question, it is not like an instance. It is exactly the same object. If you wanted some confirmation you could use the id() function (in CPython this is the memory address). Here is another example of the same function object being passed around-

def func ():
    pass


new_label = func

# Functions are not mutable exactly but you can bind additional attributes to 
# a function object
func.attr = 3

# But it is still the same object (with the same attrbiutes)
print(label.attr)

Python is perfectly happy assigning multiple references to the same object. In fact that is how lists and similar operate. They are a sequence of references to other objects.

In this way copying, or slicing a list does not require copying every underlying object, it just copies the references (there are tools to perform deep instead of shallow copies but they are used less frequently).

Alright, two follow-up questions.
First, if we define func, pass it to label2, and then call del(func), how is it that we can still print func using label2 if both are “exactly the same object”?
Also,“Python is perfectly happy assigning multiple references to the same object”, how does it know which value to pass back when called?

Thanks!

You can clear up func because all you’re doing is removing the label, you don’t actually remove the object. I may be misunderstanding your second query but if your references could change unexpectedly programming would be a bit difficult.

Thanks for getting to the first point. So what you are saying is that once you create an object, even if it is removed from the text interface via removing the label it was once attached to, that python still remembers it?

For my second point, we can return to the example you gave above:

a = ["red"]
b = a
a[0] = 5
print(b)

Here it seems that multiple labels are being assigned to a.
How is it then that python will print 5 when the last line is run?

Yes. By and large Python deals with the allocation and removal of memory itself. In its simplest form this is done by reference counting, that is, when every reference to an object has been removed then the object is scheduled for removal (freed from memory).

In the previous example more than one reference was bound to a function object, we can remove one of the references but as the reference count is still greater than zero the object itself is not removed. All that happens is removal of the “label”. If you then removed the second reference: label2, the object would be removed (reference count reached zero).

The main point of that example is that a is not the object. Just a reference to it. The syntax ["red"] creates a list object and populates it with a reference to a second object (a string object “red”). But you could run that part of the line on its own, you don’t have to assign it, or you could assign it to multiple references.

It may help to separate the object from the name a -

# this is a valid line, create an empty list object
[]
# but do not assign any references to it

# create another empty list object
# assign multiple references to it
a = b = c = []

Since this is a list object it is mutable and can be changed. Neither a nor b are changed in that previous example, the object they point to is changed.

2 Likes

Thank you so much for sticking with me through this process of figuring this all out!
I had not anticipated that my initial question would go so deep :slight_smile:
Your last comment here really makes everything above more clear! I believe I am starting to appreciate the concepts and am looking forward to getting more comfortable with these ideas as I continue learning about programming and computer science!

2 Likes