Introduction

Hello dear reader. This book is design for newcomers to Python with little knowledge on basic programming. Instructors can used it as a guide for teaching Python.

Basics

  • Central Processing Unit: Very very fast but simple calculator. It is always asking what to calculate next?
  • Main Memory: Very fast temporary storage that save all instruction and data for CPU.
  • Input Devices: Devices that is used to give computer any kind of information, like Keyboards, Mouses, Touch Screens, Microphones, ... .
  • Output Devices: Devices that computer use to give information to you with them, like Screens, Speakers, Printers, ... .
  • Secondary Memory: If power goes down, main memory erased immediately. That's where secondary memory walks in. It's stores huge data and won't lost them without power. Why we shouldn't use secondary memory instead of main memory? Because it is too slow!

Our Minds Vs. Computer Programs

Read text below quickly:

Python has a deisgn philozophy which emphasizes code readebility (notably using whitespece indentetion to delimit code blucks rather than curly brackats or keywords), and a syntax which allows programmers to express concepts in fewer lines of code than might be used in langoages such as C++ or Java. The langoage provides constructs intended to enable writing clear progrems on both a small and large scale.

There are many mistakes in the above text. Did you saw them? All of them?! If yes, good for you! If no, that is the most important difference between a computer and a human brain. Humans know how to solve problems automatically, we almost never see many of details, but computers are just fast and studpid calculators!

Remember every thing that computer is doing should be told to it. Computers doesn't have a brain, they are just very advanced calculators.

What is Python?

Python is an interpreted, general purposed language that is designed in 1991 by Guido Van Rossum. Python can be used for object oriented and functional programming. The language usage areas are tremendous. Some of them are, system programming, graphical user interfaces, game development, developing back-end, machine learning, visualizing data, media processing, scientific programming and many many more.

Python have very readable and simple syntax to write code. Nowadays python frameworks and libraries are every where.

There is only one drawback with python, runtime speed. But even this problem is almost solved by PyPy project and many libararies like numpy and numby.

What is PyCharm?

As a developer we need an environment to code in it. The best environment that I found is PyCharm, it has many features like auto-completion, debugging, version controller support, ... .

What is Jupyter?

An integrated development environment like pycharm isn't exactly what need for most of this course. We are dealing with watching effects of each line of code we write. Jupyter is what we use here.

Python is a scripting language. This is an advantage that we will get familiar through the course.

Hello Python

There is a tradition in learning programming languages, called Hello World program. We are going to respect the traditions and do it.

In [1]:
print('Hello Mickey Mouse!')
Hello Mickey Mouse!

Exercise

  • Make Python write you name

Comments

Everything after a sharp will be commented out and will not considered by interpreter.

You can even use strings as a comment.

In [2]:
# A whole line commented out.

"This is a comment too!"

"""This is a comment with a little diference.
This one is a multiline comment.
Remember this strings are not saved in any variable!
"""

print('Hello Mickey Mouse!')  # Every thing after it will be commented, not before it.
print('Hello Mickey Mouse!')  # Your code should be separated from sharps with two space characters.
Hello Mickey Mouse!
Hello Mickey Mouse!

PEP8

Python enhancement proposal documents are about everything that apply to python. New features, community input on an issue, ... . Probably the most famous and useful one for us, is PEP8.

Development is not single handed anymore, it's a social effort. Groups of many people try to write a code for a same software. PEP8 helps for readability of code for reusing by other people.

Most imporant ones:

  • Indentations are 4 spaces.
  • Maximum line length is 80 or 120.
  • Break long line before operators.
# Yes: easy to match operators with operands
income = (gross_wages
          + taxable_interest
          + (dividends - qualified_dividends)
          - ira_deduction
          - student_loan_interest)
  • Surround top-level function and class definitions with two blank lines.
  • Method definitions inside a class are surrounded by a single blank line.
  • Use seperate imports.
# This is good
import os
import sys

# This is bad
import os, sys
  • Whitespaces in Expressions and Statements.
# This is good
spam(ham[1], {eggs: 2})
foo = (0,)
if x == 4: print x, y; x, y = y, x

# This is bad
spam( ham[ 1 ], { eggs: 2 } )
bar = (0, )
if x == 4 : print x , y ; x , y = y , x

# This is good
ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:]
ham[lower:upper], ham[lower:upper:], ham[lower::step]
ham[lower+offset : upper+offset]
ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)]
ham[lower + offset : upper + offset]

# This is bad
ham[lower + offset:upper + offset]
ham[1: 9], ham[1 :9], ham[1:9 :3]
ham[lower : : upper]
ham[ : upper]
  • Naming Conventions:
    • Modules should have short, all-lowercase, underscore (readability) separated names.
    • Packages should have short, all-lowercase names. Underscore are discouraged.
    • Class names follows CapWords convention.
    • Functions should have short, all-lowercase, underscore (readability) separated names.
    • Methods arguments:
      • Always use self and cls.
      • Group related function by one more blank line between each group.
    • Contants should have all-uppercase, underscore (readability) separated names.
    • Variables should have short, all-lowercase, underscore (readability) separated names.

Variables

Variables are labeled places that you can store data in them. We use all lowercase underscore separated alphabets and numbers for variables. Never start a variable with a number. Python names are case sensitive. Variable names can't have spaces.

In [3]:
famous_disney_character = "Donald Duck"
print(famous_disney_character)
Donald Duck

Numbers and Mathematics

Python have infinite integers, booleans, floating points, ... for representing numbers.

In [1]:
print (2 ** 4096)
1044388881413152506691752710716624382579964249047383780384233483283953907971557456848826811934997558340890106714439262837987573438185793607263236087851365277945956976543709998340361590134383718314428070011855946226376318839397712745672334684344586617496807908705803704071284048740118609114467977783598029006686938976881787785946905630190260940599579453432823469303026696443059025015972399867714215541693835559885291486318237914434496734087811872639496475100189041349008417061675093668333850551032972088269550769983616369411933015213796825837188091833656751221318492846368125550225998300412344784862595674492194617023806505913245610825731835380087608622102834270197698202313169017678006675195485079921636419370285375124784014907159135459982790513399611551794271106831134090584272884279791554849782954323534517065223269061394905987693002122963395687782878948440616007412945674919823050571642377154816321380631045902916136926708342856440730447899971901781465763473223850267253059899795996090799469201774624817718449867455659250178329070473119433165550807568221846571746373296884912819520317457002440926616910874148385078411929804522981857338977648103126085903001302413467189726673216491511131602920781738033436090243804708340403154190336

Arithmetics

These operators are use for arithmetic purposes. Some of them have more priority upon others.

In [2]:
# Arithmetic operators. Upper operators group have higher priority.
# Operators under a group have same priority.

print("2 ** 4:  {}".format(2 ** 4))   # Power operator

