Skip to content

Latest commit

 

History

History
291 lines (215 loc) · 13 KB

(old)3_Variables.md

File metadata and controls

291 lines (215 loc) · 13 KB

Intro to Variables

Variables, types, and underlying objects

As mentioned in the previous section, everything in Python is an object and has a class type. That also applies to variables, but in a Pythonic way. Variables always have a type, but you don't declare the type of variables in advance: the type of a variable is determined dynamically when a value is assigned to it.

Let's start with a new interactive mode session and try to use a varible that hasn't yet been assigned—an error will occur:

> py
Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 23:03:10) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined

We referred to "x" as a variable but at this point, as far as Python is concerned, it doesn't exist.

Now let's assign a value to that variable name, get the interpreter to evaluate the variable, and then check its type using the type() function:

>>> x = 5
>>> x
5
>>> type(x)
<class 'int'>

After the assignment statement, the variable has been defined and can be evaluated, and it has a type, int. So, looking at before and after, the variable either didn't exist, or it existed and had a value and a type.

Python is said to be strongly typed, and it is in the sense that every variable that's been defined has one specific type. Because of that, the operations you can do with a variable are limited by its type. For example, just as a number literal can't be concatenated to a string, so also a number variable can't be concatenated.

>>> x = "The answer is "
>>> y = 42
>>> x + y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str

However, Python does dynamic typing, and a new assignment can result in a variable name taking on a new type.

>>> x = 5
>>> type(x)
<class 'int'>
>>>
>>> x = "hi"
>>> type(x)
<class 'str'>

There are some subtleties going on here, and it will be helpful to understand them.

In other languages, you might think of a variable such as x as a slot that takes a value, and a type declaration determines what type of values can go into that slot.

In Python, a variable x is a name that gets created and associated to an object when an object (such as the number 5) is assigned to it. A new assignment statement can assign that name to a different object. When we use the type() function to get the type of a variable, we're actually asking for the type of the object assigned to that variable name. It's the underlying objects that have a fixed type, not the name.

The built-in id() function will help us understand what's going on. Every object in Python has a unique numeric ID (its hash). For example, the literal 6 evaluates to an object of type int with the value 6, and that object has an ID. When we pass that literal to the id() function, we get the ID of the object it resolves to:

>>> id(6)
140704550147904

If a numeric expression evaluates to the integer value 6, then asking for the ID of that expression will return the very same ID:

>>> id (4 + 2)
140704550147904

The id() function is telling us that the expression result is the very same object as the result of evaluating the literal.

OK, let's apply this to variables: if we assign the integer 6 to a variable, then ask for the ID of that variable, we'll get the ID for the integer object assigned to the variable:

>>> x = 6
>>> id(x)
140704550147904

Again, it's the very same object that the literal and the expression resolved to.

What if we create a second variable, y, by assigning x to y?

>>> id(y)
140704550147904

Both variables refer to the very same object.

If we assign a different value to x, it gets associated with a different object that will have a different ID:

>>> id(42)
140704550149056
>>> x = 42
>>> id(x)
140704550149056

So, each of the objects has a fixed type and ID that never change. But the type and ID associated with the variable name depends on the object assigned to that variable name. And that can change dynamically.

>>> x = 42
>>> id(x)
140704550149056
>>>
>>> x = 3.14
>>> id(x)
2054141077712

Note: if you try using id() on strings, IDs for strings work differently than IDs for numbers. Two occurrences of a number with a given value are always the same object, and so two variables with the same numeric value will always have the same ID. But two string objects might have the same value yet be different string objects. This can get a bit confusing. The key point is that, at any given time, if two string variables have the same ID, they both refer to the same string object.

If you're coming to Python from a language like C or Pascal in which variables are strongly typed, this is a aspect of Python that may take getting used to: objects are strongly and immutably typed, but variables are dynamically and mutably typed.

When the type of an object assigned to a variable really matters, the built-in isinstance() function can be useful. It takes two arguments, an object and a type, and returns a boolean value True or False depending on whether the object is of that type. The object can be referred to using a literal expression, but more typically you'll pass a variable.

>>> isinstance("hi", str)
True
>>> x = "hi"
>>> isinstance(x, str)
True
>>> isinstance(x, int)
False

