Week 3 - Dive Into Python, Part I

This week's Jupyter notebook introduces us to basic data types and actions in Python.

Without further ado, let's start:

Numbers, variables and basic arithmetic

Assigning a number this way will create a variable of type int. If unsure, one can always check the type of a variable with the type() built-in method.

Aside from the four basic arithmetic operators +, -, *, /, there exist other operators in Python as well.

One such example is the power operator (**), where a ** b can be used for calculating $a^b$:

Note: The int type is flexible in terms of representing relatively huge numbers:

We can also represent floating-point numbers just as easily:

The following syntax also works for representing float types:

Here's an example of a complex type, which is specified by having the j suffix after the imaginary part of the number:

As an example, this type comes with its own multiplication operator:

Libraries

(Note: we learn about libraries more in detail, later...)

Some useful functions, such as sin() and cos(), are not directly available:

To be able to use these functions, we have to import a library that contains them. Such useful math functions can be accessed by importing the math library, as follows:

To use a function from a library, we use the lname.fname(...) syntax, where lname represents the imported library name and fname represents the function name. As an example:

Aside from functions, can also provide special values such as pi:

Boolean values and logic

We can define variables to have the Boolean values of True and False to do logical operations.

The and binary operator evaluates to True if both of its operands are True, otherwise it evaluates to False.

The or binary operator evalues to True if any of its operands are True, otherwise it evaluates to False.

not negates the value of a Boolean variable.

Comparison operators (such as >, <, >=, <=, ==, !=) will return Boolean values:

Container datatypes

Python gives us many types for storing data. We'll explore each one, starting from strings:

Strings

Strings represent a sequence of characters, and are created with using enclosing single (') or double (") quotation marks:

Python allows us to do several operations with indexes, one such example being concatenation:

Although both numbers and strings are able to use +, Python will not allow mixing integers with strings:

To convert a string into a number, we can use int():

We can also check if a string contains another string inside it, using the in keyword:

Strings are case sensitive:

The len() function allows us to get the length of a string:

Strings and other sequential containers allow repetition with *:

Indexing and Slicing

Sequential containers such as strings and lists allow us to access elements using their position (index) in the container. Indexing and slicing use square brackets [].

Note 1: In python, indexes start at 0. This means that the first character you see in the string has index 0, the second character has index 1, and so on.

Note 2: Python allows negative indexing. Negative indexes start by counting from the end of the string.

It's best to proceed with a simple visualization of a string (ceng240) along with its positive and negative indexes:

0 1 2 3 4 5 6 Positive indexing
c e n g 2 4 0
-7 -6 -5 -4 -3 -2 -1 Negative indexing

For example, the 'g' element can be accessed via positive indexing (3), as well as negative indexing (-4).

Simple indexing has the syntax of container[idx], which will access the element with the given index. Here are some examples with positive indexing:

Here are some examples with negative indexing:

One can observe that for negative indexing, we have the following equality:

s[-idx] = s[len(s) - idx]

With the s[idx1:idx2] syntax, Python allows us to access a slice (i.e, multiple elements) of a container. The slice starts from the first index (inclusive), up to (but not including) the second index parameter.

Some examples of slicing:

If idx1 is left empty, the slice will start from the start of the container.

(i.e, by default, idx1 = 0)

If idx2 is left empty, the slice will continue until the end of the container.

(i.e, by default, idx2 = length of the container)

(you can add additional cells under this cell to practice your slicing skills)

Finally, Python allows strided indexing, with the s[idx1:idx2:stride] syntax. The stride (or increment) parameter allows the slice to "skip over" consecutive elements of the string. After indexing the first element (with idx1), the next index will be selected by incrementing (or decrementing!) the current index with stride, and so on.

It's best to learn by example, as always:

Leaving the stride parameter empty is the same as setting it to 1:

Special characters

The following will not be accepted by Python, because we are trying to use quotation marks inside a string:

To solve this, we can use single quotes (') to define the string, and use double quotes (") inside the string:

The same technique can be used for using single quotes in a string:

Alternatively, we can use the backslash (\) character to make Python understand that this character should be inside the string:

We can make multi-line strings by using the docstring syntax, where the string is enclosed by three single quotation marks ('''text''').

Note that this will also preserve the lines:

When we display k directly, we see the following:

Here, \n is what Python uses to represent a vertical line, as a special character (called the newline character). We can use it ourselves to print text in different lines as follows:

Note: Strings are immutable, which means after we create a string, it is not possible to change its content:

Here, we would create a new string:

Lists and Tuples

Lists (and tuples) are sequential containers that can hold data of different types inside. They are defined using square brackets [ ].

As we saw with strings, we can do common operations on lists, such as slicing and indexing:

Lists can be elements of other lists, as follows:

You could represent a matrix as a list of lists:

Lists are mutable, which means the data inside the container can be changed after initialization:

If we want the data to unchangeable after initialization, we use tuples instead, which are immutable. Tuples use parentheses () instead of square brackets.

As lists are mutable, we can add new elements, or remove elements from the list. In particular, [] by itself represents an empty list.

Adding elements via list.append() will add an element to the end of the list:

Lists allow concatenation (like strings), so you can combine lists or add new elements like the following:

Lists allow repetition, like strings:

And finally, one can check for membership in lists using the in / not in syntax:

Dict

Dictionaries are efficient containers which hold key-value pairs, where each key accesses a specific value in the dictionary. They are defined using curly braces {}.

Adding a key-value pair to a dict is done by the following syntax:

We can check if a value exists for a key by the following syntax:

If the provided key does not exist, Python will complain with a KeyError:

Here's an example of a dictionary with different keys and values:

We can change the value of a key using the same syntax:

And we can delete a key-value pair by specifying the key:

We can access the individual keys and values with:

Sets

Sets are similar to dicts, and they also use curly brackets {}. Sets are mutable, however, elements of sets must be immutable. Each element in a set can appear only once.

Sets support addition and removal of new elements.

For immutable sets, you can use frozenset.

Sets have useful functions such as union, intersection and other set operations.

Expressions, Execution Logic, Ordering and Conversions

Expressions represent calculations from operations among data. These can be arithmetical operations, logical operations, indexing and membership. In terms of priority (precedence), they are ordered in the following manner:

indexing ([]) -> exponentiation (**) -> multiplication / division based operators (*, /, //, %) -> addition / subtraction (+, -) -> logical comparison (>, <=, etc.) and membership (in, not in) -> other logical operators (not > and > or)

Of course, to avoid confusion, don't forget to use parenthesis wherever necessary.

When dealing with logical and and or, Python will take shortcuts instead of evaluating everything directly.

Consider the following examples:

In the example above, since True or X will have the boolean value of True for any Boolean value of X, the result will be True without evaluating the value of X. Same for the example below:

When the result is not clear from the first operand, then the second operand will also be evaluated:

However, Python evaluates such expressions from left-to-right. The following expression, even if it would be trivially True given that X or True is always True, will evaluate the first operand and then throw an error:

Type conversion

When applying a binary operator, Python will attempt to implicitly convert one data type to another if they are different types.

In the case of boolean values:

When implicitly converting a bool to a number, True will be converted to 1 and False will be converted to 0.

When implicitly converting a number to bool, 0 will be converted to False, and any non-zero number will be converted to 1.

Python supports multiple assignment, and you can also swap values of variables in place without having to use a third temporary variable.