Python Training - Lesson 1 - Variables and Data Types

Variables

A variable refers to a certain value with specific type. For example, we may want to store a number, a fraction, or a name, date, maybe a list of numbers. All those need to be reachable using some name, some reference, which we create when we create a variable. After we create a variable with a value, we can peek at what's inside using "print" method.


In [75]:
my_name = 'Adam'
print my_name


Adam

In [76]:
my_age = 92
your_age = 23
age_difference = my_age - your_age
print age_difference


69

How to assign values to variables?

Single assignment


In [77]:
a = 1

Multiple assignment


In [78]:
a, b, c = 1, 2, 3
print a, b, c


1 2 3

In [79]:
a = b = c = d = "The same string"
print a, b, c, d


The same string The same string The same string The same string

What is a reference? What is a value?

You could ask: does Python use call-by-value, or call-by-reference? Neither of those, actually. Variables in Python are "names", that ALWAYS bind to some object, because mostly everything in Python is an object, a complex type. So assigning a variable means, binding this "name" to an object.

Actually, each time you create a number, you are not using a classic approach, like for example in C++:

int my_integer = 1;

When we look at an integer in Python, it's actually an object of type 'int'. To check the type of an object, use the "type" method.


In [80]:
type(my_age)


Out[80]:
int

To be completely precise, let's look at creating two variables that store some names. To see where in memory does the object go, we can use method "id". To see the hex representation of this memory, as you will usually see, we can use the method "id".


In [81]:
some_person = "Andrew"
person_age = 22
print some_person, type(some_person), hex(id(some_person))
print person_age, type(person_age), hex(id(person_age))


Andrew <type 'str'> 0x5a3e8a0L
22 <type 'int'> 0x1d47f60L

Now, let's change this name to something else.


In [82]:
some_person = "Jamie"
person_age = 24
print some_person, type(some_person), hex(id(some_person))
print person_age, type(person_age), hex(id(person_age))


Jamie <type 'str'> 0x5a3e878L
24 <type 'int'> 0x1d47f30L

The important bit is that, even though we use the same variable "person_age", the memory address changed. The object holding integer '22' is still living somewhere on the process heap, but is no longer bound to any name, and probably will be deleted by the "Garbage Collector". The binding that exists now, if from name "person_age" to the int object "24".

The same can be said about variable 'some_person'.

Mutability and immutability

The reason we need to talk about this, is that when you use variables in Python, you have to understand that such a "binding" can be shared! When you modify one, the other shared bindings will be modified as well! This is true for "mutable" objects. There are also "immutable" objects, that behave in a standard, standalone, not-changeable way.

Immutable types: int, float, decimal, complex, bool, string, tuple, range, frozenset, bytes

Mutable types: list, dict, set, bytearray, user-defined classes


In [83]:
shared_list = [11,22]
my_list = shared_list
your_list = shared_list
print shared_list, my_list, your_list


[11, 22] [11, 22] [11, 22]

Now, when we modify the binding of 'shared_list' variable, both of our variables will change also!


In [84]:
shared_list.append(33)
print shared_list, my_list, your_list


[11, 22, 33] [11, 22, 33] [11, 22, 33]

This can be very confusing later on, if you do not grasp this right now. Feel free to play around :)

Data types

What is a data type? It is a way of telling our computer, that we want to store a specific kind of information in a particular variable. This allows us to access tools and mechanisms that are allowed for that type.

We already mentioned that actually every time we create a variable, we create a complex type variable, or an object.

This is called creating an object, or instantiating an object. Each object comes from a specific template, or how we call it in Object Oriented Programming, from a class.

So when you assign a variable, you instantiate an object from a class.

In Python, every data type is a class!

Also, we will use some built-in tools for inspection - type() and isinstance() functions. The function type() will just say from which class does this object come from. THe function isinstance() will take an object reference, and then a class name, and will tell you if this is an instance of this class.

Let's review data types used in Python (most of them).

Numeric types

These types allow you to store numbers. Easy.

int

Integers. If you create a really big integer, it will become a 'long integer', or 'long'.


In [85]:
a = 111
print a, type(a)


111 <type 'int'>

In [86]:
b = 111111111111111111111111111111111
print b, type(b)


111111111111111111111111111111111 <type 'long'>

float

Floating decimal point numbers. Used usually for everything that is not an 'int'.


In [87]:
c = 11.33333
d = 11111.33
print c, type(c)
print d, type(d)


11.33333 <type 'float'>
11111.33 <type 'float'>