Getting a bit advanced, the second argument passed into isinstance() can be a sequence of types (specifically, a tuple—we'll learn about sequence types a bit later). So, for example, we can check if a variable is an instance of any of the numeric types:

>>> x = 42
>>> isinstance(x, (int, float, complex))
True
>>> x = 3.14
>>> isinstance(x, (int, float, complex))
True
>>> x = 4-5j
>>> isinstance(x, (int, float, complex))
True

As with type(), isinstance() is telling us about the object assigned to the variable name, not about the variable name itself.

(Im)mutability

A key concept for variables is mutability: once a value is assigned to a variable, can it be changed or not?

In Python, numeric and string objects are immutable: they can't be changed. From what we've seen of numeric objects, this seems obvious: the object for the integer value 6 is simply a different object from that for the integer value 7.

But you might be thinking about mutability of variables. That's not really the right question to ask: as we've seen, variables are simply names with objects assigned to them, and a name can easily be reassigned with a different object. So, an underlying numeric object may be immutable, but the object assigned to a given variable can certainly be change to a different object.

For strings, things are a bit less obvious. That's because strings are sequences of characters. In the previous lesson, we had a brief peek at the sequence nature of strings, and saw that the individual character elements within a string literal can be referenced by index. The same is true for strings assigned to a variable:

>>> x = "cats and dogs"
>>> x[2]
't'

So that raises a question for string variables: Are the character elements within a string variable mutable or immutable?

The answer for Python is that the elements of a string are immutable, including for strings assigned to a variable: character elements in a string cannot be changed. If you try, you'll get an error:

>>> x[2] = 'r'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment

If you need to change any characters in a string, a new string object will be needed.

It was certainly a reasonable question to ask. Later, we'll learn about other sequence types in Python, and some of these are mutable in the sense of allowing elements in the sequence to be changed. But strings are not like that.

Variables can be invaluable!

In this last section, we'll get a bit more advanced. Okay, there's an intended pun in the heading. We've said that variables are names given to objects. And from other object-oriented programming we're familiar with the idea of objects being values of different kinds: numbers, strings, animals, vehicles and the other usual suspects from OOP tutorials. But we've said that in Python everything is an object. So, can we use variables for things that aren't typical kinds of values? Yes, we can!

Let's take types as an example. We've seen that the type() function returns the type of objects (or objects referred to by a variable) that are passed to it. When the result of type() is displayed in interactive mode, we see a string. But as we saw in the previous lesson, the result isn't a string; it's an object of the type type. We can see this by passing the result of a call to type() back into itself.

>>> type(6)
<class 'int'>
>>> type(type(6))
<class 'type'>

So, <class 'int'> is an object of type <class 'type'>. Since <class 'int'> is an object, we can assign it to a variable.

>>> i = type(6)
>>> i
<class 'int'>

Now, you might be wondering, Interesting; but is it useful? Well, here's an example of a type variable being used.

>>> i = type(6)
>>> isinstance(42, i)
True

Once we have assigned a type object to a variable, we can use that variable anywhere that a type is expected. Here's another example: Remember the constructor function int() that can be used to convert a float to an integer? Well, the variable i in our example can now be used as the int function to convert the float 2.6 to the integer 2:

>>> i(2.6)
2
>>> type(i(2.6))
<class 'int'>

Again, you might be thinking, Sure, it can be used; but how can it be useful? Granted, these have been toy examples. But there's a good chance you'll eventually have a need for it with your own custom types. We haven't covered functions or flow control statements yet, but here's a preview illustrating a practical use of a variable, kind, that gets type objects assigned to it:

def speak(kind):
     if isinstance(kind, cat):
         print("meow")
     elif isinstance(kind, dog):
         print("woof")
     else:
         print("spam!")

Okay, we've seen that type objects can be assigned to variables. What about functions? Functions are also objects, and so can also be assigned to variables.

We saw something like that above with the variable i used as a function, though in those examples it was a type object assigned to the variable. But we could also assign the built-in functions type() or print() to variables.

>>> p = print
>>> p("spam\nand more spam")
spam
and more spam
>>> t = type
>>> t(6)
<class 'int'>

Again, these examples are a bit contrived. But once you've started defining your own custom functions, you'll probably find it useful to assign functions to variables. Here's an illustrative example of a function that returns functions:

def pick_pet_creator_fn(kind):
    if isinstance(kind, cat):
        f = MyCatClass.create_a_cat
    if isinstance(kind, dog):
        f = MyDogClass.create_a_dog
    if isinstance(kind, zebra)
        f = MyZebraClass.create_a_zebra
    return f

This function returns one of three different functions based based on the type that's passed in. Internally, it assigns a function to the variable f and then returns f to the caller. In the calling context, it might be used as follows:

    fn = pick_pet_creator_fn(requested_kind)
    my_new_pet = fn()

Review

In this lesson, we learned about the assignment operator, =, and about compound assignment operators:

  • +=: with numbers, addition / assignment; with strings, concatenation / assignment
  • -=: subtraction / assignment
  • *=: multiplication / assignment (for numbers, not strings)
  • /=: division / assignment
  • //=: integer division / assignment
  • %=: modulo (remainder) / assignment
  • **: exponentiation / assignment

We also learned that variables are names that get objects assigned to them. Every object has a specific type and a unique ID which can't change. But the object that a given variable name refers to can be changed, and so the variable can dynamically be associated with a different type.

We learned about the built-in id() function that provides a glimpse into the identify of the objects assigned to variables. We also learned about the isinstance() function that, like type() tells us about the type of the object assigned to a variable.

We learned that Python strings objects are immutable—in particular, that the character elements within a string cannot be changed.

Finally, we saw some more advanced ways that variables can be used by assigning more abstract object types to variables, such as functions or types themselves.