print("5 * 6:   {}".format(5 * 6))    # Multiplication operator
print("4 / 5:   {}".format(4 / 5))    # Real division operator
print("10 // 3: {}".format(10 // 3))  # Integer division operator
print("25 % 7:  {}".format(25 % 7))   # Reminder operator

print("23 + 45: {}".format(23 + 45))  # Addition operator
print("21 - 8:  {}".format(21 - 108))   # Subtraction operator

number = 12     # Assignment operator
print("number:  {}".format(number))
2 ** 4:  16
5 * 6:   30
4 / 5:   0.8
10 // 3: 3
25 % 7:  4
23 + 45: 68
21 - 8:  -87
number:  12
In [5]:
# By using parentheses you can change order of operators.
num_1 =  24 + 2  * 12
num_2 = (24 + 2) * 12
print("num_1: {}, num_2: {}".format(num_1, num_2))
num_1: 48, num_2: 312
In [6]:
# Operators with same priority, get calculated from left to right.
num_3 = 2 * 3 / 4
num_4 = 2 / 3 * 4
print("num_3: {}, num_4: {}".format(num_3, num_4))
num_3: 1.5, num_4: 2.6666666666666665

Exercise

  • What is saved in variable equation_result?
equation_result = 5 // 3 + 2 ** 3 / 4 * 5 % 7

Bigger is Better

When you are doing arithmetic operations on numbers, the result is the type that have more precision.

In [2]:
print("35 / 5      = {}".format(35 / 5))  # Result is a floating point number
print("35.0 / 5    = {}".format(35 / 5))  # Result is a floating point number
print("35 / 5.0    = {}".format(35 / 5))  # Result is a floating point number
print("35.0 / 5.0  = {}".format(35 / 5))  # Result is a floating point number
print()
print("23 // 7     = {}".format(23 // 7))  # Result is an integer number
print("23.0 // 7   = {}".format(23.0 // 7))  # Result is a floating point number
print("23 // 7.0   = {}".format(23 // 7.0))  # Result is a floating point number
print("23.0 // 7.0 = {}".format(23.0 // 7.0))  # Result is a floating point number
print()
print("12 * 3      = {}".format(12 * 3))  # Result is an integer number
print("12.0 * 3    = {}".format(12.0 * 3))  # Result is an integer number
print("12 * 3.0    = {}".format(12 * 3.0))  # Result is an integer number
print("12.0 * 3.0  = {}".format(12.0 * 3.0))  # Result is an integer number
35 / 5      = 7.0
35.0 / 5    = 7.0
35 / 5.0    = 7.0
35.0 / 5.0  = 7.0

23 // 7     = 3
23.0 // 7   = 3.0
23 // 7.0   = 3.0
23.0 // 7.0 = 3.0

12 * 3      = 36
12.0 * 3    = 36.0
12 * 3.0    = 36.0
12.0 * 3.0  = 36.0

Names

Each variable need a name.

Reserved Words

They have special meaning for python, so never use reserved words for variable, function, class, ... names!

from import as
True False
None
and or not
try except finally else raise assert
while for break continue else in
class
def lambda return yield
global nonlocal
del
if elif else pass is in
with

Readability

There is a technique called mnemonic. It's about variables name that you choose. They should be short, simple and sensible. Variables names doesn't have any influence on how python execute your code, but it is very important for who wants to read your code!

These three codes are the same thing to python. Python doesn't care about the meaning of your variables (Remember the fast, stupid calculator). It is just doing what we tell to do.

In [9]:
# VERY VERY bad code!
hqpowiefhpqowi = 35.0
poiwhgpoiqf = 12.5
poiwhzpoiqf = hqpowiefhpqowi * poiwhgpoiqf
print(poiwhzpoiqf)

# Better, but it is not mnemonic.
a = 35.0
b = 12.5
c = a * b
print(c)

# Mnemonic code. You can understand it's purpose.
worked_hours = 35.0
pay_rate = 12.5
pay = worked_hours * pay_rate
print(pay)
437.5
437.5
437.5

Strings

String are a sequence of characters that surrounded with single/double quote.

In [4]:
book_author = 'Amir Khazaie'
book_title = "Python for You and Me"

Multiline string are possible with triple single/double quote.

In [2]:
book_description = '''
This book is about Python programming language.
I started this book as a guideline for instructors
and who wants to learn python not as a first programming language.'''

book_license = """
This book is under Creative Common license.
so feel free to contribute to it and read it."""

third_way = 'This is a\
very very long\
string'

print(book_description)
This book is about Python programming language.
I started this book as a guideline for instructors
and who wants to learn python not as a first programming language.
In [3]:
print(book_license)
This book is under Creative Common license.
so feel free to contribute to it and read it.
In [4]:
print(third_way)
This is avery very longstring

Attaching two strings is easy as sum them up.

In [5]:
greetings = "Hello" + 'Goofy' + "!"
print("Greetings: " + greetings)

firstname = "Felonius"
lastname  = "Gru"
fullname  = firstname + ' ' + lastname
print('Full Name: {}, greeting: {}'.format(fullname, greetings))
Greetings: HelloGoofy!
Full Name: Felonius Gru

Each string have a length that is count of characters in the string.

In [13]:
print(len(greetings))
11
In [14]:
print(len(''))  # Empty String
0

Format

As you have seen, there is a format method for string objects that will try to make a new string. This method try to put arguments passed to it in {}.

This is somehow like printf structure in C/C++.

In [15]:
print('You can put numbers: {},\
      strings: {} in it to print.'\
      .format(125, '#TEST#'))
You can put numbers: 125, strings: #TEST# in it to print.

As we explain later, any object can get passed to a format method and get a string representation.

In [16]:
print('List: {}, Dictionary: {} and Set {}.'\
      .format([110, 'Python'], {'a': 'hi', 'b': 12},\
              {1, 2, 3, 2, 5, 1}))
List: [110, 'Python'], Dictionary: {'a': 'hi', 'b': 12} and Set {1, 2, 3, 5}.
In [6]:
name = 'ali'
print(name[1])
l

Indexed

In [17]:
print('Second arg: {1}, First arg: {0},\
Second arg again: {1}'.format('FIRST', 'SECOND'))
Second arg: SECOND, First arg: FIRST, Second arg again: SECOND

Named

In [18]:
print('First Name: {firstname}, Last Name: {lastname}'\
      .format(firstname='Mike', lastname='Wazowski'))
First Name: Mike, Last Name: Wazowski

Template

You can define a variable and put your template string in it and then get a proper string with format method.

In [19]:
book_template = '''
{title}

Author: {author}

{description}

Book License: {license}
'''

print(book_template.format(author=book_author,
                           title=book_title,
                           description=book_description,
                           license=book_license))
Python for You and Me

Author: Amir Khazaie


This book is about Python programming language.
I started this book as a guideline for instructors
and who wants to learn python not as a first programming language.

Book License: 
This book is under Creative Common license.
so feel free to contribute to it and read it.

Escape Sequences

Python interpret ' as start or end for a strings. What happen if want to use it as a apostrophe? You use an escape sequence. They have a special meaning to Python.

Escape Sequence Meaning
\\ Backslash (\)
\' Single quote (')
\" Double quote (")
\a ASCII Bell (BEL)
\b ASCII Backspace (BS)
\f ASCII Formfeed (FF)
\n ASCII Linefeed (LF)
\r ASCII Carriage Return (CR)
\t ASCII Horizontal Tab (TAB)
\v ASCII Vertical Tab (VT)
In [20]:
print("Baymax: \"On a scale of one to ten, how would you rate your pain?\"\nHiro Hamada: \"What?!\"")
Baymax: "On a scale of one to ten, how would you rate your pain?"
Hiro Hamada: "What?!"
In [7]:
print('Student Results:\n\nName\tMark\nJodi\t80\nFrank\t30\nBob\t67')

# Above and below have the same output.
# In code below we use backsplash to tell python interpreter to ingnore newlines.

print('Student Results:\n\
\n\
Name\tMark\n\
Jodi\t80\n\
Frank\t30\n\
Bob\t67')

print('''Student Results:

Name\tMark
Jodi\t80
''')
Student Results:

Name	Mark
Jodi	80
Frank	30
Bob	67

Input from user

For terminal applications, there is a built-in function called input that you can prompt user to get data.

In [3]:
string = input()  # The output is always a string.
print(string)
user input
user input
In [4]:
name = input("What is your name? ")  # You can ask user for special kind of information.
print("Hello {}.".format(name))
What is your name? Tadashi Hamada
Hello Tadashi Hamada.

Exercise

Ask name and age of user and printing something nice for them.

Indexing

We said strings are created from characters. You can select characters in a string in very customize way.

In [8]:
the_string = 'Hi. This is a simple string that we will use as an example.'

First of all you can access each character. Remember in Python counting starts from 0.

In [25]:
print('1st character is: {}\n5th character is: {}'\
      .format(the_string[0], the_string[4]))
1st character is: H
5th character is: T

You want last character? No problem. Use negative index!

In [26]:
print('Last character is: {}\n5th character from end of the string is: {}'
      .format(the_string[-1], the_string[-5]))
Last character is: .
5th character from end of the string is: m

String indexes are like:

H e l l o W o r l d !
0 1 2 3 4 5 6 7 8 9 10 11
-12 -11 -10 -9 -8 -7 -6 -5 -4 -3 -2 -1

Slicing

It's almost like indexing, but instead of selecting a single character, you can select mutiple characters. There is three things you use in here Start, Stop, Step and sperate them by a colon. In any of them missing, Python use their default value.

Variable Default Value
Start Beginning of the string
Stop End of the string
Step Equals to 1
In [13]:
print(the_string)
Hi. This is a simple string that we will use as an example.
In [9]:
print(the_string[14:20])  # [start stop)
simple
In [28]:
print(the_string[33:])
we will use as an example.
In [29]:
print(the_string[:27])
Hi. This is a simple string
In [30]:
print(the_string[12:30:6])
ali
In [31]:
print(the_string[::-1])
.elpmaxe na sa esu lliw ew taht gnirts elpmis a si sihT .iH

Exercise

Use message below and generate word gun with slice operator.

message = '"Simplicity is about subtracting the obvious and adding the meaningful." - John Maeda'

in Operator

Checks for existence of string in another string.

In [32]:
the_string_contains_hi = 'hi' in the_string
print(the_string_contains_hi)
True
In [6]:
greeting_contains_hi = 'hi' in 'Hi, how are you?'.lower()
print(greeting_contains_hi)
True

None

This is a special object that represent Nothing in Python.

In [34]:
print(None)
None

Boolean

Boolean types are actually intergers 0 and 1. Boolean keywords are True, False.

In [35]:
print("What you see when you print\
true: {} and false: {}".format(True, False))
What you see when you print true: True and false: False

We will see, any object can have a boolean equal. But for now look at code below.

In [36]:
print("Numbers: bool(814): {}, bool(-38): {}, bool(1.5): {}, bool(0): {}"
      .format(bool(814), bool(-38), bool(1.5), bool(0)))
Numbers: bool(814): True, bool(-38): True, bool(1.5): True, bool(0): False
In [37]:
print("Strings: bool('A'): {},\
bool('Python is a wonderful language.'): {},\
bool(''): {}"
      .format(bool('A'),\
              bool('Python is a wonderful language.'),\
              bool('')))
Strings: bool('A'): True, bool('Python is a wonderful language.'): True, bool(''): False

All empty python datasets are evaluated to false.

In [38]:
print("0: {}, '': {}, []: {}, {}: {}, set(): {}"
      .format(bool(0), bool(''), bool([]), '{}', bool({}), bool(set())))
0: False, '': False, []: False, {}: False, set(): False

Lists

A list is very similar to an array. You can put any data in a list and access them with indexing and slicing.

In [10]:
primes = [2, 3, 5, 7, 11, 13, 17, 19, 23]
print("3rd Prime Number: {}".format(primes[2]))
print("Even indexed Prime Numbers: {}".format(primes[::2]))
print("Last Prime Number: {}".format(primes[-1]))
3rd Prime Number: 5
Even indexed Prime Numbers: [2, 5, 11, 17, 23]
Last Prime Number: 23

Lists can have multiple data types at the same time! A list can have integers, floats, string and even other lists!

In [40]:
stuff = ['Apple', 12, [1, 2, 3], {'a': 'Alice', 'b': 'Bob'}]

The len function return length of our list.

In [41]:
print(len(stuff))
4

The difference between normal arrays and Python lists is that Python lists can change!

Add new values with append method and remove value with del keyword.

In [11]:
primes.append(27)
print(primes)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 27]
In [13]:
primes.extend([9, 31, 37])
print(primes)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 27, 9, 31, 37]
In [44]:
del primes[9:11]
print(primes)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 31, 37]
In [45]:
primes.insert(9, 29)
print(primes)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]

With method pop you can get the last element of list and remove it.

In [46]:
print(primes.pop())
37
In [47]:
print(primes)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31]

remove/index methods get an object, search for it in the list and remove it/return index of it.

In [48]:
primes.remove(2)
print(primes)
[3, 5, 7, 11, 13, 17, 19, 23, 29, 31]
In [49]:
print(primes.index(17))
5

Add to lists together and get new one.

In [9]:
positive_nums = [1, 2, 3, 4, 5]
negative_nums = [-1, -2, -3, -4, -5]
integers = negative_nums + [0] + positive_nums
print(integers)
[-1, -2, -3, -4, -5, 0, 1, 2, 3, 4, 5]
In [51]:
sample_list = [1, 2, 'hi', 2, 'hello', 9, 3.14, 2]
print(sample_list.count(2))
3

We have in operators for lists too.

In [10]:
[1, 2, 3] in integers
Out[10]:
False
In [3]:
2 in integers
Out[3]:
True

Packing & Unpacking

These two concepts come with sequences. Unpacking means separate each part of a sequence and packing means gather them together in a place.

In Python we have multiple assignments.

In [54]:
name, age, color = 'Bugs Bunny', 77, 'White'  # Mixed types. No type limitations.
print(name, age, color)
Bugs Bunny 77 White

You can even put multiple objects in a single variable.

In [55]:
character = 'Spongebob Squarepants', 18, 'Yellow'
print(character)  # Character is a Tuple actually.
('Spongebob Squarepants', 18, 'Yellow')

If you have a long list and you wanted to get head and tail of it, you can use this syntax.

In [5]:
students = ['Emma', 'Noah', 'Olivia', 'Liam', 'Sophia', 'Mason', 'Ava', 'Jacob', 'William', 'Isabella']

head, *students, tail = students
print('Head: {}, Students: {}, Tail: {}'.format(head, students, tail))
Head: Emma, Students: ['Noah', 'Olivia', 'Liam', 'Sophia', 'Mason', 'Ava', 'Jacob', 'William'], Tail: Isabella

If you use a star before variable name in a left side of assignment, it means packing variables.

In [6]:
print(students)
['Noah', 'Olivia', 'Liam', 'Sophia', 'Mason', 'Ava', 'Jacob', 'William']
In [7]:
print(*students)  # Is equal to print('Noah', 'Olivia', ...)
Noah Olivia Liam Sophia Mason Ava Jacob William

Packing and unpacking won't happen magically, you have to tell what python should do.

In [8]:
[1, 2, 3] == (1, 2, 3)  # No packing or unpacking. No usage here.
Out[8]:
False

Comprehension

There is a faster and better way to define a list.

In [60]:
squared = [num**2 for num in range(100)]
print(squared)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361, 400, 441, 484, 529, 576, 625, 676, 729, 784, 841, 900, 961, 1024, 1089, 1156, 1225, 1296, 1369, 1444, 1521, 1600, 1681, 1764, 1849, 1936, 2025, 2116, 2209, 2304, 2401, 2500, 2601, 2704, 2809, 2916, 3025, 3136, 3249, 3364, 3481, 3600, 3721, 3844, 3969, 4096, 4225, 4356, 4489, 4624, 4761, 4900, 5041, 5184, 5329, 5476, 5625, 5776, 5929, 6084, 6241, 6400, 6561, 6724, 6889, 7056, 7225, 7396, 7569, 7744, 7921, 8100, 8281, 8464, 8649, 8836, 9025, 9216, 9409, 9604, 9801]

To be or not to be

A little more complex comprehension, let your code decide to add a value or not to add the value there.

In [61]:
squared_cubed = [num for num in squared if round(num ** (1/3)) ** 3 == num]
print(squared_cubed)
[0, 1, 64, 729, 4096]

Exercise

Consider list below:

countries = ['USA', 'Germany', 'France', 'Iran']

Write piece of code that change upper list to:

countries = ['UK', 'USA', 'France', 'Iran', 'Canada']
In [1]:
countries = ['USA', 'Germany', 'France', 'Iran']

countries.insert(0, 'UK')
# countries = ['UK'] + countries

del countries[2]
# countries.remove('Germany')

countries.append('Canada')

print(countries)
['UK', 'USA', 'France', 'Iran', 'Canada']

Tuples

Tuples are frozen Lists. It means when you define a tuple you can't add to it or remove an object from it. Even you can't change a object at specific index! It is freezed for ever.

In [3]:
alvin_marks = (12, 10, 17, 14)

Now is you pass alvin marks to any function or method or ... they can only read it and not changing it!

Tuples are much like lists. You can add two tuples and get new tuple. You can search for an object in it. You can do anything lists can do with tuples as long as what you want to do won't change it.

In [4]:
woody_marks = (19, 18.5, 20, 17)
alvin_woody_marks = alvin_marks + woody_marks
print(alvin_woody_marks)
(12, 10, 17, 14, 19, 18.5, 20, 17)
In [6]:
alvin_got_12 = 12 in alvin_marks
print("Alving got 12? {}".format(alvin_got_12))
Alving got 12? True

If you wanted to define a tupe with only one value in it, you should write it like:

In [4]:
a = (12,)
print(a)
12

Dictionaries

They are like lists with a little differences. One of differences is that programmer defines indexes here! Indexes are called keys. A dictionary is like a rope, you can connect a keys to its value.

In [65]:
per2eng_dict = {
    'سلام': 'Hello',
    'خوبی؟': 'How are you?',
    'پایتون': 'Python',
}

print(per2eng_dict['خوبی؟'])
How are you?

Adding new value to a dictionary is so simple. Choose your key and value.

Using the same syntax for adding, you can update value of keys that exist.

In [66]:
per2eng_dict['خدانگهدار'] = 'Goodbye'
print(per2eng_dict['خدانگهدار'])
Goodbye
In [67]:
per2eng_dict['خوبی؟'] = 'How is it going?'
print(per2eng_dict['خوبی؟'])
How is it going?

You can use any immutable datatype as a key for your dictionary.

In [68]:
per2eng_dict[1396] = 2017
per2eng_dict[('شنبه', 'یک شنبه', 'دو شنبه')] = \
('saturday', 'sunday', 'monday')

print(per2eng_dict)
{'سلام': 'Hello', 1396: 2017, 'خدانگهدار': 'Goodbye', 'پایتون': 'Python', ('شنبه', 'یک شنبه', 'دو شنبه'): ('saturday', 'sunday', 'monday'), 'خوبی؟': 'How is it going?'}

You can get all keys and values of a dictionary.

In [69]:
print(per2eng_dict.keys())
dict_keys(['سلام', 1396, 'خدانگهدار', 'پایتون', ('شنبه', 'یک شنبه', 'دو شنبه'), 'خوبی؟'])
In [70]:
print(per2eng_dict.values())
dict_values(['Hello', 2017, 'Goodbye', 'Python', ('saturday', 'sunday', 'monday'), 'How is it going?'])

Another example for dictionaries is in simple cryptography algorithm called substitution.

In [10]:
 
[(1, 'a'), (2, 'b'), (3, 'c')]
In [1]:
ciphertext = '''QRNE NYVPR,

V NZ N FCL NAQ JBEXVAT SBE HAVGRQ XVATQBZ. NF GUR SVEFG QNL V FGNEG ZL JBEX URER LBH JNF NYJNLF XVAQ GB ZR.
V JVYY ARIRE SBETRG LBHE URYCF.

FVAPRERYL, FNEN.'''

print(list(zip('ABCDEFGHIJKLMNOPQRSTUVWXYZ',
               'NOPQRSTUVWXYZABCDEFGHIJKLM')))

# Generate a dictionary with comprehension
subs = {i: j for i, j in zip('ABCDEFGHIJKLMNOPQRSTUVWXYZ',
                             'NOPQRSTUVWXYZABCDEFGHIJKLM')}

# 
# subs = {'J': 'W', 'Y': 'L', 'I': 'V',
#         'A': 'N', 'B': 'O', 'C': 'P',
#         'D': 'Q', 'W': 'J', 'F': 'S',
#         'U': 'H', 'Q': 'D', 'M': 'Z',
#         'P': 'C', 'E': 'R', 'K': 'X',
#         'Z': 'M', 'S': 'F', 'O': 'B',
#         'R': 'E', 'H': 'U', 'X': 'K',
#         'N': 'A', 'L': 'Y', 'G': 'T',
#         'V': 'I', 'T': 'G'}

# Characters that is not encrypted.
subs.update({'.': '.', ' ': ' ',
             ',': ',', '\n': '\n'})


plaintext = ''.join([subs[char] for char in ciphertext])
print(plaintext)
[('A', 'N'), ('B', 'O'), ('C', 'P'), ('D', 'Q'), ('E', 'R'), ('F', 'S'), ('G', 'T'), ('H', 'U'), ('I', 'V'), ('J', 'W'), ('K', 'X'), ('L', 'Y'), ('M', 'Z'), ('N', 'A'), ('O', 'B'), ('P', 'C'), ('Q', 'D'), ('R', 'E'), ('S', 'F'), ('T', 'G'), ('U', 'H'), ('V', 'I'), ('W', 'J'), ('X', 'K'), ('Y', 'L'), ('Z', 'M')]
DEAR ALICE,

I AM A SPY AND WORKING FOR UNITED KINGDOM. AS THE FIRST DAY I START MY WORK HERE YOU WAS ALWAYS KIND TO ME.
I WILL NEVER FORGET YOUR HELPS.

SINCERELY, SARA.

Comprehensions

There is a better way to define dictionaries.

In [71]:
length_of_names = {name: len(name) for name in students}
print(length_of_names)
{'William': 7, 'Noah': 4, 'Ava': 3, 'Olivia': 6, 'Mason': 5, 'Liam': 4, 'Sophia': 6, 'Jacob': 5}

Just like lists, here we can use if statement too.

In [72]:
long_names = {name: len(name) for name in students if len(name) >= 6}
print(long_names)
{'Olivia': 6, 'Sophia': 6, 'William': 7}

Set

You can put unique object in a set.

In [73]:
numbers = {1, 5, 4, 1, 3, 2, 5, 2, 3, 5, 1}
print(numbers)
{1, 2, 3, 4, 5}
In [74]:
numbers.add(10)
print(numbers)
{1, 2, 3, 4, 5, 10}
In [75]:
numbers.remove(3)
print(numbers)
{1, 2, 4, 5, 10}
In [76]:
even_nums = {2, 4, 6, 8, 10}
print(numbers & even_nums)  # 
{10, 2, 4}
In [77]:
print(numbers | even_nums)
{1, 2, 4, 5, 6, 8, 10}
In [78]:
print(numbers - even_nums)
{1, 5}

Comprehensions

Faster way to define a set.

In [79]:
all_marks = {mark for mark in alvin_woody_marks}
print(all_marks)
{10, 12, 14, 17, 18.5, 19, 20}

Again, you can use an if.

In [80]:
high_marks = {mark*5 for mark in alvin_woody_marks if mark >= 16}  # We change marks scale from 0 -> 20 to 0 -> 100
print(high_marks)
{100, 92.5, 85, 95}

Conversion

There are function with same name as datatypes that try to convert you data from any datatype, to that you want.

In [4]:
print(int(3.14))
print(int('-45'))

print(float(18))
print(float('3.14'))

print(list({1, 2, 3, 1, 4, 1, 2}))
print(set((10, 1, 5, 2, 9, 10, 4, 2, 1, 5)))

set2str = str({1, 2, 3, 1, 4, 1, 2})
print(type([]))
3
-45
18.0
3.14
[1, 2, 3, 4]
{1, 2, 4, 5, 9, 10}
<class 'list'>

Mutable Vs. Immutable

Some of datatypes are mutable and some of them are immutable.

If a datatype is mutable, if you change a copy of mutable datatype, you will change original variable.

In [82]:
shop_list = ['Egg', 'Milk']

new_shop_list = shop_list
new_shop_list.append('Sugar')

print(shop_list)
['Egg', 'Milk', 'Sugar']

But if a datatype is immutable, if you change a copy of immutable datatype, you will not change original variable.

In [12]:
book_title = "Python for You and Me"

new_book_title = book_title
new_book_title = "Python 4 U & Me"

print(book_title)
Python for You and Me

Indentation

Until now we had all of our code running sequentially. Now we need to group them, decide when to run what part of our code, or which part is going to be reused.

To define sub-code, we use indentation. Indentation is a set of whitespaces before the actual code begins.

password = input('Enter your password: ')

if password == 'blah blah blah':
    print('Correct.')

Considering PEP8 it if better to use 4 spaces for each step of indentation.

Decision Making

There will be a time that you need to make a decision about age of a user, or correctness of a password, or information on a website and ... .

Decision making in Python is done with this three statements:

  • if CONDITION:
  • elif CONDITION:
  • else:
In [7]:
secret_knock = input('What is the secret knock? ')

if secret_knock == '1 2 1 3':
    print('Correct, you can get in.')  # This is indented code. This code is sub-code of if statement.
if secret_knock != '1 2 1 3':
    print('Incorrect!')
What is the secret knock? abc
In [85]:
number = int(input('Enter an odd number: '))

if number % 2 == 1:
    print('That is correct.')
else:
    print('This is an even number!')
Enter an odd number: 7
That is correct.
In [22]:
age = int(input('How old are you? '))  # input function return datatype is always a string.

where_to_go = ''
if age < 7:
    where_to_go = 'kindergarten'
elif age < 18:
    where_to_go = 'school'
elif age < 120:
    where_to_go = 'college'
else:
    where_to_go = 'grave'

print('Then probabely you are going to {}.'.format(where_to_go))
How old are you? 5
Then probabely you are going to kindergarten.

Comparison Operators

In above code, if user input is 5, then all of the conditions evaluate to true, but the first one will be used for printing. When you use if, else or if, elif, else there is a priority for conditions. If one of upper conditions comes true, none of conditions below the true one will be check or executed.

Python have six comparison operators:

Operator Meaning
a < b a is smaller than b
a <= b a is smaller than or equal to b
a > b a is bigger than b
a >= b a is bigger than or equal to b
a == b a is equal to b
a != b a is not equal to b

Combining Conditions

You can combine conditions together with and and or operators.

Condition Result
True and True True
True and False False
False and True False
False and False False
In [87]:
ali_age, ali_school, ali_friend = \
17, 'high school', {'mohammad', 'mahdi'}

amir_age, amir_school, amir_friend = \
16, 'high school', {'reza', 'mohammad'}

if ali_school == amir_school and ali_friend & amir_friend:
    print('Ali and Amir are friends.')
Ali and Amir are friends.
Condition Result
True or True True
True or False True
False or True True
False or False False
In [88]:
if ali_school == amir_school or ali_friend & amir_friend:
    print('Ali and Amir are probabely friends.')
Ali and Amir are probabely friends.

Chaining

There is a very useful feature named Chained comparsion. In most programming languages like C, C++, Java, PHP, Drupal, ... this is not possible.

In [ ]:
if 5 >= 2 <= 3:
    print('Correct!')
In [89]:
if -1 <= (ali_age - amir_age) <= 1:
    print('Ali and Amir have same age range.')
Ali and Amir have same age range.

Condition Operator

It like any operators, it have operands and a result.

In [90]:
age = int(input('How old are you? '))

print('You are {} young.'\
      .format('so' if age < 25 else 'not so'))
How old are you? 22
You are so young.

Special Cases

If you were going to check if a variable is None or True or False, considering PEP8, you have to use is operator.

In [91]:
information_about_bermuda_triangle = None

if information_about_bermuda_triangle is None:
    print('We don\'t know anything.')

rainy_day = False

if rainy_day is not False:
    print('Use an umbrella.')
We don't know anything.

There is another operator that you can use in condition.

In [92]:
name = input('What is your name? ')  # Mason

if name in students:
    print('Your name is on the list.')
else:
    print('Your name is not on the list!')
What is your name? Mason
Your name is on the list.

Objects as Condition

Any object is evaluated to True or False. In Python all empty built-in datatypes are evaluated to False and in other situations evaluated to True. As we told, bool function get the object and return evaluated result.

In [13]:
if [False]:
    print('List is true.')
else:
    print('List is false.')
List is true.
In [ ]:
 

Loops

When you need to do repetitive tasks you have to use loops.

Python have for and while loop.

For

For loop is almost used for iterating over a sequence.

In [9]:
shop_list = ['Egg', 'Milk', '...']

for item in shop_list:
    print('We should buy {}.'.format(item))
    print('This is another print function.')
We should buy Egg.
This is another print function.
We should buy Milk.
This is another print function.
We should buy ....
This is another print function.
In [11]:
number = int(input('Which factorial number to calculate? '))

total = 1
for i in range(1, number + 1):
    total *= i

print('Factorial of {} is {}.'.format(number, total))
Which factorial number to calculate? 10
Factorial of 10 is 3628800.

Exercise

  • Get a number from user and print all prime numbers less than that number.
In [3]:
biggest_num = int(input('Enter a number to get all prime numbers less than that: '))

primes = []
for num in range(2, biggest_num):
    is_prime = True
    
    for prime in primes:
        if num % prime == 0:
            is_prime = False
    
    if is_prime:
        primes.append(num)

print(primes)
Enter a number to get all prime numbers less than that: 100
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
In [5]:
total = 1

for prime in primes:
    total *= prime
    
print(total)
2305567963945518424753102147331756070
In [96]:
for key in per2eng_dict:
    print('With key {} we get value {}.'\
          .format(key, per2eng_dict[key]))
With key سلام we get value Hello.
With key 1396 we get value 2017.
With key خدانگهدار we get value Goodbye.
With key پایتون we get value Python.
With key ('شنبه', 'یک شنبه', 'دو شنبه') we get value ('saturday', 'sunday', 'monday').
With key خوبی؟ we get value How is it going?.

While

This loop is used for situation when we don't know exactly when we should stop.

In [14]:
from random import randint

selected_num = randint(1, 1000)

num = 1
while num != selected_num:
    num = int(input('Guess a number: '))
    if num < selected_num:
        print('Smaller!')
    elif num > selected_num:
        print('Bigger!')
    else:
        print('BINGO')
Guess a number: 500
Bigger!
Guess a number: 250
Bigger!
Guess a number: 125
Smaller!
Guess a number: 175
Bigger!
Guess a number: 150
Smaller!
Guess a number: 165
Bigger!
Guess a number: 160
Smaller!
Guess a number: 162
Bigger!
Guess a number: 161
BINGO
In [98]:
password = input('Enter the password: ')
while password != 'wonder woman':
    password = input('Password was incorrect!\nPlease enter correct password: ')
Enter the password: test
Password was incorrect!
Please enter correct password: password
Password was incorrect!
Please enter correct password: wonder woman

Break

Maybe you have complex situation that need to be checked and if they come true, the loop should stop. For this purpose you can use break inside a loop.

In [99]:
print('Enter numbers to get total, finish with 0.')

total = 0
while True:
    num = int(input('Enter your number: '))
    if num == 0:
        break
    total += num

print('Total: {}'.format(total))
Enter numbers to get total, finish with 0.
Enter your number: 4
Enter your number: 5
Enter your number: 1
Enter your number: -10
Enter your number: 0
Total: 0

Continue

If you wanted to skip rest of a loop execution, you can use continue.

In [100]:
print('Enter number to get total, finish with 0. Negative numbers are ignored.')

total = 0
while True:
    num = int(input('Enter your number: '))
    if num < 0:
        continue
    elif num == 0:
        break
    total += num
    
print('Total: {}'.format(total))
Enter number to get total, finish with 0. Negative numbers are ignored.
Enter your number: 4
Enter your number: 5
Enter your number: 1
Enter your number: -10
Enter your number: 0
Total: 10

Comprehensions

To create some of built-in datatypes you need use loops. For example to get students names and put them in a list.

In [101]:
students = []

for i in range(5):
    student_name = input('Enter a name: ')
    students.append(student_name)

print(students)
Enter a name: Alice
Enter a name: Bob
Enter a name: Jack
Enter a name: Sophia
Enter a name: Sara
['Alice', 'Bob', 'Jack', 'Sophia', 'Sara']
In [1]:
students = [input('Enter a name: ') for i in range(5)]  # Shorter and faster way to do it.
print(students)
Enter a name: 1
Enter a name: 2
Enter a name: 3
Enter a name: 4
Enter a name: 5
['1', '2', '3', '4', '5']

Exercise

Get a number from user and create a list with comprehension that contains square of numbers in range [1, user_number).

In [2]:
user_num = int(input('Enter a number: '))

numbers = [num**2 for num in range(1, user_num + 1)]

print(numbers)
Enter a number: 10
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

You can use comprehension with lists, set and dictionary.

In [103]:
num_count = int(input('How many number you have? '))
unique_nums = {int(input('Enter a number: '))\
               for i in range(num_count)}
print(unique_nums)
How many number you have? 5
Enter a number: 1
Enter a number: 2
Enter a number: 1
Enter a number: 2
Enter a number: 3
{1, 2, 3}
In [8]:
num_of_words = int(input('How many words you want in your dictionary? '))
eng2per = {input('Enter english word: '):\
           input('Enter persian translation of your word: ')\
           for i in range(num_of_words)}
print(eng2per)
How many words you want in your dictionary? ۳
Enter persian translation of your word: hi
Enter english word: سلام
Enter persian translation of your word: test
Enter english word: تست
Enter persian translation of your word: test2
Enter english word: تست۲
{'سلام': 'hi', 'تست': 'test', 'تست۲': 'test2'}

Omiting

Maybe you don't want all of the thing you have, get into your list, set or dictionary, well you can use if statement for that in comprehensions.

In [105]:
words = ["Trip", "restaurant", "GOOD", "reviews", "expectations", "hIGh","service", "SLOW", "full"]
lowercase_words = [word for word in words if word.islower()]
print(lowercase_words)
['restaurant', 'reviews', 'expectations', 'service', 'full']

Nesting

You can nest loops for complex results like multiplication table.

In [3]:
from pprint import pprint

size = int(input('Enter size of multiplication table: '))
mul_tab = []
for row in range(1, size + 1):
    mul_tab.append([])
    for column in range(1, size + 1):
        mul_tab[-1].append(row * column)
        pprint(mul_tab)
        print()
Enter size of multiplication table: 5
[[1]]

[[1, 2]]

[[1, 2, 3]]

[[1, 2, 3, 4]]

[[1, 2, 3, 4, 5]]

[[1, 2, 3, 4, 5], [2]]

[[1, 2, 3, 4, 5], [2, 4]]

[[1, 2, 3, 4, 5], [2, 4, 6]]

[[1, 2, 3, 4, 5], [2, 4, 6, 8]]

[[1, 2, 3, 4, 5], [2, 4, 6, 8, 10]]

[[1, 2, 3, 4, 5], [2, 4, 6, 8, 10], [3]]

[[1, 2, 3, 4, 5], [2, 4, 6, 8, 10], [3, 6]]

[[1, 2, 3, 4, 5], [2, 4, 6, 8, 10], [3, 6, 9]]

[[1, 2, 3, 4, 5], [2, 4, 6, 8, 10], [3, 6, 9, 12]]

[[1, 2, 3, 4, 5], [2, 4, 6, 8, 10], [3, 6, 9, 12, 15]]

[[1, 2, 3, 4, 5], [2, 4, 6, 8, 10], [3, 6, 9, 12, 15], [4]]

[[1, 2, 3, 4, 5], [2, 4, 6, 8, 10], [3, 6, 9, 12, 15], [4, 8]]

[[1, 2, 3, 4, 5], [2, 4, 6, 8, 10], [3, 6, 9, 12, 15], [4, 8, 12]]

[[1, 2, 3, 4, 5], [2, 4, 6, 8, 10], [3, 6, 9, 12, 15], [4, 8, 12, 16]]

[[1, 2, 3, 4, 5], [2, 4, 6, 8, 10], [3, 6, 9, 12, 15], [4, 8, 12, 16, 20]]

[[1, 2, 3, 4, 5], [2, 4, 6, 8, 10], [3, 6, 9, 12, 15], [4, 8, 12, 16, 20], [5]]

[[1, 2, 3, 4, 5],
 [2, 4, 6, 8, 10],
 [3, 6, 9, 12, 15],
 [4, 8, 12, 16, 20],
 [5, 10]]

[[1, 2, 3, 4, 5],
 [2, 4, 6, 8, 10],
 [3, 6, 9, 12, 15],
 [4, 8, 12, 16, 20],
 [5, 10, 15]]

[[1, 2, 3, 4, 5],
 [2, 4, 6, 8, 10],
 [3, 6, 9, 12, 15],
 [4, 8, 12, 16, 20],
 [5, 10, 15, 20]]

[[1, 2, 3, 4, 5],
 [2, 4, 6, 8, 10],
 [3, 6, 9, 12, 15],
 [4, 8, 12, 16, 20],
 [5, 10, 15, 20, 25]]

You can nest loops with comprehension too!

In [5]:
from pprint import pprint  # Better way

size = int(input('Enter size of multiplication table: '))
mul_tab = [[row*col for col in range(1, size + 1)] for row in range(1, size + 1)]

pprint(mul_tab)
Enter size of multiplication table: 5
[[1, 2, 3, 4, 5],
 [2, 4, 6, 8, 10],
 [3, 6, 9, 12, 15],
 [4, 8, 12, 16, 20],
 [5, 10, 15, 20, 25]]

Another way of nesting

In [4]:
mul_tab_nums = {num for row in mul_tab for num in row}
print(mul_tab_nums)
{1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, 16, 20, 25}

Exercises

  • Write a program and generate fibonacci numbers.
    • 1 1 2 3 5 8 13 21 34 55 ...
  • Find greatest common divisor of two positive numbers.
    • GCD of A and B == GCD of B and A%B (A>=B)
  • Generate a triangle with comprehensions.
*
**
***
****
*****
******
In [7]:
fibo_num = [1, 1]

fibo_count = int(input('Enter fibo count: '))

# for i in range(2, fibo_count):
#     fibo_num.append(fibo_num[-1] + fibo_num[-2])

nothing = [fibo_num.append(fibo_num[-1] + fibo_num[-2])
 for i in range(2, fibo_count)]

print(nothing)


print(fibo_num)
Enter fibo count: 10
[None, None, None, None, None, None, None, None]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
In [13]:
num1, num2 = int(input('Enter first number: ')), int(input('Enter second number: '))

while num1 % num2 != 0:
    num1, num2 = num2, num1 % num2

print(num2)
Enter first number: 23
Enter second number: 17
1
In [8]:
names = ['Jodi', 'Alice', 'Bob']
print(''.join(names))
JodiAliceBob
In [16]:
size = int(input('Size of triangle: '))

for row in range(1, size + 1): # [start, stop)
    stars = []
    for i in range(row):
        stars.append('*')
#     print(stars)
    print(''.join(stars))
#     print('*' * row)

# print('\n'.join(['*' * row 
#                  for row in range(1, size + 1)]))
Size of triangle: 5
*
**
***
****
*****

Functions

Some parts of code is going to be reused again and again and again, we can't write them again and again and again! Remember don't repeat yourself (DRY). Function are reusable block of code that make coding easy. We can split tasks to function and use them any where. Functions improve readability and hide complexity.

Generally in Python function take zero, one or more input (called arguments) and return exactly one output (called returned value).

In [21]:
def greeting(name):
    print('Hello {}'.format(name))

greeting('Alice')
print(greeting('Jack'))
Hello Alice
Hello Jack
None

Each function should have a very specific job and should not alter anything that is not necessary. So we can rewrite above code.

In [109]:
def greeting(name):
    return 'Hello {}'.format(name)

print(greeting('Alice'))
print(greeting('Bob'))
Hello Alice
Hello Bob

Arguments

A function can take multiple arguments.

In [110]:
from pprint import pprint

def multiplication_table(row, column):
    return [[i*j for j in range(1, column + 1)] for i in range(1, row + 1)]

pprint(multiplication_table(4, 8))
[[1, 2, 3, 4, 5, 6, 7, 8],
 [2, 4, 6, 8, 10, 12, 14, 16],
 [3, 6, 9, 12, 15, 18, 21, 24],
 [4, 8, 12, 16, 20, 24, 28, 32]]

Pass by Value or Refrence

Mutable and immutable values behave different when passed to a function. Mutable variables is pass by refrence but immutable variables is pass by value.

In [5]:
def add_one(a_list):  # Mutable
    a_list.append(1)

num_list = [3, 2, 9]
add_one(num_list)
print(num_list)

def add_one(a_string):  # Immutable
    a_string += 'ONE'
    print('a_string is "{}"'.format(a_string))

numbers = 'TWO, '
add_one(numbers)
print(numbers)
[3, 2, 9, 1]
a_string is "TWO, ONE"
TWO, 

Labeled Arguments

You can explicitly pass an argument by using name of that argument.

In [112]:
pprint(multiplication_table(column=10, row=3))
[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
 [2, 4, 6, 8, 10, 12, 14, 16, 18, 20],
 [3, 6, 9, 12, 15, 18, 21, 24, 27, 30]]

Default Values

Function arguments can have default values. Remember all arguments with default values should be last arguments.

In [113]:
def multiplication_table(row=10, column=10):
    return [[i*j for j in range(1, column + 1)] for i in range(1, row + 1)]

pprint(multiplication_table())  # Use default values
[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
 [2, 4, 6, 8, 10, 12, 14, 16, 18, 20],
 [3, 6, 9, 12, 15, 18, 21, 24, 27, 30],
 [4, 8, 12, 16, 20, 24, 28, 32, 36, 40],
 [5, 10, 15, 20, 25, 30, 35, 40, 45, 50],
 [6, 12, 18, 24, 30, 36, 42, 48, 54, 60],
 [7, 14, 21, 28, 35, 42, 49, 56, 63, 70],
 [8, 16, 24, 32, 40, 48, 56, 64, 72, 80],
 [9, 18, 27, 36, 45, 54, 63, 72, 81, 90],
 [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]]
In [114]:
pprint(multiplication_table(5, 5))  # Don't use default values
[[1, 2, 3, 4, 5],
 [2, 4, 6, 8, 10],
 [3, 6, 9, 12, 15],
 [4, 8, 12, 16, 20],
 [5, 10, 15, 20, 25]]
In [115]:
pprint(multiplication_table(3))  # Pass first argument and use second default value
[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
 [2, 4, 6, 8, 10, 12, 14, 16, 18, 20],
 [3, 6, 9, 12, 15, 18, 21, 24, 27, 30]]
In [116]:
pprint(multiplication_table(column=3))  # Even pass second value and use first default value
[[1, 2, 3],
 [2, 4, 6],
 [3, 6, 9],
 [4, 8, 12],
 [5, 10, 15],
 [6, 12, 18],
 [7, 14, 21],
 [8, 16, 24],
 [9, 18, 27],
 [10, 20, 30]]

List Arguments

Function can get arguments of any count.

In [117]:
def total_of(*numbers):
    total = 0
    for number in numbers:
        total += number
    return total

print(total_of(1, 2, 3))
6
In [118]:
print(total_of(9018, 8712, 2376, 19, 268, 683, 1912875))
1933951
In [119]:
print(total_of())
0

Keyword Arguments

Function arguments even can be labeled.

In [120]:
def keywords(**kwargs):
    return(kwargs)

print(keywords(hi='سلام', hello='سلام', goodbye='خداحافظ'))
{'hi': 'سلام', 'hello': 'سلام', 'goodbye': 'خداحافظ'}

Function arguments have order to use, positional args, list args, keyword args.

In [17]:
def function(first, second, third=3, fourth='four', *fifth, **sixth):
    print('First:  {}'.format(first))
    print('Second: {}'.format(second))
    print('Third:  {}'.format(third))
    print('Fourth: {}'.format(fourth))
    print('Fifth:  {}'.format(fifth))
    print('Sixth:  {}'.format(sixth))

function(1, 2, 3, 4, 5, 6, test=123)
First:  1
Second: 2
Third:  3
Fourth: 4
Fifth:  (5, 6)
Sixth:  {'test': 123}
In [122]:
function(1, 2)
First:  1
Second: 2
Third:  3
Fourth: four
Fifth:  ()
Sixth:  {}
In [123]:
function(1, 2, test='keyword')
First:  1
Second: 2
Third:  3
Fourth: four
Fifth:  ()
Sixth:  {'test': 'keyword'}

Return

Each function always return exactly one data even when you don't return anything!

In [124]:
def no_return():
    pass

result = no_return()
print(result)
None

With Python function you can simply return multiple values. They will be pack as a single tuple and returned.

In [125]:
def multiple_value_returned():
    return 'Lightning McQueen', 11, ['Mater', 'Chick Hicks', 'Sally Carrera', 'Doc Hudson']

values = multiple_value_returned()               # Single variable to store.
name, age, partners = multiple_value_returned()  # Multiple variables to store.

print("Values: {}".format(values))
print("Name: {}, Age: {}, Partners: {}".format(name, age, partners))
Values: ('Lightning McQueen', 11, ['Mater', 'Chick Hicks', 'Sally Carrera', 'Doc Hudson'])
Name: Lightning McQueen, Age: 11, Partners: ['Mater', 'Chick Hicks', 'Sally Carrera', 'Doc Hudson']

Scope

Each function have its one scope of variables.

In [3]:
name = 'Tonto'

if name == 'Tonto':
    age = 12

print('Age: {}'.format(age))

while age == 12:
    age += 1
    shopping_list = ['Milk', 'Egg']

print('Shopping List: {}'.format(shopping_list))

def func():
    print('Func -> Age: {}'.format(age))
#     print('Func -> Shopping List: {}'.format(shop_list))
    var_in_func = 'Function'

func()
print(var_in_func)
Age: 12
Shopping List: ['Milk', 'Egg']
Func -> Age: 13
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-3-defa0c0d63ec> in <module>()
     18 
     19 func()
---> 20 print(var_in_func)

NameError: name 'var_in_func' is not defined

Global and Nonlocal

Documentation

Packages, modules, classes and functions all have docstrings. For complete information read docstring conventions, PEP257.

A triple double quoted string at first line of the function is a docstring.

In [127]:
def multiplication_table(row=10, column=10):
    """Get number of rows and columns of multiplication table then generate and returns a two dimensions list."""
    return [[i*j for j in range(1, column + 1)] for i in range(1, row + 1)]

help(multiplication_table)
Help on function multiplication_table in module __main__:

multiplication_table(row=10, column=10)
    Get number of rows and columns of multiplication table then generate and returns a two dimensions list.

lambda

We can define nameless functions.

In [3]:
square = lambda x: x**2
print(square(10))
100

Everything about function arguments works with lambda functions too.

In [129]:
print((lambda a, b=3, **kwargs: (a, b, kwargs))('Hello', keyword='argument'))
('Hello', 3, {'keyword': 'argument'})

Recursive Functions

If you call a function inside itself, it is called recursive function. Recursive function are useful for solving some complex algorithmic problems.

In [130]:
def factorial(n):
    if n == 0:
        return 1
    return factorial(n - 1) * n

print(factorial(10))
3628800
In [131]:
def move_disks3(n, source, intermediate, destination):
    """Print movee needed to move disks in source tower to destination tower.
    
    @param int n: Number of disk in source tower.
    @param int source: Name of the source tower.
    @param int intermediate: Name of intermediate tower.
    @param int destination: Name of destination tower.
    @rtype: None
    """
    if n > 1:
        move_disks3(n - 1, source, destination, intermediate)
        move_disks3(1, source, intermediate, destination)
        move_disks3(n - 1, intermediate, source, destination)
    else:
        print("{} -> {}".format(source,destination))

move_disks3(3, 'First', 'Second', 'Third')
First -> Third
First -> Second
Third -> Second
First -> Third
Second -> First
Second -> Third
First -> Third

Exercise

  • GCD recursive function.
  • All prime number between two intervals.
In [7]:
def gcd(num1, num2):
    """This function calculate GCD of two numbers with recursive function."""
    return gcd(num2, num1%num2) if num2 else num1

print(gcd(45, 33))
3
In [4]:
def prime_range(start, stop):
    primes = []
    
    for number in range(start, stop):
        is_prime = True
        
        for i in range(2, number):
            if number % i == 0:
                is_prime = False
                break
        
        if is_prime:
            primes.append(number)

    return primes

for prime in prime_range(1000, 1050):
    print('{} is a prime number.'.format(prime))
1009 is a prime number.
1013 is a prime number.
1019 is a prime number.
1021 is a prime number.
1031 is a prime number.
1033 is a prime number.
1039 is a prime number.
1049 is a prime number.
In [ ]:
# (stop)
# (start, stop)
# (start, stop, step)

def prime_range(start=None, stop=None, step=None):
    """A function with parameter that is completely like
    range function but instead of generate natural numbers
    here the function generates prime numbers."""
    
    # Evaluate start, stop and step by what user of this
    # function code want it to do.
    if start is None:
        print('WRONG')
        return
    elif stop is None:
        start, stop, step = 2, start, 1
    elif step is None:
        step = 1
    
    # A list to store all primes that I found.
    primes = []
    
    # Generate all primes from start to stop.
    for number in range(start, stop):
        is_prime = True
        
        for i in range(2, number):
            if number % i == 0:
                is_prime = False
                break
        
        if is_prime:
            primes.append(number)

    # Slice primes with step that user wants and return
    # it.
    return primes[::step]

for prime in prime_range(1000, 1050):
    print('{} is a prime number.'.format(prime))
In [8]:
print(prime_range(10))
1009 is a prime number.
1013 is a prime number.
1019 is a prime number.
1021 is a prime number.
1031 is a prime number.
1033 is a prime number.
1039 is a prime number.
1049 is a prime number.
[2, 3, 5, 7]
In [10]:
print(prime_range(5, 15))
[5, 7, 11, 13]
In [11]:
print(prime_range(10, 100, 5))
[11, 29, 47, 71, 97]
In [ ]:
def prime_range(*args):
    """A function with parameter that is completely like
    range function but instead of generate natural numbers
    here the function generates prime numbers."""
    
    # Evaluate start, stop and step by what user of this
    # function code want it to do.
    if len(args) == 1:
        start, stop, step = 2, args[0], 1
    elif len(args) == 2:
        start, stop, step = args[0], args[1], 1
    elif len(args) == 3:
        start, stop, step = args
    else:
        print('WRONG')
        return
    
    # A list to store all primes that I found.
    primes = []
    
    # Generate all primes from start to stop.
    for number in range(start, stop):
        is_prime = True
        
        for i in range(2, number):
            if number % i == 0:
                is_prime = False
                break
        
        if is_prime:
            primes.append(number)

    # Slice primes with step that user wants and return
    # it.
    return primes[::step]

Built-in Functions

Python have built-in functions to help easy coding.

help

Print a documentation on object passed to it.

In [8]:
help(help)
Help on _Helper in module _sitebuiltins object:

class _Helper(builtins.object)
 |  Define the builtin 'help'.
 |  
 |  This is a wrapper around pydoc.help that provides a helpful message
 |  when 'help' is typed at the Python interactive prompt.
 |  
 |  Calling help() at the Python prompt starts an interactive help session.
 |  Calling help(thing) prints help for the python object 'thing'.
 |  
 |  Methods defined here:
 |  
 |  __call__(self, *args, **kwds)
 |      Call self as a function.
 |  
 |  __repr__(self)
 |      Return repr(self).
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

print

We work with this one so many times. Print function have 3 labeled arguments:

  • end: Specify how to end a printed text.
  • sep: Specify separation of each argument passed to print function.
  • file: Specify a file to print text into that file.
In [132]:
print(1, 2, 3)
1 2 3
In [13]:
print(1, 2, 3, sep='\n')
1
2
3
In [14]:
print(1, 2, 3, sep=', ', end='.')
print('Hello')
1, 2, 3.Hello

len

Get length of an object. You can define length of each object you define. Sequences and collections have length.

bool

Return evaluted boolean value of an object. You can define boolean value of objects you define.

str

Convert object passed to it, to string representaion.

int

Convert object passed to it, to integer values.

set

Convert a sequence passed to it, to a set.

list

Convert a sequence passed to it, to a list.

dict

Defining a dictionary with keyword argument of a function.

In [135]:
marks = dict(Joe='A', Bob='C', Alice='B+')
print(marks)
{'Bob': 'C', 'Joe': 'A', 'Alice': 'B+'}

tuple

Convert a sequence passed to it, to a tuple.

range

Get three arguments, start, stop and step and return an iterable object that count from start (including) to stop (excluding) with specified steps.

In [16]:
print(list(range(10)))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
In [137]:
print(list(range(1, 10)))
[1, 2, 3, 4, 5, 6, 7, 8, 9]
In [138]:
print(list(range(1, 10, 3)))
[1, 4, 7]

zip

Get any number of sequences and work exactly like a zip!

In [139]:
print(list(zip([1, 2, 3], ('one', 'two','three'))))
[(1, 'one'), (2, 'two'), (3, 'three')]
In [140]:
print(list(zip(range(1, 5), {'this', 'is', 'a', 'set'})))
[(1, 'a'), (2, 'is'), (3, 'set'), (4, 'this')]
In [141]:
 print(list(zip([1, 2, 3], ['1', '2', '3'], ['one', 'two', 'three'])))
[(1, '1', 'one'), (2, '2', 'two'), (3, '3', 'three')]

map

You should give one function with N arguments, as the first argument of map function and then give it N sequences with same length. It will calculate function with those and save the output as a new sequence.

In [2]:
print(list(map(lambda x, y: x**y, [1, 2, 3], [10, 20, 30])))
[1, 1048576, 205891132094649]

filter

As the name says, it is used to filter some of object out. The first parameter would be a function that if returns True the object stays, it returns false the object is filtered.

In [17]:
numbers = list(range(1, 20))
print(numbers)

print(list(filter(lambda x: x%2, numbers)))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

min/max

Get a sequence and compare each of objects inside the sequence with each other and return minimum/maximum value.

In [4]:
greeting ='Hello World!'
print('Min: "{}", Max: "{}"'.format(min(greeting), max(greeting)))
Min: " ", Max: "r"
In [5]:
numbers = [12, 6, 47, -52, 158]
print('Min: {}, Max: {}'.format(min(numbers), max(numbers)))
Min: -52, Max: 158
In [6]:
numbers = {12, 6, 47, -52, 158}
print('Min: {}, Max: {}'.format(min(numbers), max(numbers)))
Min: -52, Max: 158
In [4]:
marks = {'John': 18, 'Sophia': 12, 'Sara': 16.7}
print('Min: {}, Max: {}'.format(min(marks), max(marks)))
Min: John, Max: Sophia

Useful Tips

  • Functions should do one thing.
  • Functions should be small.
  • The fewer input parameters, the better.
  • Functions should be consistent in their return values. Functions shouldn't have side effects.

Exercise

  • Create a function that take a number of digit for precision and calculate $\pi$ with formula below.
$$\frac{\pi}{2} = 1 + \frac{1}{3} + \frac{1 \times 2}{3 \times 5} + \frac{1 \times 2 \times 3}{3 \times 5 \times 7} + \dots$$

Default precision should be 5 digits. You have to write proper documentation for your function.

  • Print factors of a number (You will need a prime number generator function).
$$ 12 = 2^2 \times 3^1 $$
[(2, 2), (3, 1)]
In [7]:
def fraction(frac_num):
    result = 1.0
    
    for i in range(1, frac_num + 1):
        result *= i/(2*i+1)
    
    return result

    
def pi(precision=5):
    total = 0.0
    
    i = 0
    while fraction(i) > 10**-precision:
        total += fraction(i)
        i += 1
    
    return total * 2

print(pi(8))
3.1415926331440347
In [14]:
def factors(number):
    result = []
        
    for prime in prime_range(number + 1):
        # Find the right superscript for specific prime number.
        superscript = 0
        while number % prime == 0:
            number /= prime
            superscript += 1
        # If superscript is bigger that zero, we add it to result
        if superscript:
            result.append((prime, superscript))
    
    return result

print(factors(2017))
[(2017, 1)]
In [24]:
print(factors(21000))
[(2, 3), (3, 1), (5, 3), (7, 1)]

Files

All data we have is store in files. Even this document you are reading is store in a file. Python have very good API and very very libraries to work with files.

In [2]:
# Simple reading from file.

hello = open('hello')
print(hello.read())
print(hello.read())
hello.close()
First file operation.


The open function first argument is a path to file, second argument is to specify which mode to open the file.

Modes to open a file is:

Character Meaning
r open for reading (default)
w open for writing, truncating the file first
x open for exclusive creation, failing if the file already exists
a open for writing, appending to the end of the file if it exists
b binary mode
t text mode (default)
+ open a disk file for updating (reading and writing)
U universal newlines mode (deprecated)
In [12]:
def rwfile_test():
    rwfile = open('rwfile', 'a+')
    rwfile.seek(0, 0)
    data = rwfile.read()
    line_count = data.count('\n')
    print('Line Number {}.'.format(line_count + 1), file=rwfile)
    rwfile.close()

open('rwfile', 'w').close()  # Clear file.
for i in range(5):
    rwfile_test()

print(open('rwfile').read())
Line Number 1.
Line Number 2.
Line Number 3.
Line Number 4.
Line Number 5.

Save Objects

You can even save your objects to a file. pickle module can do that for you.

In [13]:
# Sample save and load binary object to a file in binary mode.

from pickle import dump, load

shopping_list = ['Milk', 'Egg', 'Bread', 'Sugar']

shopping_list_file = open('shop-list-file', 'wb')
dump(shopping_list, shopping_list_file)
shopping_list_file.close()

shopping_list_file = open('shop-list-file', 'rb')
loaded_shopping_list = load(shopping_list_file)
print(loaded_shopping_list[0])
Milk

Easier File Handeling

For each file you should open it to use and then you have to close it. Without closing file, changes that you made to file will be ignored!

It is easier to use with statement instead of opening and closing a file yourself.

In [31]:
with open('rwfile') as rwfile:
    print(rwfile.read())
Line Number 1.
Line Number 2.
Line Number 3.
Line Number 4.
Line Number 5.

Exercise

  • Write program that if a file exists, read it and print the list in the file. Then get list of names from user and add names to the file.
  • Write a function that get a filename and return dictionary that keys are words in a file and values are number of occurences of that word.
  • Write a function that get a word and a filename and return number of occurences of the word in the filename.
In [29]:
# Phase 1: Read information and write it to user.
from pathlib import Path
from pickle import load, dump

data = Path("data")
if data.exists():
    with open('data', 'rb') as data_file:
        print(load(data_file))

# Phase 2: Get information from user and write
# them to file.

num_of_names = int(input('Enter number of names: '))
user_data = [input('Name: ') for i in range(num_of_names)]

with open('data', 'wb') as data_file:
    dump(user_data, data_file)
['Alice', 'Bob', 'Sara']
Enter number of names: 3
Name: 1
Name: 2
Name: 3
In [ ]:
# 1- Open file
# 2- Read all of file
# 3- Convert all file information to a words
# 4- Count each word and create a dictionary
# 5- Return it!
In [27]:
w = "a b c d"

print(w.split())
['a', 'b', 'c', 'd']
In [15]:
def frequency_of_words(filename):
    with open(filename) as f:
        words = f.read().split()
        result = {word:words.count(word) for word in set(words) if word.isalpha()}
    return result

print(frequency_of_words('todo.md'))
{'and': 2, 'like': 1, 'Programming': 1, 'comment': 3, 'Data': 1, 'names': 1, 'Oriented': 1, 'a': 2, 'Functions': 1, 'change': 1, 'book': 1, 'Search': 1, 'Write': 1, 'Sequences': 1, 'exercises': 1, 'line': 1, 'Types': 1, 'scientific': 1, 'loop': 1, 'for': 3, 'code': 1, 'proper': 1, 'Introduction': 1, 'Gather': 1, 'camel': 1, 'Separate': 1, 'Decision': 1, 'Making': 1, 'Add': 4, 'under': 1, 'lost': 1, 'front': 1, 'that': 1, 'should': 1, 'after': 1, 'each': 2, 'section': 3, 'of': 3, 'comprehension': 1, 'input': 1, 'Loops': 1, 'Move': 1, 'strings': 1, 'in': 1, 'with': 2, 'some': 1, 'see': 1, 'Object': 1, 'to': 2}
In [27]:
def frequency_of_word(word, fname):
    return frequency_of_words(filename=fname)[word]

print(frequency_of_word('section', 'todo.md'))
3

Modules

When your project is getting bigger you have to organize your codes into different files and even different folders. To create new modules just create a file with anyname you want and then import it into your own code when you need it.

In [1]:
import datetime

print(datetime.__file__)
/usr/lib/python3.5/datetime.py
In [4]:
print(datetime.__name__)
datetime
In [33]:
from math import pi

print(pi)
3.141592653589793
In [16]:
import datetime as dt
print(dt.datetime.now())
2017-08-19 10:36:33.719867
In [7]:
help(datetime)
Help on module datetime:

NAME
    datetime - Fast implementation of the datetime type.

MODULE REFERENCE
    https://docs.python.org/3.5/library/datetime.html
    
    The following documentation is automatically generated from the Python
    source files.  It may be incomplete, incorrect or include features that
    are considered implementation detail and may vary between Python
    implementations.  When in doubt, consult the module reference at the
    location listed above.

CLASSES
    builtins.object
        date
            datetime
        time
        timedelta
        tzinfo
            timezone
    
    class date(builtins.object)
     |  date(year, month, day) --> date object
     |  
     |  Methods defined here:
     |  
     |  __add__(self, value, /)
     |      Return self+value.
     |  
     |  __eq__(self, value, /)
     |      Return self==value.
     |  
     |  __format__(...)
     |      Formats self with strftime.
     |  
     |  __ge__(self, value, /)
     |      Return self>=value.
     |  
     |  __getattribute__(self, name, /)
     |      Return getattr(self, name).
     |  
     |  __gt__(self, value, /)
     |      Return self>value.
     |  
     |  __hash__(self, /)
     |      Return hash(self).
     |  
     |  __le__(self, value, /)
     |      Return self<=value.
     |  
     |  __lt__(self, value, /)
     |      Return self<value.
     |  
     |  __ne__(self, value, /)
     |      Return self!=value.
     |  
     |  __new__(*args, **kwargs) from builtins.type
     |      Create and return a new object.  See help(type) for accurate signature.
     |  
     |  __radd__(self, value, /)
     |      Return value+self.
     |  
     |  __reduce__(...)
     |      __reduce__() -> (cls, state)
     |  
     |  __repr__(self, /)
     |      Return repr(self).
     |  
     |  __rsub__(self, value, /)
     |      Return value-self.
     |  
     |  __str__(self, /)
     |      Return str(self).
     |  
     |  __sub__(self, value, /)
     |      Return self-value.
     |  
     |  ctime(...)
     |      Return ctime() style string.
     |  
     |  fromordinal(...) from builtins.type
     |      int -> date corresponding to a proleptic Gregorian ordinal.
     |  
     |  fromtimestamp(...) from builtins.type
     |      timestamp -> local date from a POSIX timestamp (like time.time()).
     |  
     |  isocalendar(...)
     |      Return a 3-tuple containing ISO year, week number, and weekday.
     |  
     |  isoformat(...)
     |      Return string in ISO 8601 format, YYYY-MM-DD.
     |  
     |  isoweekday(...)
     |      Return the day of the week represented by the date.
     |      Monday == 1 ... Sunday == 7
     |  
     |  replace(...)
     |      Return date with new specified fields.
     |  
     |  strftime(...)
     |      format -> strftime() style string.
     |  
     |  timetuple(...)
     |      Return time tuple, compatible with time.localtime().
     |  
     |  today(...) from builtins.type
     |      Current date or datetime:  same as self.__class__.fromtimestamp(time.time()).
     |  
     |  toordinal(...)
     |      Return proleptic Gregorian ordinal.  January 1 of year 1 is day 1.
     |  
     |  weekday(...)
     |      Return the day of the week represented by the date.
     |      Monday == 0 ... Sunday == 6
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors defined here:
     |  
     |  day
     |  
     |  month
     |  
     |  year
     |  
     |  ----------------------------------------------------------------------
     |  Data and other attributes defined here:
     |  
     |  max = datetime.date(9999, 12, 31)
     |  
     |  min = datetime.date(1, 1, 1)
     |  
     |  resolution = datetime.timedelta(1)
    
    class datetime(date)
     |  datetime(year, month, day[, hour[, minute[, second[, microsecond[,tzinfo]]]]])
     |  
     |  The year, month and day arguments are required. tzinfo may be None, or an
     |  instance of a tzinfo subclass. The remaining arguments may be ints.
     |  
     |  Method resolution order:
     |      datetime
     |      date
     |      builtins.object
     |  
     |  Methods defined here:
     |  
     |  __add__(self, value, /)
     |      Return self+value.
     |  
     |  __eq__(self, value, /)
     |      Return self==value.
     |  
     |  __ge__(self, value, /)
     |      Return self>=value.
     |  
     |  __getattribute__(self, name, /)
     |      Return getattr(self, name).
     |  
     |  __gt__(self, value, /)
     |      Return self>value.
     |  
     |  __hash__(self, /)
     |      Return hash(self).
     |  
     |  __le__(self, value, /)
     |      Return self<=value.
     |  
     |  __lt__(self, value, /)
     |      Return self<value.
     |  
     |  __ne__(self, value, /)
     |      Return self!=value.
     |  
     |  __new__(*args, **kwargs) from builtins.type
     |      Create and return a new object.  See help(type) for accurate signature.
     |  
     |  __radd__(self, value, /)
     |      Return value+self.
     |  
     |  __reduce__(...)
     |      __reduce__() -> (cls, state)
     |  
     |  __repr__(self, /)
     |      Return repr(self).
     |  
     |  __rsub__(self, value, /)
     |      Return value-self.
     |  
     |  __str__(self, /)
     |      Return str(self).
     |  
     |  __sub__(self, value, /)
     |      Return self-value.
     |  
     |  astimezone(...)
     |      tz -> convert to local time in new timezone tz
     |  
     |  combine(...) from builtins.type
     |      date, time -> datetime with same date and time fields
     |  
     |  ctime(...)
     |      Return ctime() style string.
     |  
     |  date(...)
     |      Return date object with same year, month and day.
     |  
     |  dst(...)
     |      Return self.tzinfo.dst(self).
     |  
     |  fromtimestamp(...) from builtins.type
     |      timestamp[, tz] -> tz's local time from POSIX timestamp.
     |  
     |  isoformat(...)
     |      [sep] -> string in ISO 8601 format, YYYY-MM-DDTHH:MM:SS[.mmmmmm][+HH:MM].
     |      
     |      sep is used to separate the year from the time, and defaults to 'T'.
     |  
     |  now(tz=None) from builtins.type
     |      Returns new datetime object representing current time local to tz.
     |      
     |        tz
     |          Timezone object.
     |      
     |      If no tz is specified, uses local timezone.
     |  
     |  replace(...)
     |      Return datetime with new specified fields.
     |  
     |  strptime(...) from builtins.type
     |      string, format -> new datetime parsed from a string (like time.strptime()).
     |  
     |  time(...)
     |      Return time object with same time but with tzinfo=None.
     |  
     |  timestamp(...)
     |      Return POSIX timestamp as float.
     |  
     |  timetuple(...)
     |      Return time tuple, compatible with time.localtime().
     |  
     |  timetz(...)
     |      Return time object with same time and tzinfo.
     |  
     |  tzname(...)
     |      Return self.tzinfo.tzname(self).
     |  
     |  utcfromtimestamp(...) from builtins.type
     |      Construct a naive UTC datetime from a POSIX timestamp.
     |  
     |  utcnow(...) from builtins.type
     |      Return a new datetime representing UTC day and time.
     |  
     |  utcoffset(...)
     |      Return self.tzinfo.utcoffset(self).
     |  
     |  utctimetuple(...)
     |      Return UTC time tuple, compatible with time.localtime().
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors defined here:
     |  
     |  hour
     |  
     |  microsecond
     |  
     |  minute
     |  
     |  second
     |  
     |  tzinfo
     |  
     |  ----------------------------------------------------------------------
     |  Data and other attributes defined here:
     |  
     |  max = datetime.datetime(9999, 12, 31, 23, 59, 59, 999999)
     |  
     |  min = datetime.datetime(1, 1, 1, 0, 0)
     |  
     |  resolution = datetime.timedelta(0, 0, 1)
     |  
     |  ----------------------------------------------------------------------
     |  Methods inherited from date:
     |  
     |  __format__(...)
     |      Formats self with strftime.
     |  
     |  fromordinal(...) from builtins.type
     |      int -> date corresponding to a proleptic Gregorian ordinal.
     |  
     |  isocalendar(...)
     |      Return a 3-tuple containing ISO year, week number, and weekday.
     |  
     |  isoweekday(...)
     |      Return the day of the week represented by the date.
     |      Monday == 1 ... Sunday == 7
     |  
     |  strftime(...)
     |      format -> strftime() style string.
     |  
     |  today(...) from builtins.type
     |      Current date or datetime:  same as self.__class__.fromtimestamp(time.time()).
     |  
     |  toordinal(...)
     |      Return proleptic Gregorian ordinal.  January 1 of year 1 is day 1.
     |  
     |  weekday(...)
     |      Return the day of the week represented by the date.
     |      Monday == 0 ... Sunday == 6
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors inherited from date:
     |  
     |  day
     |  
     |  month
     |  
     |  year
    
    class time(builtins.object)
     |  time([hour[, minute[, second[, microsecond[, tzinfo]]]]]) --> a time object
     |  
     |  All arguments are optional. tzinfo may be None, or an instance of
     |  a tzinfo subclass. The remaining arguments may be ints.
     |  
     |  Methods defined here:
     |  
     |  __eq__(self, value, /)
     |      Return self==value.
     |  
     |  __format__(...)
     |      Formats self with strftime.
     |  
     |  __ge__(self, value, /)
     |      Return self>=value.
     |  
     |  __getattribute__(self, name, /)
     |      Return getattr(self, name).
     |  
     |  __gt__(self, value, /)
     |      Return self>value.
     |  
     |  __hash__(self, /)
     |      Return hash(self).
     |  
     |  __le__(self, value, /)
     |      Return self<=value.
     |  
     |  __lt__(self, value, /)
     |      Return self<value.
     |  
     |  __ne__(self, value, /)
     |      Return self!=value.
     |  
     |  __new__(*args, **kwargs) from builtins.type
     |      Create and return a new object.  See help(type) for accurate signature.
     |  
     |  __reduce__(...)
     |      __reduce__() -> (cls, state)
     |  
     |  __repr__(self, /)
     |      Return repr(self).
     |  
     |  __str__(self, /)
     |      Return str(self).
     |  
     |  dst(...)
     |      Return self.tzinfo.dst(self).
     |  
     |  isoformat(...)
     |      Return string in ISO 8601 format, HH:MM:SS[.mmmmmm][+HH:MM].
     |  
     |  replace(...)
     |      Return time with new specified fields.
     |  
     |  strftime(...)
     |      format -> strftime() style string.
     |  
     |  tzname(...)
     |      Return self.tzinfo.tzname(self).
     |  
     |  utcoffset(...)
     |      Return self.tzinfo.utcoffset(self).
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors defined here:
     |  
     |  hour
     |  
     |  microsecond
     |  
     |  minute
     |  
     |  second
     |  
     |  tzinfo
     |  
     |  ----------------------------------------------------------------------
     |  Data and other attributes defined here:
     |  
     |  max = datetime.time(23, 59, 59, 999999)
     |  
     |  min = datetime.time(0, 0)
     |  
     |  resolution = datetime.timedelta(0, 0, 1)
    
    class timedelta(builtins.object)
     |  Difference between two datetime values.
     |  
     |  Methods defined here:
     |  
     |  __abs__(self, /)
     |      abs(self)
     |  
     |  __add__(self, value, /)
     |      Return self+value.
     |  
     |  __bool__(self, /)
     |      self != 0
     |  
     |  __divmod__(self, value, /)
     |      Return divmod(self, value).
     |  
     |  __eq__(self, value, /)
     |      Return self==value.
     |  
     |  __floordiv__(self, value, /)
     |      Return self//value.
     |  
     |  __ge__(self, value, /)
     |      Return self>=value.
     |  
     |  __getattribute__(self, name, /)
     |      Return getattr(self, name).
     |  
     |  __gt__(self, value, /)
     |      Return self>value.
     |  
     |  __hash__(self, /)
     |      Return hash(self).
     |  
     |  __le__(self, value, /)
     |      Return self<=value.
     |  
     |  __lt__(self, value, /)
     |      Return self<value.
     |  
     |  __mod__(self, value, /)
     |      Return self%value.
     |  
     |  __mul__(self, value, /)
     |      Return self*value.
     |  
     |  __ne__(self, value, /)
     |      Return self!=value.
     |  
     |  __neg__(self, /)
     |      -self
     |  
     |  __new__(*args, **kwargs) from builtins.type
     |      Create and return a new object.  See help(type) for accurate signature.
     |  
     |  __pos__(self, /)
     |      +self
     |  
     |  __radd__(self, value, /)
     |      Return value+self.
     |  
     |  __rdivmod__(self, value, /)
     |      Return divmod(value, self).
     |  
     |  __reduce__(...)
     |      __reduce__() -> (cls, state)
     |  
     |  __repr__(self, /)
     |      Return repr(self).
     |  
     |  __rfloordiv__(self, value, /)
     |      Return value//self.
     |  
     |  __rmod__(self, value, /)
     |      Return value%self.
     |  
     |  __rmul__(self, value, /)
     |      Return value*self.
     |  
     |  __rsub__(self, value, /)
     |      Return value-self.
     |  
     |  __rtruediv__(self, value, /)
     |      Return value/self.
     |  
     |  __str__(self, /)
     |      Return str(self).
     |  
     |  __sub__(self, value, /)
     |      Return self-value.
     |  
     |  __truediv__(self, value, /)
     |      Return self/value.
     |  
     |  total_seconds(...)
     |      Total seconds in the duration.
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors defined here:
     |  
     |  days
     |      Number of days.
     |  
     |  microseconds
     |      Number of microseconds (>= 0 and less than 1 second).
     |  
     |  seconds
     |      Number of seconds (>= 0 and less than 1 day).
     |  
     |  ----------------------------------------------------------------------
     |  Data and other attributes defined here:
     |  
     |  max = datetime.timedelta(999999999, 86399, 999999)
     |  
     |  min = datetime.timedelta(-999999999)
     |  
     |  resolution = datetime.timedelta(0, 0, 1)
    
    class timezone(tzinfo)
     |  Fixed offset from UTC implementation of tzinfo.
     |  
     |  Method resolution order:
     |      timezone
     |      tzinfo
     |      builtins.object
     |  
     |  Methods defined here:
     |  
     |  __eq__(self, value, /)
     |      Return self==value.
     |  
     |  __ge__(self, value, /)
     |      Return self>=value.
     |  
     |  __getinitargs__(...)
     |      pickle support
     |  
     |  __gt__(self, value, /)
     |      Return self>value.
     |  
     |  __hash__(self, /)
     |      Return hash(self).
     |  
     |  __le__(self, value, /)
     |      Return self<=value.
     |  
     |  __lt__(self, value, /)
     |      Return self<value.
     |  
     |  __ne__(self, value, /)
     |      Return self!=value.
     |  
     |  __new__(*args, **kwargs) from builtins.type
     |      Create and return a new object.  See help(type) for accurate signature.
     |  
     |  __repr__(self, /)
     |      Return repr(self).
     |  
     |  __str__(self, /)
     |      Return str(self).
     |  
     |  dst(...)
     |      Return None.
     |  
     |  fromutc(...)
     |      datetime in UTC -> datetime in local time.
     |  
     |  tzname(...)
     |      If name is specified when timezone is created, returns the name.  Otherwise returns offset as 'UTC(+|-)HH:MM'.
     |  
     |  utcoffset(...)
     |      Return fixed offset.
     |  
     |  ----------------------------------------------------------------------
     |  Data and other attributes defined here:
     |  
     |  max = datetime.timezone(datetime.timedelta(0, 86340))
     |  
     |  min = datetime.timezone(datetime.timedelta(-1, 60))
     |  
     |  utc = datetime.timezone.utc
     |  
     |  ----------------------------------------------------------------------
     |  Methods inherited from tzinfo:
     |  
     |  __getattribute__(self, name, /)
     |      Return getattr(self, name).
     |  
     |  __reduce__(...)
     |      -> (cls, state)
    
    class tzinfo(builtins.object)
     |  Abstract base class for time zone info objects.
     |  
     |  Methods defined here:
     |  
     |  __getattribute__(self, name, /)
     |      Return getattr(self, name).
     |  
     |  __new__(*args, **kwargs) from builtins.type
     |      Create and return a new object.  See help(type) for accurate signature.
     |  
     |  __reduce__(...)
     |      -> (cls, state)
     |  
     |  dst(...)
     |      datetime -> DST offset in minutes east of UTC.
     |  
     |  fromutc(...)
     |      datetime in UTC -> datetime in local time.
     |  
     |  tzname(...)
     |      datetime -> string name of time zone.
     |  
     |  utcoffset(...)
     |      datetime -> timedelta showing offset from UTC, negative values indicating West of UTC

DATA
    MAXYEAR = 9999
    MINYEAR = 1
    datetime_CAPI = <capsule object "datetime.datetime_CAPI">

FILE
    /usr/lib/python3.5/datetime.py


In [1]:
from primes import prime_range

print(prime_range(10, 100))
[11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]

Exceptions

Errors happen. User error or logical error are not always avoidable. Standard way of error happening is called exceptions. An exception occures when an error is occured. You have to always be ready for exceptions and try the code for any exception.

In [33]:
# How to try for exceptions.

try:
    print('always execute')
    print(int('hi'))
    print('end')
except TypeError as e:
    print('Type Error occured.')
except ValueError as ve:
    print('Value Error occured.')

print('Hello world!')
always execute
Value Error occured.
Hello world!

Different type of exceptions can happen. You can specify different section for each one of them.

In [40]:
# How to trye for different exceptions.

number = input('Enter a number: ')  # hi

try:
    print('Square of {} is {}.'.format(number, int(number)**2))
    print(some_name_that_does_not_exist)
except ValueError as ve:
    print(ve)
except NameError as ne:
    print(ne)
except SyntaxError as se:
    print(se)

print('Running code continues.')
Enter a number: 123
Square of 123 is 15129.
name 'some_name_that_does_not_exist' is not defined
Running code continues.

There are other sections:

  • finally: That will executed in any condition. If exception happened or didn't, if we try for it or we didn't and ... .
  • else: That will executed if no exception happens.
In [149]:
# How to trye for different exceptions.

number = input('Enter a number: ')  # 123
some_name_that_exist = 'No error from this part.'

try:
    print('Square of {} is {}.'.format(number, int(number)**2))
    print(some_name_that_exist)  # Change to some_name_that_does_not_exist
except ValueError as ve:
    print(ve)
else:
    print('Else Section')
finally:
    print('Finally Section')

print('Running code continues.')
Enter a number: 123
Square of 123 is 15129.
No error from this part.
Else Section
Finally Section
Running code continues.

Exercise

  • Write a program that get two numbers from a user then print result of their division. Remember tow handle divide by zero exception!
In [18]:
num1, num2 = input('num1: '), input('num2: ')

try:
    num1, num2 = int(num1), int(num2)
except ValueError as ve:
    print('ERROR: You should enter numbers!')
else:
    try:
        print(num1/num2)
    except ZeroDivisionError as zde:
        print('ERROR: Second number can not be zero!')
num1: 123
num2: abc
ERROR: You should enter numbers!

Generate Error

If you wanted to signal an error you can do it by using Exception object for now. After OOP section, we learn new and more standard way for defining our exception.

In [19]:
def multiplication_table(row=10, column=10):
    if (row <= 0 or column <= 0):
        raise Exception('{} argument is {}. It should be bigger than zero!'
                        .format(*(['Row', row] if row <= 0 else ['Column', column])))
    return [[i*j for j in range(1, column + 1)] for i in range(1, row + 1)]

# print(multiplication_table(-1, 5))

try:
    print(multiplication_table(2, -3))
except Exception as e:
    print(e)
Column argument is -3. It should be bigger than zero!

You can even make this code smaller and more readable by using assert. It gets a condition and an exception, if condition is False, the exception raised.

In [151]:
def multiplication_table(row=10, column=10):
    assert row >= 0 and column >= 0, Exception('{} argument is {}. It should be bigger than zero!'
                                               .format(*(['Row', row] if row <= 0 else ['Column', column])))
    return [[i*j for j in range(1, column + 1)] for i in range(1, row + 1)]

try:
    multiplication_table(-1, 5)
except Exception as e:
    print(e)
Row argument is -1. It should be bigger than zero!

Object Oriented Programming

Good for easy code designing, reusability and readability.

Concepts:

  • Objects
    • State
    • Behavior
  • Class
    • Attributes
    • Methods
  • Instances

Simplest python class:

In [152]:
# A Class (code blueprints) that represent an Object (real world concepts).
class simple:
    pass

# Creating an Instance of the object.
simple_var = simple()

Class Attributes

In [153]:
class Car:
    """This is a simple Car class."""
    number_of_wheels = 4

# It's defined withing the structures of our class.
print(Car.number_of_wheels)
4
In [154]:
# Each instance can see it from the class.
simple_car = Car()
print(simple_car.number_of_wheels)
4
In [155]:
# Class attributes updates through all instances.
Car.number_of_wheels = 3
not_so_simple_car = Car()
print(not_so_simple_car.number_of_wheels)
print(simple_car.number_of_wheels)
3
3

Class attribute can have shadows for each object instances.

In [156]:
class Car:
    number_of_wheels = 4

strange_car = Car()

# Altering class attribute through instances make specific class attribute values for them.
strange_car.number_of_wheels = 18
print(Car.number_of_wheels)
print(strange_car.number_of_wheels)
4
18
In [157]:
# But it will break updated through class structure.
Car.number_of_wheels = 3
print(strange_car.number_of_wheels)
18
In [158]:
# Don't panic, you can bind your instance to your class again.
del strange_car.number_of_wheels
print(strange_car.number_of_wheels)
3

Exercise

  • Write a Circle class
In [ ]:
class Circle:
    radius = 0

a = Circle()
a.radius = 13

b = Circle()
b.radius = 1.3

Access to Instance

Each method in a class have a default first argument that is not passed by what we code.

In [21]:
class Person:
    name = 'Nobody'
    def print_name(self, a, b, c):
        print(self.name)
        print(a, b, c)

david = Person()
# david.name = 'David'
david.print_name(1, 2, 3)

john = Person()
john.name = 'john'
john.print_name('a', 'b', 'c')
Nobody
1 2 3
john
a b c

Now that we can access instances, we can define instance attributes.

In [1]:
class Person:
    def set(self, name, age):
        self.name = name
        self.birth_year = 2017 - age
    
    def get(self):
        return self.name, self.birth_year

bob = Person()
bob.set(name='Bod', age=25)
print(bob.get())
('Bod', 1992)

Exercise

  • Write a circle class that have:
    • A class attribute. One thing that is shared between all circles.
    • An instance attribute. What makes two circle different?
    • A method to set radius.
    • A method to calculate area of circle using the class and instance attributes.
In [3]:
class Circle:
    PI = 3.1415926535
    
    def set(self, radius):
        self.radius = radius
    
    def area(self):
        return self.PI * self.radius ** 2


c = Circle()
c.set(5)
print("Area of a circle with redius {} is {}".format(c.radius, c.area()))
Area of a circle with redius 5 is 78.5398163375

Special Methods

These methods have specific name and specific job. They have names that starts and ends with __

When we are creating an instance, __init__ is the very first method that get called automatically.

In [161]:
class Person:
    def __init__(self, name, age):
        self.name, self.age = name, age

    def get(self):
        return self.name, self.age

alice = Person('Alice', 21)
print(alice.get())
('Alice', 21)

Operator Overloading

Some of these special methods have is use by built-in operator like len function. By writing right special methods you can overload those behaviours.

In [3]:
class Person:
    def __init__(self, name, age):
        self.name, self.age = name, age
        
    def __len__(self):
        return self.age
    
    def __bool__(self):
        if self.age > 18:
            return True
        else:
            return False
        # return True if self.age > 18 else False
        # return self.age > 18
    
    def __iter__(self):
        return self.name.__iter__()
        
    def __str__(self):
        return "{} is {} year{} old.".format(self.name, self.age, 's' if self.age > 1 else '')

me = Person('Amir', 22)
print(len(me))
22
In [4]:
str(me)
Out[4]:
'Amir is 22 years old.'
In [163]:
if me:
    print('You are allowed to vote.')
else:
    print('You are not allowed to vote.')
You are allowed to vote.
In [164]:
print(me)
Amir is 22 years old.
In [165]:
for p in me:
    print(p)
A
m
i
r

You can find all information on special methods in Here.

Static Methods

Normal methods need an instance to do operation on it. Static methods on the other hand get called from class name. In this way you can categorize your function under a title.

In [7]:
class String:
    @staticmethod
    def reverse(string):
        return string[::-1]
    
    @staticmethod
    def is_upper_lower(string):
        """Checks for strings like ``, `A`, `a`, `Aa`, `aA`, `AaA`, `aAa`, ... ."""
        last_upper, last_lower = False, False
        for character in string:
            if not last_upper and not last_lower:
                if character.isupper():
                    last_upper = True
                else:
                    last_lower = True
            else:
                if last_upper and not last_lower and character.islower():
                    last_upper, last_lower = False, True
                elif not last_upper and last_lower and character.isupper():
                    last_upper, last_lower = True, False
                elif (last_upper and last_lower) or (not last_upper and not last_lower):
                    raise Exception("There is bug in the code! last_upper: {} and last_lower: {}"
                                    .format(last_upper, last_lower))
                else:
                    return False
        return True
    
#         part1, part2 = string[::2], string[1::2]
#         return part1.islower() and part2.isupper() or part1.isupper() and part2.islower()
    
    @staticmethod
    def unique_words(string, case_sensitive=False):
        return set([s if case_sensitive else s.lower() for s in string.split()])

print(list(map(String.reverse, ['', 'a', 'ab', 'aa', 'aba', 'abc', 'aaa'])))
print(list(map(String.is_upper_lower, ['', 'a', 'A', 'aA', 'Aa', 'aAa', 'AaA', 'ab', 'AB'])))
print(String.unique_words('What you want is unique word, so you should get what you want!', True))
['', 'a', 'ba', 'aa', 'aba', 'cba', 'aaa']
[True, True, True, True, True, True, True, False, False]
{'want', 'is', 'so', 'get', 'word,', 'unique', 'should', 'you', 'want!', 'what', 'What'}

Class Methods

They are special creators. In some ways they are like __init__. They create an instance. The first parameter of a class method is always class itself.

In [167]:
class Celsius:
    def __init__(self, temperature):
        self.temperature = temperature
    
    @classmethod
    def from_fahrenheit(cls, fahrenheit):
        return cls((fahrenheit - 32) * 5 / 9)
    
    @classmethod
    def from_kelvin(cls, kelvin):
        return cls(kelvin - 273.15)
    
    def __str__(self):
        return "Temperature in celsius is {:0.2f}°C.".format(self.temperature)

print(Celsius(15))
print(Celsius.from_fahrenheit(98.6))
print(Celsius.from_kelvin(300))
Temperature in celsius is 15.00°C.
Temperature in celsius is 37.00°C.
Temperature in celsius is 26.85°C.

If you have mutiple static methods and use some of in other ones, you have to hardcode the class name. This is not so good, we have so make changes as small as possible when class name is going to change. Another usage of class methods are for this problem.

In [168]:
class String:
    @classmethod
    def is_palindrome(cls, string, case_insensitive=True):
        string = cls._strip_string(string)
        
        # For case insensitive comparison, we lower-case string
        if case_insensitive:
            string = string.lower()
            
        return cls._is_palindrome(string)
    
    @staticmethod
    def _strip_string(string):
        return ''.join(c for c in string if c.isalnum())
    
    @staticmethod
    def _is_palindrome(string):
        for c in range(len(string) // 2):
            if string[c] != string[-c -1]:
                return False
        return True
    
    @staticmethod
    def get_unique_words(string):
        return set(string.split())

print(String.is_palindrome('A nut for a jar of tuna')) # True
print(String.is_palindrome('A nut for a jar of beans')) # False
True
False

Inheritance & Composition

Main purpose of OOP is reusing existing codes. Inheritance (is-a relationship) and composition (has-a relationship) are two main ways to reuse code.

In [5]:
class Engine:  # Base class, Parent class
    def start(self):
        return 'Engine started.'

    def stop(self):
        return 'Engine stopped.'

class Car:
    def __init__(self):
        self.engine = Engine()  # Composition

class MechanicalEngine(Engine):  # Inheritance
    def __init__(self):
        self.max_speed = 120

# Above, Engine is called Parent, Base class and MechanicalEngine is called Child, Derived class.

motorcycle_engine = MechanicalEngine()
print(motorcycle_engine.start())  # MechanicalEngine class inherits attributes and methods of Engine class.

c = Car()
print(c.engine.start())
Engine started.
Engine started.

Overwriting Methods

If you overwrite a method of parent class, the functionality of that method from parent class is gone.

In [170]:
class Book:
    def __init__(self, title, author):
        self.title, self.author = title, author
    
    def get_info(self):
        return "Title: {}, Author: {}".format(self.title, self.author)

class EBook(Book):
    def __init__(self, title, author, online_id):
        # Not so good way! What if Book init changes? What if we didn't know Book init?
        self.title, self.author, self.online_id = title, author, online_id

        # Better solution BUT, what Book is not the only one we inherits? What if we
        # wanted to change Book to Article?
        Book.__init__(self, title, author)
        self.online_id = online_id
        
        # Best solution!
        super().__init__(title, author)
        self.online_id = online_id
    
    def get_info(self):
        book_info = super().get_info()
        return "{}, Online ID: {}".format(book_info, self.online_id)

alice_in_wonderland = EBook('Alice in Wonderlang', 'Lewis Carroll', 1298)
print(alice_in_wonderland.get_info())
Title: Alice in Wonderlang, Author: Lewis Carroll, Online ID: 1298

Exercise

  • Write Person (coord & walk) and Employee classes.
  • Write Point and Line classes.
In [7]:
class Person:
    def __init__(self, name, age, height, x, y):
        self.name, self.age, self.height = name, age, height
        self.x, self.y = x, y
    
    def walk(self, move_x, move_y):
        self.x += move_x
        self.y += move_y

    def __str__(self):
        return '{} at {}, {}'.format(self.name, self.x, self.y)

class Employee(Person):
    def __init__(self, name, age, height, x, y, salary):
        super().__init__(name, age, height, x, y)
        self.salary = salary
    
    def __str__(self):
        return "{}, S: {}".format(super().__str__(), self.salary)


ahmad = Person('Ahmad', 16, 180, 4, 18)
print(ahmad)

Sara = Employee('Sara', 25, 180, 10, 5, 100)
print(Sara)
Ahmad at 4, 18
Sara at 10, 5, S: 100
In [8]:
class Point:
    def __init__(self, x, y):
        self.x, self.y = x, y
    
    def move(self, move_x, move_y):
        self.x += move_x
        self.y += move_y

        
class Line:
    def __init__(self, point_1, point_2):
        self.point_1 = point_1
        self.point_2 = point_2
    
    def gradient(self):
        x1, y1 = self.point_1.x, self.point_1.y
        x2, y2 = self.point_2.x, self.point_2.y
        
        if x1 - x2 == 0:
            return float('inf')
        
        return (y1 - y2) / (x1 - x2)


l = Line(Point(2, 3), Point(2, 4))
print(l.gradient())
inf

Multiple Inheritance & Method Resolution Order

Put names, comma seperated, and know that order of them matters (diamond problem)!

As the following code shows, MRO like our class then parents of our class then grandparents of out class and so on.

In [8]:
import math

class Shape:
    tag = 'Shape'
    
    def area():
        return 0

class Rectangle(Shape):
    tag = 'Rectangle'
    
    def __init__(self, a, b):
        self.a, self.b = a, b
    
    def area(self):
        return self.a * self.b

class Rhombus(Shape):
    tag = 'Rhombus'
    
    def __init__(self, a, theta):
        self.a, self.theta = a, theta
    
    def area(self):
        return self.a * self.a * math.sin(self.theta)

class Square(Rectangle, Rhombus):
    def __init__(self, a):
        # For Rectangle
        super().__init__(a, a)
        
        # For Rhombus
#         super().__init__(a, math.pi / 2)

my_square = Square(10)
print("Area of our square is: {} and Tag is: {}".format(my_square.area(), my_square.tag))
Area of our square is: 100 and Tag is: Rectangle

Private and Public

Every thing in python is public! And everyone working with it without a single problem. But there is an agreement on it.

If you use a single underscore at the beginning of names you use, means this is used for the inner workings of your code and it is better not to manipulate it.

If you use two or more underscore at the beginning and at most one underscore at end of names you use, means this is more special name that even child classes should not manipulate it.

In [172]:
class A:
    def __init__(self):
        self._private = 'Private value from A'
        self.__sensitive = 'Sensitive value from A'

class B(A):
    def __init__(self):
        super().__init__()
        self._private = 'Private value from B'
        self.__sensitive = 'Sensitive value from B'
    
    def how_to_change_private_values(self):
        self.__sensitive = 'Use the name defined value with.'

a, b = A(), B()

print('A Private: {}, Sensitive: {}'.format(a._private, a._A__sensitive))
print('B Private: {}, Sensitive: {}'.format(b._private, b._B__sensitive))
print('B also include A sensitive to A works find.')
print('A sensitive value in B: {}'.format(b._A__sensitive))
A Private: Private value from A, Sensitive: Sensitive value from A
B Private: Private value from B, Sensitive: Sensitive value from B
B also include A sensitive to A works find.
A sensitive value in B: Sensitive value from A

Name Mangling

When you use two or more underscores at the beginning of a name, the actual name, that is reachable from outside class scope is mangled. Mangling is the process of adding class specific name to beginning of the name you defined. For example __VARNAME changes to _CLASSNAME__VARNAME.

Useful Tips

  • Use isinstance() to find a varialble origins.
In [173]:
isinstance(motorcycle_engine, MechanicalEngine)
Out[173]:
True
  • Use issubclass() to find examine inheritance relation between to classes.
In [174]:
issubclass(MechanicalEngine, Engine), issubclass(Engine, MechanicalEngine)
Out[174]:
(True, False)
  • Property decorators are another thing to mention. They are like setter and getter.
In [175]:
class Person:
    def __init__(self, firstname, lastname):
        self.__firstname = firstname
        self.__lastname = lastname
        
    @property
    def full_name(self):
        return '{} {}'.format(self.__firstname, self.__lastname)
    
    @full_name.setter
    def full_name(self, full_name):
        fname_splited = full_name.split()
        self.__firstname = fname_splited[0]
        self.__lastname = ' '.join(fname_splited[1:])

p = Person('Nikola', 'Tesla')
print(p.full_name)
p.full_name = 'Thomas Alva Edison'
print(p.full_name)
Nikola Tesla
Thomas Alva Edison

Generators

  • yield
  • yeild from

Decorators

Example: Decorator to calculate up execution time.

Decorator Factory

Homeworks:

  • Describing what some codes do.

Notes to talk:

  • Printing structures and customization.

PIP

Python have libraries. pip is a program that can install and remove your packages. This a fast and safe way to get a python library for our projects.

Virtualenv

Consider situations like 2 projects with same dependencies of different versions. Like pip, virtualenv helps you manage your packages but in different way.

Installation

pip3 install virtualenv

Usage

virtualenv .venv           # Creating python virtual environment
source .venv/bin/activate  # Activating virtual environment
deactivate                 # Deactivating virtual environment

Database

Python has many many libraries for managing databases. Here we use SQLite3 that comes with Python when you installed it.

In [16]:
import sqlite3
import os

try:
    os.remove('simple-db')
except FileNotFoundError as e:
    pass

db = sqlite3.connect('simple-db')

# Create a new table.
db.execute('create table music ( title text, artist text, rating int)')
db.commit()

# Insert new data to music table.
db.execute('insert into music (title, artist, rating) values ("Immigrant Sont", "Led Zeppelin",8)')
db.execute('insert into music (title, artist, rating) values ("GOT", "Ramin Djawadi",9)')
db.execute('insert into music (title, artist, rating) values ("The map of reality", "The New Smiths",9)')
db.commit()

# Grab stored data from our database.
table = db.execute('select * from music')
for row in table:
    print(row)
('Immigrant Sont', 'Led Zeppelin', 8)
('GOT', 'Ramin Djawadi', 9)
('The map of reality', 'The New Smiths', 9)

Graphical User Interface with Tkinter

We use tkinter module for this part of our course. To start we need to import the module first.

In [12]:
# 3 ways to import tkinter module

from tkinter import *  # This is very bad way. We import everything in the module to global scope.
# There is chance that we overwrite something imported from or to tkinter module.

import tkinter  # This way is not best but works fine. The name is long to type.

import tkinter as tk  # This is best way to do this.

If you have problem with importing tkinter you might want to check the installation of it. Read the error completely there should be some clue to how to fix it or search the error online for more clues.

Three components for GUI programming:

  • What to place on screen? -> Widgets
  • Where to place the widgets? -> Geometry Management
  • How do widgets behave? -> Events & Callbacks

Every thing is part of a big tree structure.

Root Window

With tkinter there is no need to start from scratch and invent the wheel! The root window is what you have to start with. A window that have basic functionality that you need.

The concept of a mainloop is like a manager. You are the one who write standards an law, but someone else know better how to execute those.

In [13]:
root = tk.Tk()  # root variable is filled with a basic root window object.
root.mainloop()

Widgets

Widgets are building blocks of a GUI application. They work together to serve an application goal. Labels, text inputs, button, sliders, image viewer, tabs and even root window are widgets.

In [14]:
root = tk.Tk()

greetings_label = tk.Label(root, text='Hello User!')  # Attaching a label to root with some text.
greetings_label.pack()  # This method should be called to define a position for our new widget.

root.mainloop()

Remember all widgets are objects that is created and returned by a specific functions like Label. Then you configure them as you like them to behave (OOP) and then call the manager to do dirty works!

In [ ]:
root = tk.Tk()

username_frame = tk.Frame(root)
tk.Label(username_frame, text='Username').pack(side=tk.LEFT)
username_entry = tk.Entry(username_frame)
username_entry.pack(side=tk.LEFT)
username_frame.pack(fill=tk.X)

password_frame = tk.Frame(root)
tk.Label(password_frame, text='Password').pack(side=tk.LEFT)
password_entry = tk.Entry(password_frame, show='*')
password_entry.pack(side=tk.LEFT)
password_frame.pack(fill=tk.X)

def login():
    username = username_entry.get()
    password = password_entry.get()
    if username == 'username' and password == 'password':
        print('Correct username and password.')
    else:
        print('ERROR: Wrong username and password. username: {}, password: {}'.format(username, password))

button_frame = tk.Frame(root)
login_button = tk.Button(button_frame, text='Login', command=login)
login_button.pack(fill=tk.X)
button_frame.pack(fill=tk.X)

root.mainloop()
ERROR: Wrong username and password. username: username, password: 123
Correct username and password.

Geometry Management

Three ways to manage:

  • Pack: Simple for simple layouts.
  • Grid: Most popular one. Manage a window as it is a table.
  • Place: Least popular one. Best control with absolute positioning.
In [180]:
root = tk.Tk()

frame = tk.Frame(root)
tk.Label(frame, text='Pack demo of side and fill.').pack()
tk.Button(frame, text='A').pack(side=tk.LEFT, fill=tk.Y)
tk.Button(frame, text='B').pack(side=tk.TOP, fill=tk.X)
tk.Button(frame, text='C').pack(side=tk.RIGHT, fill=tk.NONE)
tk.Button(frame, text='D').pack(side=tk.TOP, fill=tk.BOTH)
frame.pack()

tk.Label(root, text='Pack demo of expand.').pack()
tk.Button(root, text='I do not expand').pack()
tk.Button(root, text='I do not fill X but I expand').pack(expand=True)
tk.Button(root, text='I fill X and expand.').pack(expand=True, fill=tk.X)

root.mainloop()

Examples:

  • Count unique words of a file and write a json about it to another file.
  • Prime number function.
  • Prime number generator.
  • Fibonacci number recursive function.
  • Fibonacci number generator.
  • Iterator object.
  • Web crawler. (ircalendarevents)

Project

Now learning python syntax and usages is finished. Let us start our big project!

The project is simple client chat. This applicatoin have 3 main parts.

  • Graphical User Interface
  • Network Programming
  • Database Management
In [ ]: