My Python Learninng Notes

Python Learning Course


Session 1 —

Install

  • windows
    • Set the Add Python *.* to PATH option during the installation process.
  • linux/mac
  • online interpreter

Python2 vs Python3

  • Python3 history
    • Python 3.0 - December 3, 2008
    • Python 3.1 - 2009
    • Python 3.2 - 2011
    • Python 3.3 - 2012
    • Python 3.4 - 2014
    • Python 3.5 - 2015
    • Python 3.6 - 2016
    • Python 3.7 - 2018
    • Python 3.8 - 2019
    • Python 3.9 - 2020
  • Key differences
Python3 Pythom2
syntax is simpler and easily understandable. syntax is comparatively difficult to understand
default storing of strings is Unicode define Unicode string value with “u.”
value of variables never changes value of the global variable will be changed inside a for-loop
exceptions, enclosed in parenthesis exceptions, enclosed in notations
ordering comparisons are simplified rules of ordering comparison are complex
offers Range() function to perform iterations the xrange() is used for iterations

Example:

# Python 3
print("Hello World!")

# Python 2
print "Hello World!"
  • Python2 EOL (End of Life) 2020

Code Editors

Python Cheat Sheets

Primitive Datatypes and Operators

Mathematical Operations

# Numbers
3    # => 3

# Simple math
1 + 1     # => 2
8 - 1     # => 7
10 * 2    # => 20
35 / 5    # => 7.0


#Integer division rounds down for both positive and negative numbers.
5 // 3         # => 1
-5 // 3        # => -2
5.0 // 3.0     # => 1.0 # works on floats too
-5.0 // 3.0    # => -2.0


# The result of division is always a float
10.0 / 3    # => 3.3333333333333335

# Modulo operation
7 % 3     # => 1
# i % j have the same sign as j, unlike C
-7 % 3    # => 2

# Exponentiation (x**y, x to the yth power)
2**3    # => 8

# Enforce precedence with parentheses
1 + 3 * 2      # => 7
(1 + 3) * 2    # => 8

Variables

There are no declarations, only assignments. Convention is to use lower_case_with_underscores

integer

nunber = 5
number    # => 5

# Define an integer in binry
number = 0b10
number    # => 2
bin(number)    # => 0b10

# Define an integer in hexadecimal
number = 0x10
number    # => 16
hex(number)    # => 0x10

Complex Numbers

x = 0 + 1j
x * x    # => (-1+0j)

string

string = "I'm a string variable"
string        # => "I'm a string variable"
string[0]     # => "I"
string[-1]    # => "e"
string[0:3]   # => "I'm"

# Strings are created with " or '
"This is a string."
'This is also a string.'

# Strings can be added too
"Hello " + "world!"    # => "Hello world!"
# String literals (but not variables) can be concatenated without 
# using '+'
"Hello " "world!"      # => "Hello world!"

# A string can be treated like a list of characters
"Hello world!"[0]    # => 'H'

# You can find the length of a string
len("This is a string")    # => 16

# You can also format using f-strings or formatted string literals 
# (in Python 3.6+)
name = "Reiko"
f"She said her name is {name}."   # => "She said her name is Reiko"
# You can basically put any Python expression inside the braces and 
# it will be output in the string.
f"{name} is {len(name)} characters long."   # => "Reiko is 5 
						# characters long."

multi_line_string ="""
First line
Second line
...
Last line
"""
multi_line_string    # => '\nFirst line\nSecond line\n...\nLast line\n'
String Methods
str.title()
"""
Return a titlecased version of the string where words start with an 
uppercase character and the remaining characters are lowercase.
"""

'Hello world'.title()   # => 'Hello World'

str.strip()
"""
Return a copy of the string with the leading and trailing characters
removed. The chars argument is a string specifying the set of 
characters to be removed. If omitted or None, the chars argument 
defaults to removing whitespace. The chars argument is not a 
prefix or suffix; rather, all combinations of its 
values are stripped.
"""

'   spacious   '.strip()		  # => 'spacious'
'www.example.com'.strip('cmowz.')	  # => 'example'