complex

Complex numbers. Advanced sorceries of mathematicians. In simple terms, numbers that have two components. Historically, they were named 'real' component (regular numbers) and 'imaginary' component - marked in Python using the 'j' letter.


In [88]:
c = 2 + 3j
print c, type(c)


(2+3j) <type 'complex'>

Numeric operations


In [4]:
# Addition
print(1+1)

# Multiplication
print(2*2)

# Division
print(4/2)

# Remainder of division
print(5%2)

# Power
print(2**4)


2
4
2.0
1
16

Strings

Represents text, or to be more specific, sequences of 'Unicode' characters. To let Python know we are using strings, put them in quotes, either single, or double.


In [89]:
a = "Something"
b = 'Something else'
print type(a), type(b)


<type 'str'> <type 'str'>

Even though strings are not numbers, you can do a lot of operations on them using the usual operators.


In [90]:
name = 'Adam'
print name + name
print name * 3


AdamAdam
AdamAdamAdam

Actually, strings are 'lists' of characters. We will explore lists in just a moment, but I want you to become familiar with a new notation. It is based on the order of sequence. When I say, "Give me the second character of this string", I can write is as such:


In [91]:
print 'Second character is:  ' + name[1]


Second character is:  d

Since we are counting from 0, the second character has index = 1.

Now, say I want characters from second, to fourth.


In [92]:
print 'From second to fourth: ' + name[1:4]


From second to fourth: dam

In [93]:
print 'The last character (or first counting from the end) is: ' + name[-1]


The last character (or first counting from the end) is: m

In [94]:
print 'All characters, but skip every second: ' + name[0:4:2]


All characters, but skip every second: Aa

These operations are called 'slicing'.

We can also find substrings in other substrings. THe result is the index, at which this substring occurs.


In [6]:
some_string = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAxxAAAAAAAAAAAAAAAAAAAA"
substring = "xx"
location = some_string.find(substring)

print("Lets see what we found:")
print(some_string[location:location+len(substring)])


Lets see what we found:
xx

We can also replace substrings in a bigger string. Very convenient. But more complex replacements or searches are done using regular expressions, which we will cover later


In [7]:
some_string = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAxxAAAAAAAAAAAAAAAAAAAA"
substring = "xx"

print(some_string.replace( substring , "___REPLACED___"))


AAAAAAAAAAAAAAAAAAAAAAAAAAAAA___REPLACED___AAAAAAAAAAAAAAAAAAAA

Boolean

It represents the True and False values. Variables of this type, can be only True or False. It is useful to know, that in Python we can check any variable to be True or False, even lists!

We use the bool() function.


In [5]:
a = True
b = False

print("Is a equal to b ?")
print(a==b)

print("Logical AND")
print(a and b)

print("Logical OR")
print(a or b)

print("Logical value of True")
print( bool(a) )

print("Logical value of an empty list")
print( bool([]) )

print("Logical value of an empty string")
print( bool("") )

print("Logical value of integer 0")
print( bool(0) )


Is a equal to b ?
False
Logical AND
False
Logical OR
True
Logical value of True
True
Logical value of an empty list
False
Logical value of an empty string
False
Logical value of integer 0
False

List

Prepare to use this data type A LOT. Lists can store any objects, and have as many elements, as you like. The most important thing about lists, is that their elements are ordered. You can create a list by making an empty list, converting something else to a list, or defining elements of a list right there, when you declare it.

Creating lists.


In [95]:
empty_list = []
list_from_something_else = list('I feel like Im going to explode')
list_elements_defined_when_list_is_created = [1, 2, 3, 4]
print empty_list
print list_from_something_else
print list_elements_defined_when_list_is_created


[]
['I', ' ', 'f', 'e', 'e', 'l', ' ', 'l', 'i', 'k', 'e', ' ', 'I', 'm', ' ', 'g', 'o', 'i', 'n', 'g', ' ', 't', 'o', ' ', 'e', 'x', 'p', 'l', 'o', 'd', 'e']
[1, 2, 3, 4]

Selecting from lists


In [96]:
l = ["a", "b", "c", "d", "e"]
print l[0]
print l[-1]
print l[1:3]


a
e
['b', 'c']

Adding and removing from a list


In [97]:
l = []
l.append(1)
print l
l[0] = 222
print l


[1]
[222]

In [98]:
l.remove(1)
print l


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-98-ad0322e67e7f> in <module>()
----> 1 l.remove(1)
      2 print l

ValueError: list.remove(x): x not in list

In [ ]:
l = [1,2,3,3,4,5,3,2,3,2]

# Make a new list from a part of that list
new = l[4:7]
print new

Iterating over a list

But lists are not only used to hold some sequences! You can iterate over a list. This means no more, no less, then doing something for each of the elements in a given range, or for all of them. We will cover the so-called 'for' loop in next lessons, but I guess you can easily imagine what this minimal example would do.


In [ ]:
# Do something for all of elements.
for element in [1, 2, 3]:
    print element + 20

In [ ]:
# Do something for numbers coming from a range of numbers.
for number in range(0,3):
    print number + 20

In [ ]:
# Do something for all of elements, but written in a short way.
some_list = ['a', 'b', 'c']
print [element*2 for element in some_list]

Even though the short notation is a more advanced topic, it is very elegant and 'pythonic'. This way of writing down the process of iteration is called 'list comprehensions'.

Tuple

A tuple is a simple data structure - it behaves pretty much like a list, except for one fact - you can not change elements of tuple after it is created! You create it the same as a list, but using normal brackets.


In [ ]:
some_tuple = (1,3,4)
print some_tuple
print type(some_tuple)
print len(some_tuple)
print some_tuple[0]
print some_tuple[-1]
print some_tuple[1:2]


other_tuple = 1, 2, 3
print other_tuple
print type(other_tuple)

In [ ]:
# This will cause an error! You can not modify a tuple.
some_tuple[1] = 22

Dictionary

This data structure is very useful. In essence, it stores pairs of values, first of which is always a "key", a unique identifier, and the "value", which is the connected object. A dictionary performs a mapping between keys and values. Because the key is always unique (has to be, we will find out in a minute), there is always exactly one key with specific content. A dictionary is also very efficient - finding a value in a dictionary takes only one operation, whereas searching through a list one by one could require going through the whole list. This means that for any situation, where you need to store lot's of values, that will be often used, it is much better to store them in a dictionary. Also, I recommend to read on Wikipedia on "hash maps".

Creating dictionaries


In [ ]:
empty_dictionary = {}
print empty_dictionary
print type(empty_dictionary)

In [ ]:
dictionary_from_direct_definition = {"key1": 1, "key2": 33}
print dictionary_from_direct_definition

In [ ]:
# Let's create a dictionary from a list of tuples
dictionary_from_a_collection = dict([("a", 1), ("b", 2)])
print dictionary_from_a_collection

In [ ]:
# Let's create a dictionary from two lists
some_list_with_strings = ["a", "b", "c"]
some_list_with_numbers = [1,2,3]
dictionary_from_two_lists = dict(zip(some_list_with_strings, some_list_with_numbers))
print dictionary_from_two_lists
print type(dictionary_from_two_lists)

In [ ]:
# Let's create a dictionary from a dictionary comprehension
dict_from_comprehension = {key:value for key, value in zip(some_list_with_strings, some_list_with_numbers)}
print dict_from_comprehension

Using dictionaries

Add key-value pairs


In [ ]:
d = {}
d["a"] = 1
d["bs"] = 22
d["ddddd"] = 31
print d

In [ ]:
d.update({"b": 2, "c": 3})
print d

Remove items


In [ ]:
del d["b"]
print d

In [ ]:
d.pop("c")
print d

Inspect a dictionary


In [ ]:
# How many keys?
print d.keys()
print len(d)
print len(d.keys())

In [ ]:
# How many values?
print d.values()
print len(d.values())

Iterate over dictionary


In [ ]:
for key, value in d.items():
    print key, value

Example of looking for a specific thing in a list, and in a dictionary:


In [ ]:
l = ["r", "p", "s", "t"]
d = {a: a for a in l}

# Find "t" in list.
for letter in l:
    if letter == "t":
        print "Found it!"
    else:
        print "Not yet!"

# Find "t" in dictionary keys.
print "In dictionary - found it! " + d["t"]

Sets

A set behaves pretty much like a mixture of a dictionary and a list. It has two features:

  • it only has unique values
  • it does not respect order of things - it has no order, like a dictionary

In [ ]:
some_sequence = [1,1,1,1,2,2,2,3,3,3]
some_set = set(some_sequence)
print some_set

some_string = "What's going ooooon?"
another_set = set(some_string)
print another_set

In [ ]:
some_dictionary = {"a": 2, "b": 2}
print some_dictionary


yet_another_set = set(some_dictionary)
print yet_another_set

print set(some_dictionary.values())