str.rstrip()
"""
Return a copy of the string with trailing characters removed. The 
chars argument is a string specifying the set of characters to be 
removed. If omitted or None, the chars argument defaults to removing
whitespace. The chars argument is not a suffix; rather, all 
combinations of its values are stripped.
"""

str.lstrip()
"""
Return a copy of the string with leading characters removed. The 
chars argument is a string specifying the set of characters to be 
removed. If omitted or None, the chars argument defaults to removing 
whitespace. The chars argument is not a prefix; rather, all 
combinations of its values are stripped.
"""

str.find(sub[, start[, end]])
"""
Return the lowest index in the string where substring sub is found 
within the slice s[start:end]. Optional arguments start and end are
interpreted as in slice notation. Return -1 if sub is not found.
"""

"""
 Note The find() method should be used only if you need to know the
position of sub. To check if sub is a substring or not, use the in
operator:
"""
'Py' in 'Python'   # => True

str.replace(old, new[, count])
"""
Return a copy of the string with all occurrences of substring old
replaced by new. If the optional argument count is given, only the
first count occurrences are replaced.
"""

str.lower()
"""
Return a copy of the string with all the cased characters 4 
converted to lowercase.
"""

str.upper()
"""
Return a copy of the string with all the cased characters 4 
converted to uppercase. Note that s.upper().isupper() might 
be False if s contains uncased characters or if the Unicode 
category of the resulting character(s) is not “Lu” (Letter,
uppercase), but e.g. “Lt” (Letter, titlecase).
"""

str.split(sep=None, maxsplit=-1)
"""
Return a list of the words in the string, using sep as the delimiter string. 
If maxsplit is given, at most maxsplit splits are done (thus, the list will 
have at most maxsplit+1 elements). If maxsplit is not specified or -1, then 
there is no limit on the number of splits (all possible splits are made).
If sep is given, consecutive delimiters are not grouped together and are deemed
to delimit empty strings (for example, '1,,2'.split(',') returns ['1', '', '2']). 
The sep argument may consist of multiple characters (for example, 
'1<>2<>3'.split('<>') returns ['1', '2', '3']). Splitting an empty string with a 
specified separator returns [''].
"""
'1,2,3'.split(',')		  # => ['1', '2', '3']
'1,2,3'.split(',', maxsplit=1)	  # => ['1', '2,3']
'1,2,,3,'.split(',')		  # => ['1', '2', '', '3', '']

str.splitlines([keepends])
"""
Return a list of the lines in the string, breaking at line boundaries. Line 
breaks are not included in the resulting list unless keepends is given and true.
This method splits on the following line boundaries. In particular, the 
boundaries are a superset of universal newlines.

Representation	Description
-----------------------------===============
\n		Line Feed
\r		Carriage Return
\r\n		Carriage Return + Line Feed
\v or \x0b	Line Tabulation
\f or \x0c	Form Feed
\x1c		File Separator
\x1d		Group Separator
\x1e		Record Separator
\x85		Next Line (C1 Control Code)
\u2028		Line Separator
\u2029		Paragraph Separator
"""

sorted("This is a test string from Andrew".split(), key=str.lower)
"""
# => ['a', 'Andrew', 'from', 'is', 'string', 'test', 'This']
"""

float

number = 45
real_number = 3.14159265359

Booleans and Logical operators

Boolean values are primitives (Note: the capitalization)

True     # => True
False    # => False

# negate with not
not True     # => False
not False    # => True

# Boolean Operators
# Note "and" and "or" are case-sensitive
True and False    # => False
False or True     # => True

# True and False are actually 1 and 0 but with different keywords
True + True   # => 2
True * 8      # => 8
False - 5     # => -5

# Comparison operators look at the numerical value of True and False
0 == False    # => True
1 == True     # => True
2 == True     # => False
-5 != False   # => True

"""
Using boolean logical operators on ints casts them to booleans for
evaluation, but their non-cast value is returned
Don't mix up with bool(ints) and bitwise and/or (&,|)
"""
bool(0)       # => False
bool(4)       # => True
bool(-6)      # => True
0 and 2       # => 0
-5 or 0       # => -5

# Equality is ==
1 == 1    # => True
2 == 1    # => False

# Inequality is !=
1 != 1    # => False
2 != 1    # => True

# More comparisons
1 < 10    # => True
1 > 10    # => False
2 <= 2    # => True
2 >= 2    # => True

# Seeing whether a value is in a range
1 < 2 and 2 < 3    # => True
2 < 3 and 3 < 2    # => False
# Chaining makes this look nicer
1 < 2 < 3    # => True
2 < 3 < 2    # => False

# (is vs. ==) is checks if two variables refer to the same object,
# but == checks if the objects pointed to have the same values.
a = 1
b = 1
b is a              # => True, a and b refer to the same object

a = [1, 2, 3, 4]    # Point a at a new list, [1, 2, 3, 4]
b = a               # Point b at what a is pointing to
b is a              # => True, a and b refer to the same object
b == a              # => True, a's and b's objects are equal
b = [1, 2, 3, 4]  # Point b at a new list, [1, 2, 3, 4]
b is a              # => False, a and b do not refer to the same object
b == a              # => True, a's and b's objects are equal
b is not a
"""
# =>
True, a's and b's objects are equal but a and b do 
not refer to the same object
"""

# id(some_var) returns the address of the memory location that "some_var"
# references

# None is an object
None    # => None

# Don't use the equality "==" symbol to compare objects to None
# Use "is" instead. This checks for equality of object identity.
"etc" is None    # => False
None is None     # => True

# None, 0, and empty strings/lists/dicts/tuples all evaluate to False.
# All other values are True
bool(0)     # => False
bool("")    # => False
bool([])    # => False
bool({})    # => False
bool(())    # => False

Convert Variable Types

You can use type(variable) to get the type of variable.

string_as_float = float(string)
real_number_as_int = int(real_number)
number_as_bool = bool(number)
boolean_as_str = str(boolean)
print("I'm Python. Nice to meet you!")    # => I'm Python. Nice to meet you!

# By default the print function also prints out a newline at the end.
# Use the optional argument end to change the end string.
print("Hello, World", end="!")    # => Hello, World!

input Function

Simple way to get input data from console

input_string_var = input("Enter some data: ") # Returns the data as a string

Session 2 —

Linter

A linter, is a tool that analyzes source code to flag programming errors, bugs, stylistic errors, and suspicious constructs.

Pylint is a tool that checks for errors in Python code, tries to enforce a coding standard and looks for code smells. It can also look for certain type errors, it can recommend suggestions about how particular blocks can be refactored and can offer you details about the code’s complexity.

Python Enhancement Proposals

How to Write Beautiful Python Code. PEP 8 exists to improve the readability of Python code.

Source Code Encoding

By default, Python source files are treated as encoded in UTF-8. In that encoding, characters of most languages in the world can be used simultaneously in string literals, identifiers and comments — although the standard library only uses ASCII characters for identifiers, a convention that any portable code should follow. To display all these characters properly, your editor must recognize that the file is UTF-8, and it must use a font that supports all the characters in the file.

# To declare that Windows-1252 encoding is to be used
# -*- coding: cp1252 -*-

Control Flow and Iterables

Indentation is significant in Python! Convention is to use four spaces, not tabs.

if Statement

# Here is an if statement.
# This prints "some_var is smaller than 10"
# In python "else if" is spelled "elif".
# Also, you need a colon after the elif and the else.

if some_var > 10:
    print("some_var is totally bigger than 10.")
elif some_var < 10:    # This elif clause is optional.
    print("some_var is smaller than 10.")
else:                  # This is optional too.
    print("some_var is indeed 10.")

# Putting a simple if-then-else statement on one line (Ternary Operator)
value_when_true if condition else value_when_false

body_temperature = 39
have_fever = True if body_temperature > 37 else False	  # => have_fever = True

for Loops

# range(number)" returns an iterable of numbers
# from zero to the given number
for i in range(4):
    print(i)
"""
# =>
    0
    1
    2
    3
"""

# "range(lower, upper)" returns an iterable of numbers
# from the lower number to the upper number
for i in range(4, 8):
    print(i)
"""
# =>
    4
    5
    6
    7
"""


# "range(lower, upper, step)" returns an iterable of numbers
# from the lower number to the upper number, while incrementing
# by step. If step is not indicated, the default value is 1.
for i in range(4, 8, 2):
    print(i)
"""
# =>
    4
    6
"""

print(range(10))	  # => range(0, 10)

sum(range(4))		  # => 0 + 1 + 2 + 3 = 6

list(range(4))		  # => [0, 1, 2, 3]

type(range(4))		  # => <class 'range'>


# To loop over a list, and retrieve both the index and the value of each item
# in the list
animals = ["dog", "cat", "mouse"]
for i, value in enumerate(animals):
    print(i, value)
"""
# =>
    0 dog
    1 cat
    2 mouse
"""

# For loops iterate over lists
for animal in ["dog", "cat", "mouse"]:
    # You can use format() to interpolate formatted strings
    print("{} is a mammal".format(animal))
"""
# =>
    dog is a mammal
    cat is a mammal
    mouse is a mammal
"""

# Code that modifies a collection while iterating over that same collection
# can be tricky to get right. Instead, it is usually more straight-forward 
# to loop over a copy of the collection or to create a new collection.
# Strategy:  Iterate over a copy
for user, status in users.copy().items():
    if status == 'inactive':
        del users[user]
# Strategy:  Create a new collection
active_users = {}
for user, status in users.items():
    if status == 'active':
        active_users[user] = status

# To iterate over the indices of a sequence, you can combine range() and len().
a = ['Mary', 'had', 'a', 'little', 'lamb']
for i in range(len(a)):
    print(i, a[i])
"""
# =>
0 Mary
1 had
2 a
3 little
4 lamb
"""

while Loops

# While loops go until a condition is no longer met.
x = 0
while x < 4:
    print(x)
    x += 1  # Shorthand for x = x + 1
"""
#+>
    0
    1
    2
    3
"""

# An initial sub-sequence of the Fibonacci series.
a, b = 0, 1
while a < 1000:
    print(a, end=',')
    a, b = b, a+b
# => 0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,

break and continue Statements, and else Clauses on Loops

The break statement, breaks out of the innermost enclosing for or while loop. Loop statements may have an else clause; it is executed when the loop terminates through exhaustion of the iterable (with for) or when the condition becomes false (with while), but not when the loop is terminated by a break statement.

# This is exemplified by the following loop, which 
# searches for prime numbers.
for n in range(2, 10):
    for x in range(2, n):
        if n % x == 0:
            print(n, 'equals', x, '*', n//x)
            break
    else: # else clause belongs to the for loop, not the if statement.
        # loop fell through without finding a factor
        print(n, 'is a prime number')
"""
=>
2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3
"""

# Like break, the continue statement, also borrowed from C, continues with the
# next iteration of the loop.
for num in range(2, 10):
    if num % 2 == 0:
        print("Found an even number", num)
        continue
    print("Found a number", num)
"""
=>
Found an even number 2
Found an odd number 3
Found an even number 4
Found an odd number 5
Found an even number 6
Found an odd number 7
Found an even number 8
Found an odd number 9
"""

Iterable Object

Python offers a fundamental abstraction called the Iterable. An iterable is an object that can be treated as a sequence. The object returned by the range function, is an iterable.

filled_dict = {"one": 1, "two": 2, "three": 3}
our_iterable = filled_dict.keys()
print(our_iterable)    # => dict_keys(['one', 'two', 'three']).
			  # This is an object that implements our
			  # Iterable interface.

# We can loop over it.
for i in our_iterable:
    print(i)  # Prints one, two, three

# However we cannot address elements by index.
our_iterable[1]  # Raises a TypeError

# An iterable is an object that knows how to create an iterator.
our_iterator = iter(our_iterable)

"""
Our iterator is an object that can remember the state as we 
traverse through it.
We get the next object with "next()".
"""
next(our_iterator)    # => "one"

# It maintains state as we iterate.
next(our_iterator)    # => "two"
next(our_iterator)    # => "three"

# After the iterator has returned all of its data, it raises a 
# StopIteration exception
next(our_iterator)  # Raises StopIteration

# We can also loop over it, in fact, "for" does this implicitly!
our_iterator = iter(our_iterable)
for i in our_iterator:
    print(i)  # Prints one, two, three

# You can grab all the elements of an iterable or iterator by
# calling list() on it.
list(our_iterable)    # => Returns ["one", "two", "three"]
list(our_iterator)    # => Returns [] because state is saved

Session 3 —

Handling Exceptions

It is possible to write programs that handle selected exceptions. Look at the following example, which asks the user for input until a valid integer has been entered, but allows the user to interrupt the program (using Control-C or whatever the operating system supports); note that a user-generated interruption is signalled by raising the KeyboardInterrupt exception.

Built-in Exceptions Description
ArithmeticError Raised when an error occurs in numeric calculations
AssertionError Raised when an assert statement fails
AttributeError Raised when attribute reference or assignment fails
Exception Base class for all exceptions
EOFError Raised when the input() method hits an “end of file” condition (EOF)
FloatingPointError Raised when a floating point calculation fails
GeneratorExit Raised when a generator is closed (with the close() method)
ImportError Raised when an imported module does not exist
IndentationError Raised when indendation is not correct
IndexError Raised when an index of a sequence does not exist
KeyError Raised when a key does not exist in a dictionary
KeyboardInterrupt Raised when the user presses Ctrl+c, Ctrl+z or Delete
LookupError Raised when errors raised cant be found
MemoryError Raised when a program runs out of memory
NameError Raised when a variable does not exist
NotImplementedError Raised when an abstract method requires an inherited class to override the method
OSError Raised when a system related operation causes an error
OverflowError Raised when the result of a numeric calculation is too large
ReferenceError Raised when a weak reference object does not exist
RuntimeError Raised when an error occurs that do not belong to any specific expections
StopIteration Raised when the next() method of an iterator has no further values
SyntaxError Raised when a syntax error occurs
TabError Raised when indentation consists of tabs or spaces
SystemError Raised when a system error occurs
SystemExit Raised when the sys.exit() function is called
TypeError Raised when two different types are combined
UnboundLocalError Raised when a local variable is referenced before assignment
UnicodeError Raised when a unicode problem occurs
UnicodeEncodeError Raised when a unicode encoding problem occurs
UnicodeDecodeError Raised when a unicode decoding problem occurs
UnicodeTranslateError Raised when a unicode translation problem occurs
ValueError Raised when there is a wrong value in a specified data type
ZeroDivisionError Raised when the second operator in a division is zero
while True:
    try:
        x = int(input("Please enter a number: "))
        break
    except ValueError:
        print("Oops!  That was no valid number.  Try again...")

# Handle exceptions with a try/except block
try:
    # Use "raise" to raise an error
    raise IndexError("This is an index error")
except IndexError as e:
    pass                 # Pass is just a no-op. Usually you would do recovery here.
except (RuntimeError, TypeError, NameError, ValueError):
    pass                 # Multiple exceptions can be handled together, if required.
else:                    # Optional clause to the try/except block. Must follow all except blocks
    print("All good!")   # Runs only if the code in try raises no exceptions
finally:                 #  Execute under all circumstances
    print("We can clean up resources here")
    

Session 4 —

Functions

Create A Function

# Use "def" to create new functions
def add(x, y):
    print("x is {} and y is {}".format(x, y))
    return x + y  # Return values with a return statement

# Calling functions with parameters
add(5, 6)  # => prints out "x is 5 and y is 6" and returns 11

# Another way to call functions is with keyword arguments
add(y=6, x=5)  # Keyword arguments can arrive in any order.

Procedures and Functions

"""
Write Fibonacci series up to n (as a procedure)
The first statement of the function body can optionally be a string
literal; this string literal is the function’s documentation string,
or docstring.
"""
def fib(n):    
    """Print a Fibonacci series up to n."""
    a, b = 0, 1
    while a < n:
        print(a, end=' ')
        a, b = b, a+b
    print()
# Now call the function we just defined:
fib(2000)  # => 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597

# You can assign the function name to another name which can then 
# also be used as a function. This serves as a general renaming mechanism:

fib	  # => <function fib at 10042ed0>
f = fib
f(100)	  # => 0 1 1 2 3 5 8 13 21 34 55 89

# The difference between Procedures and Functions:
# Even functions without a return statement do return the None value.
print(fib(0))	  # => None

# A function that returns a list of the numbers of the Fibonacci series,
# instead of printing it
def fib2(n):  # return Fibonacci series up to n
    """Return a list containing the Fibonacci series up to n."""
    result = []
    a, b = 0, 1
    while a < n:
        result.append(a)    # see below
        a, b = b, a+b
    return result

f100 = fib2(100)    # call it
# write the result
f100	  # => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

Positional Arguments And Keyword Arguments

You can define functions that take a variable number of positional arguments or using keyword arguments of the form kwarg=value. You can also define functions that take a variable number of keyword arguments, as well.

def varargs(*args):
    return args

varargs(1, 2, 3)  # => (1, 2, 3)

def keyword_args(**kwargs):
    return kwargs

# Let's call it to see what happens
keyword_args(big="foot", loch="ness")
# => {"big": "foot", "loch": "ness"}

# You can do both at once, if you like
def all_the_args(*args, **kwargs):
    print(args)
    print(kwargs)
"""
all_the_args(1, 2, a=3, b=4) prints:
    (1, 2)
    {"a": 3, "b": 4}
"""

# When calling functions, you can do the opposite of args/kwargs!
# Use * to expand tuples and use ** to expand kwargs.
args = (1, 2, 3, 4)
kwargs = {"a": 3, "b": 4}
all_the_args(*args)
# equivalent to all_the_args(1, 2, 3, 4)
all_the_args(**kwargs)
# equivalent to all_the_args(a=3, b=4)
all_the_args(*args, **kwargs)	
# equivalent to all_the_args(1, 2, 3, 4, a=3, b=4)

def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
    print("-- This parrot wouldn't", action, end=' ')
    print("if you put", voltage, "volts through it.")
    print("-- Lovely plumage, the", type)
    print("-- It's", state, "!")

# Valid way of calling parrot function
# 1 positional argument
parrot(1000)                                          
# 1 keyword argument
parrot(voltage=1000)                                  
# 2 keyword arguments
parrot(voltage=1000000, action='VOOOOOM')             
# 2 keyword arguments
parrot(action='VOOOOOM', voltage=1000000)             
# 3 positional arguments
parrot('a million', 'bereft of life', 'jump')         
# 1 positional, 1 keyword
parrot('a thousand', state='pushing up the daisies')  

# The following calls would be invalid
# required argument missing
parrot()                     
# non-keyword argument after a keyword argument
parrot(voltage=5.0, 'dead')  
# duplicate value for the same argument
parrot(110, voltage=220)     
# unknown keyword argument
parrot(actor='John Cleese')  

Default Argument Values

def ask_ok(prompt, retries=4, reminder='Please try again!'):
    while True:
        ok = input(prompt)
        if ok in ('y', 'ye', 'yes'):
            return True
        if ok in ('n', 'no', 'nop', 'nope'):
            return False
        retries = retries - 1
        if retries < 0:
            raise ValueError('invalid user response')
        print(reminder)

# This function can be called in several ways:
ask_ok('Do you really want to quit?')
ask_ok('OK to overwrite the file?', 2)
ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')

The default values are evaluated at the point of function definition in the defining scope. Important warning: The default value is evaluated only once. This makes a difference when the default is a mutable object such as a list, dictionary, or instances of most classes.

i = 5

def f(arg=i):
    print(arg)

i = 6
f()	  # => 5

""" 
The following function accumulates the arguments passed to it on 
subsequent calls:
"""
def f(a, L=[]):
    L.append(a)
    return L

print(f(1))	  # => [1]
print(f(2))	  # => [1, 2]
print(f(3))	  # => [1, 2, 3]

"""
If you don’t want the default to be shared between subsequent 
calls, you can write the function like this instead:
"""
def f(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L

print(f(1))	  # => [1]
print(f(2))	  # => [2]
print(f(3))	  # => [3]

Special Parameters

def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
#     -----------    ----------     ----------
#       |             |                  |
#       |        Positional or keyword   |
#       |                                - Keyword only
#        -- Positional only

def standard_arg(arg):
    print(arg)

def pos_only_arg(arg, /):
    print(arg)

def kwd_only_arg(*, arg):
    print(arg)

def combined_example(pos_only, /, standard, *, kwd_only):
    print(pos_only, standard, kwd_only)

pos_only_arg(1)	  # => 1
pos_only_arg(arg=1)
"""
# =>
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: pos_only_arg() got an unexpected keyword argument 'arg'
"""

kwd_only_arg(3)
"""
# =>
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: kwd_only_arg() takes 0 positional arguments but 1 was given
"""
kwd_only_arg(arg=3)	  # => 3

combined_example(1, 2, 3)
"""
# =>
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: combined_example() takes 2 positional arguments but 3 were given
"""
combined_example(1, 2, kwd_only=3)		  # => 1 2 3
combined_example(1, standard=2, kwd_only=3)	  # =>1 2 3
combined_example(pos_only=1, standard=2, kwd_only=3)
"""
# =>
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: combined_example() got an unexpected keyword argument 'pos_only'
"""

# Collision between the positional argument name and **kwds 
# which has name as a key
def foo(name, **kwds):
    return 'name' in kwds

foo(1, **{'name': 2})
"""
# =>
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() got multiple values for argument 'name'
"""

# Using / (positional only arguments), it is possible since it allows
# name as a positional argument and 
# 'name' as a key in the keyword arguments:
def foo(name, /, **kwds):
    return 'name' in kwds

foo(1, **{'name': 2})  # => True

Unpacking Argument Lists

# normal call with separate arguments
list(range(3, 6))	  # =>[3, 4, 5]
# call with arguments unpacked from a list
args = [3, 6]
list(range(*args))	  # => [3, 4, 5]

# In the same fashion, dictionaries can deliver keyword arguments 
# with the **-operator.
def parrot(voltage, state='a stiff', action='voom'):
    print("-- This parrot wouldn't", action, end=' ')
    print("if you put", voltage, "volts through it.", end=' ')
    print("E's", state, "!")

d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
parrot(**d)
"""
# =>
-- This parrot wouldn't VOOM if you put four million volts through 
it. E's bleedin' demised !
"""

Returning Multiple Values (with tuple assignments)

def swap(x, y):
    return y, x	
"""
Return multiple values as a tuple without the parenthesis.
(Note: parenthesis have been excluded but can be included)
"""

x = 1
y = 2
x, y = swap(x, y)     # => x = 2, y = 1
# (x, y) = swap(x,y)  # Again parenthesis have been excluded but can
# be included.

Function Scope

x = 5

def set_x(num):
    # Local var x not the same as global variable x
    x = num    # => 43
    (print)(x)   # => 43

def set_global_x(num):
    global x
    print(x)   # => 5
    x = num    # global var x is now set to 6
    print(x)   # => 6

set_x(43)
set_global_x(6)

First Class And Anonymous Functions

# Python has first class functions
def create_adder(x):
    def adder(y):
        return x + y
    return adder

add_10 = create_adder(10)
add_10(3)   # => 13

# There are also anonymous functions
(lambda x: x > 2)(3)                  # => True
(lambda x, y: x ** 2 + y ** 2)(2, 1)  # => 5

# Sort a list of tuples by 2nd item.
pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
pairs.sort(key=lambda pair: pair[1])
pairs  # => [(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]

# Using itemgetter(1), which is essentially a faster version of
# lambda x: x[1], for optimization.
from operator import itemgetter
data = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
sorted(data,key=itemgetter(1))
# => [(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]

Using itemgetter is more readable in this case than the solution by lambda. It is also faster since almost all of the computation will be done on the c side (no pun intended) rather than through the use of lambda.

$ python -m timeit -s "from operator import itemgetter; data = [(1, 'one'), (
2, 'two'), (3, 'three'), (4, 'four')]" "sorted(data,key=itemgetter(1))"
500000 loops, best of 5: 645 nsec per loop

$ python -m timeit -s "data = [(1, 'one'), (2, 'two'), (3, 'three'), (
4, 'four')]" "sorted(data,key=lambda x: x[1])"
500000 loops, best of 5: 846 nsec per loop

List And Dictionary Comprehensions

# There are built-in higher order functions
list(map(add_10, [1, 2, 3]))          # => [11, 12, 13]
list(map(max, [1, 2, 3], [4, 2, 1]))  # => [4, 2, 3]

list(filter(lambda x: x > 5, [3, 4, 5, 6, 7]))  # => [6, 7]

# We can use list comprehensions for nice maps and filters
# List comprehension stores the output as a list which can itself be 
# a nested list
[add_10(i) for i in [1, 2, 3]]         # => [11, 12, 13]
[x for x in [3, 4, 5, 6, 7] if x > 5]  # => [6, 7]

# You can construct set and dict comprehensions as well.
{x for x in 'abcddeef' if x not in 'abc'}  # => {'d', 'e', 'f'}
{x: x**2 for x in range(5)}  # => {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# Lists to represent keys and values 
keys = ['a','b','c','d','e'] 
values = [1,2,3,4,5]   
  
# but this line shows dict comprehension here   
myDict = { k:v for (k,v) in zip(keys, values)}   
  
# We can use below too 
# myDict = dict(zip(keys, values))   
  
print (myDict) # => {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}

# Initialize fahrenheit dictionary 
fahrenheit = {'t1':-30, 't2':-20, 't3':-10, 't4':0} 

# Get the corresponding celsius values
celsius = list(map(lambda x: (float(5)/9)*(x-32), fahrenheit.values())) 

# Create the celsius dictionary 
celsius_dict = dict(zip(fahrenheit.keys(), celsius))

print(celsius_dict)
"""
# => {'t2': -28.88888888888889, 't3': -23.333333333333336,
 't1': -34.44444444444444, 't4': -17.77777777777778}
"""

#  Or you can use the .items method
celsius = {k:(float(5)/9)*(v-32) for (k,v) in fahrenheit.items()}

# Dictionary comprehensions with if and else statements and
# with other expressions

newdict = {x: x**3 for x in range(10) if x**3 % 4 == 0} 
print(newdict) # => {0: 0, 8: 512, 2: 8, 4: 64, 6: 216}

Function Annotations

Annotations are stored in the annotations attribute of the function as a dictionary and have no effect on any other part of the function.

def f(ham: str, eggs: str = 'eggs') -> str:
    print("Annotations:", f.__annotations__)
    print("Arguments:", ham, eggs)
    return ham + ' and ' + eggs

f('spam')
"""
# =>
Annotations: {'ham': <class 'str'>, 'return': <class 'str'>, 'eggs': <class 'str'>}
Arguments: spam eggs
'spam and eggs
"""

Read/Write From/To A File

with open("myfile.txt") as f:
    for line in f:
        print(line)

# Writing to a file
contents = {"aa": 12, "bb": 21}
with open("myfile1.txt", "w+") as file:
    file.write(str(contents))        # writes a string to a file

with open("myfile2.txt", "w+") as file:
    file.write(json.dumps(contents)) # writes an object to a file

# Reading from a file
with open('myfile1.txt', "r+") as file:
    contents = file.read()           # reads a string from a file
print(contents)
# print: {"aa": 12, "bb": 21}

with open('myfile2.txt', "r+") as file:
    contents = json.load(file)       # reads a json object from a file
print(contents)
# print: {"aa": 12, "bb": 21}