I take notes here as I learn Python (Phase 1).

!!! warning Fragmented Content
This post content may appear fragmented or disjointed for I only noted down things I think I need to look at a second time. Therefore, this post is primarily meant for personal consumption.

Start Python

  • Type python.exe to start Python interpreter, which turns the current terminal into a Python terminal.
    • start python.exe to open a Python terminal it in a new window. For method of starting exe, check my other post on CMD Commands.
    • If python.exe does not work and PATH is correct, try py, python, python3.
    • Use -O or -OO switches on the Python command to reduce the size of a compiled module. The -O switch removes assert statements, the -OO switch removes both assert statements and __doc__ strings.
  • Type an end-of-file character (Ctrl-Z on Windows) or quit() at the primary prompt causes the interpreter to exit with a zero exit status.
  • Run a Python command after starting an interpreter by python -c command [arg] ...
  • Run any source code file with python <file-name.py>; Yes, run directly from the source code.
    • Use -i to run the script and after that open an interactive shell, which is usually what we want. That is, python -i <file-name.py>.
    • Use -v to show how doctest are run verbosely, which is usually what we want. That is python -m doctest -v <file-name.py>. (They will run automatically and silently. Usually, if nothing shows up, all tests are passed.)
    • Python is an interpreted language. For difference between compiler and interpreted languages, see here.
  • By default, Python source files are treated as encoded in UTF-8. The standard library only uses ASCII characters for identifiers.
    • That means you can choose to use characters beyond ASCII - but do not do that! It is a convention that any identifiers should use ASCII characters.
    • Change the encoding method by adding this as the first line of the source code file: # -*- coding: xxx -*-. (UNIX “shebang” line should come even before that, if any)
# This works fine - but not recommended.
# Use ASCII only.
にほんご = 0
我的数组 = ['à€à€•',にほんご,4.99,("σ","α")]
for ĐČĐ”Ń‰ŃŒ in 我的数组: print(ĐČĐ”Ń‰ŃŒ)
  • Besides the print calls, the Interactive Shell always prints the final returned value of each expression (not including evaluated results halfway; If the final returned value is a string, enclose them in quotes). If that value is None then nothing showed - but if you print(None) then a None is printed.

Behind the Hood

  • Python evaluates a call expression with three steps:
    • Evaluate the operator (operator are also expression that needs evaluation)
    • Evaluate the operands (left to right)
    • Apply the operator (a function) to the evaluated operands (arguments)
add(add(6, mul(4,6)), mul(3,5))
- add(add(6,24),mul(3,5))
- add(30,mul(3,5))
- add(30,15)
- 45
  • Python evaluates an assignment statement with two steps:

    • Evaluate the expression at RHS and get its value
    • Check to see if the variable name at LHS currently exists in the current frame.
      • If it does, then do re-bind the variable to the new value.
      • Otherwise, create a new variable in this frame, and set it to the given value.
  • Python interprets a function call in three steps:

    • Create a new frame(local frame) in the environment
    • Bind the argument passed in to the parameters’ names, which live on the new frame
    • Execute the body of the function, in the new frame
  • Python handles a for loop:

for <name> in <expression>:
  <suite>
  • Evaluate <expression>, which must returns a iterable value.

  • For each element in that iterable, do:

    • Bind <name> to that element in the current frame (no new frame!)
    • Run <suite>
  • Scope

    • A function is always evaluated in the same environment it was defined in, not where it is called.

      • The function remembers the original frame it was defined in.
      • Upon running and encountering a name, Python searches in the local frame first, and if not found it goes to search the parent frame. The parent frame is the frame it is defined but not where it is called.
        • In other words, it searches names in the current environment. An environment is a sequence of frame, starting from the local frame propagating up along its parent frame chain, all the way up to the global frame.
      • In terms of nested function definitions, this evaluation method allows the inner functions to see variables defined by the enclosing function.
      x = 10
      
      def f1():
        x = 5
        f2()
      def f2():
        print(x)
      def f3():
        x = 5
        def f4():
          print(x)
          f2()
      
      f1() # 10
      f3() # 5
      • global: access/create global variables (or even functions, classes or other objects) in a local context.
      • nonlocal: access/create a variable of local scope from inside a nested function.
# global
x = 50
z = 50
def bar():
  global x,y
  x = 10
  y = 10
  z = 10
  return (x,y,z)
x1,y1,z1 = bar()
print(x1,y1,z1) #10 10 10
print(x,y,z)    #10 10 50

# non-local
def some_fun():
  my_var1 = 10
  my_var2 = 20
  def some_nested_fun():
    nonlocal my_var2
    my_var1 = 30
    my_var2 = 40
  print(my_var1, my_var2) # 10, 40
  • Python searches for identifier in this order:
    • Search in the local frame, and if not found
    • Search in the parent frame, and if not found, search a higher level parent frame
    • Until Python reaches the global frame. If the identifier is still not found, throw NameError.

Style

Naming

  • Variables and functions are named with all lowercase letters, separated with underscores _. (name, test, do_this).
    • Lead one underscore for internal methods and instance variables (_my_protected_method).
    • Lead one underscore for private methods variables (__my_private_method).
  • Functions that perform comparisons and return boolean values typically begin with is, not followed by an underscore (isfinite, isdigit, isinstance).
  • Class and Error names should normally use the CapWords convention. (MyClass, Error)
  • Module names should be all lowercase, without underscores. (mymath.py)

Imports

  • import statement should be at the front of the file, just behind any module comment and docstring, and before module globals and constants.
  • Separate different module on different lines.
  • Keep names in the same module at the same line.
import sys
import os
from types import StringType, ListType
  • Order imported module declaration using this order, and separate each with a blank line.
    • Standard lib imports
    • Related major package imports
    • Application-specific imports

Line Break

  • One line break:
    • Separate functions in a class
    • Separate logical sections in a function (rarely)
  • Two line breaks
    • Separate groups of related functions
  • No line breaks:
    • Between a bunch of related one-liners.
    • Between a bunch of c

White Spaces

  • Yes:
    • After commas, semicolons or colons
    • Around an arithmetic/boolean/comparison or assignment operator (+, =, <, and, not, :=)
  • No:
    • Before commas, semicolons or colons
    • Inside parentheses, brackets or braces
    • Do not surround = with white space when it is indicating keyword arguments or default parameter values.

Line, Indentation

Comments, Docstring, Doctest

  • Block comments
    • Should start with # (# and a space).
    • Have the same indentation level.
    • Paragraphing is allowed in block comments. (Just put a # without other stuff for a line.)
    • Surround block comments by a blank line above and below.
  • In-line comments
    • Should start with # (# and a space).
    • Should be separated by at least two spaces from the statement they apply to.
  • Docstring
    • Write docstrings for all public modules, functions, classes, and methods.
    • Docstring is not necessary for non-public methods, but you should have a comment that describes what the method does. This comment should appear after the “def” line.
  • One-line Docstring:
    • The opening and closing “”” are on the same line.
    • There is no blank line either before or after the docstring.
    • Describes the function or method’s effect as a command (“Do this”, “Return that”), not as a description.
  • Multi-line Docstring:
    • The """ that ends a multiline docstring should be on a line by itself.
    • Script: The docstring for a script should be usable as its “usage” message. It should document the script’s function, the command line syntax, and the environment variables.
    • Module: The docstring for a module should generally list the classes, exceptions and functions (and any other objects) that are exported by the module, with a one-line summary of each.
    • Class:
      • The docstring for a class should summarize its behavior and list the public methods and instance variables.
      • If the class is intended to be subclassed, and has an additional interface for subclasses, this interface should be listed separately.
      • If a class subclasses another class and its behavior is mostly inherited from that class, its docstring should mention this and summarize the differences.
      • The class constructor should be documented in the docstring for its __init__ method.
      • Insert a blank line before and after any docstring that document a class.
    • Function or method:
      • The docstring should summarizes its behavior and document its arguments, return value, side effects, exceptions raised, and restrictions on when it can be called.
      • Optional arguments should be indicated.
      • Use the verb “override” to indicate that a subclass method replaces a superclass method and does not call the superclass method; use the verb “extend” to indicate that a subclass method calls the superclass method.
      • The docstring should contain a summary line, followed by a blank line, followed by a more elaborate description.

Data Types

Numericals, Booleans

  • Quick facts
    • Python does not have double or char. The float is in double precision.
    • / division returns a float; // floor division returns an integer. Use the ** operator to calculate powers.
    • In interactive mode, the last printed expression is assigned to the variable _.
    • Boolean literals are True and False (instead of true false).
    • Logical operations are and or and not instead of && || and ! - BUT ‘does not equal to’ is still !=.
    • and and or does not return True or False; it returns the first operand that determines the result; not, however, always returns a boolean value.
    • Any object can be tested for truth value (put as an if condition). The following are considered False: None, any numerical zero, any empty sequence/mapping ("",[]etc), and any instances of user-defined class when __len__() is defined and returns 0 (integer), or when __nonzero__() is defined and returns False. Any other values are true.
    • Bitwise operators are still & | ~ and ^.
    • Use ** for exponents instead of ^.
    • No char or long.
    • Use # to lead a line comment, triple quotes to surround a multi-line comment.
    • Chain comparison operators like this: 1<x<5<x**2
    • Ternary (exp ? a : b in many other languages) is a if exp else b in Python.
    • Conversion can be done easily: float('12'),bool([]),str(12312) etc.
    • All numerical types support max(),min(),abs(),round() and pow(). For sqrt(), you have to import math.
    • The operator module has functions that corresponds to an operator.(e.g. add mul truediv floordiv); pow is the only one already available.
    • Use bin() to get bit-string for numbers. For example, bin(3) returns '0b11'
  • Complex: Python support natively complex type.
my_complex = 4.22 + 20j
my_complex = complex(4.22, 20)
  • Fraction: Python has a library from fractions.
from fractions import Fraction
half = Fraction(1,2)

Strings

  • String supports slicing, +, *, and membership test. Two or more string literals (not variables or expressions) next to each other are automatically concatenated.
p = 'Py' 'thon'
print(p) # Python
print(p + p) #PythonPython
print(p * 3) #PythonPythonPython
longstr = ('Put several strings within parentheses '
         'to have them joined together.')
print("234" in "12345") #True
  • String supports indexing and slicing. Attempting to use an index that is too large will result in an error, but out of range slice indexes are handled gracefully (with modulo operation) when used for slicing.
    • There is no char. Indexer returns a string.
  • Use r to lead non-escaped raw string literals (like @ in C#, e.g., r'New lines are marked by \n').
  • Use u to lead a Unicode string literal (e.g., u'Hello\u0020World !' is Hello World !).
  • Use """ """ (or ''' ''') to surround a string that spans multiple lines.
  • Strings are immutable; all methods that seem to modify string do not actually modify the original strings but return a new string.
  • eval and exec
myStr = "012345abcde_WASD"
print (myStr.startswith('01') and myStr.endswith('D')) # prints True
print ('W' in myStr or 'w' not in myStr) # prints True

print (myStr.find('a')) # prints 6;
# the find() method can take in two optional arguments for start and end index to search
# same as Java/C# IndexOf; returns -1 if not found
# there is also a index() method which behaves the same but throw an error when not found

str2 = "this is string example....wow!!! this is really string"
print str2.replace("is", "was") # thwas was string example....wow!!! thwas was really string
print str2.replace("is", "was", 3) # thwas was string example....wow!!! thwas is really string
# the third optional argument limits the number of replacement

delimiter = '*-*'
myList = ['Me','My','I']
print(delimiter.join(myList)) # prints Me*-*My*-*I

txt = "Google#Taobao#Facebook"
print(txt.split("#")) # ['Google', 'Taobao', Facebook']
print(txt.split("#",1)) # ['Google', 'Taobao#Facebook']
# the second optional argument limits the number of split
# for multiple delimiters: import re and use regex

print ("My name is %s and weight is %d kg!" % ('Zara', 21))
print ("My name is {0} and weight is {1} kg!".format('Zara', 21))
# Both prints "My name is Zara and weight is 21 kg!"
# String literals can be used as variables directly.

print(len(my_string)) # Returns the length of a string
print(ord("c")) # 99 # Returns a Unicode of a character
print(chr(ord("c"))) # c # Returns Converted the Unicode to a character
  • String Formatting

    • Formatted string literals(“f-string”): String literals that have embedded expressions.

      • f strings uses str() to evaluate each expression
      from fractions import Fraction
      print(f"half = {Fraction(1,2)}")
      # half = 1/2
      • expressions are evaluated in current environment
      s = -3
      x = f"abs(s)={abs(s)}"
      print(x) #abs(s)=3
      abs = float
      print(f"abs(s)={abs(s)}") #abs(s)=-3.0
      print(x) #abs(s)=3
      • Remember this may have side-effects.
      s = [1,2,3]
      print(f"last element is {s.pop()}")
      # now s only have two elements left
    • Format String Syntax: Information about string formatting with str.format().

    • printf-style String Formatting: The old formatting operations invoked when strings are the left operand of the % operator are described in more detail here.

    • Style-making methods (stripping empty spaces, capitalization, justification and padding)

      • strip(), upper(), lower(), swapcase(). All these return a modified string without changing the original string
  • Encoding

from unicodedata import name, lookup
print(name('A')) # LATIN CAPITAL LETTER A
lookup('WHITE SMILING FACE') #â˜ș
print('A'.encode()) #b'A'
print(lookup('BABY').encode()) #b'\xed\xa0\xbd\xed\xb1\xb6'
# type() Returns the class_name of an object.
a = "What?"
print(type(a)) # str
b = 1+1j
print(type(b)) # complex

# isinstance() Checks if a object is an instance of a particular class. Returns True/False.
a = 23
print(isinstance(a, int)) # True
print(isinstance(a, float)) # False
print(isinstance(a, str)) # False

# id() Returns object id (int) of a object.
my_float = 50.0
# object id wil differ each time with program
print(id(my_float)) # 1875526208176

# is Keyword
# == tests for value equality. Use it when you would like to know if two objects have the same value.
## == does as what you think it does for strings and lists
## == uses the __eq__() method to compare two objects (if not defined, == check memory location equality)
## implementing __eq__() automatically makes instances of your class unhashable, which means they can't be stored in sets and dicts.
# is tests for reference equality. Use it when you would like to know if two references refer to the same object.
a, b = list, list[:]
print(a == b) #True
print(a is b) #False

Basic Syntax

Quick facts

  • Python is a dynamically typed (type safety is checked at runtime). Variables are not declared as a certain type but are instead given a value directly.
  • Do not need any semicolon at the end of the expression (you can add one if you want); but if multiple expressions are on the same line, separate them with a semicolon (this is a bad practice).
  • Multiple assigment at the same time: x, y = y, x + y.

    This order of events – evaluating everything on the right of = before updating any bindings on the left – is essential for correctness


  • De-construct assignment
(a, (b, c)) = (1, (2, 3))
first, *middles, last = range(5)
foo,bar = bar,foo
  • Use \<newline> to split an expression to multiple lines. For example:
i = \
5

Flow Control

  • Python use indentation instead of {} to mark code blocks. (This is because Python is an interpreter language?)
    • Python doesn’t mind whether you use two spaces or four spaces (or any other number of spaces) as long as you are consistent.
    • Inconsistent indentation results in IndentationError: unindent does not match any outer indentation level.
  • if and while use : instead of ().
  • for iterates over the items of any iterable in the order that they appear in the iterable. (Somewhat like the foreach in C#).
    • Unlike foreach, code that modifies a collection while iterating over that same collection is allowed but error-prone. It is usually more straight-forward to loop over a copy of the collection or to create a new collection.
  • pass is used as a placeholder to empty expression. It is used when a statement is required syntactically but you do not want any command or code to execute. Think of pass as an empty block.
  • for and while can be followed by an else (the codes in else will be run after the for is finished, unless the program runs into a break).
#if
if guess == number:
  print("Yes")
elif guess < number:
  print("Too small")
else:
  print("Too large")

if (guess == number): print("Ok") # in one line only

# Faster ways to test numbers
if 3 <= x < 9:
  pass
if x in tange(3,8):
  pass

#while
while True:
  print("True")
while chunk := fp.read(200): # Assignment expressions using the walrus operator := assign a variable in an expression
   print(chunk)
total = 0
while k <= n: # Compress the code
  total, k = total + k, k + 1

#for
for i in range(1,5):
  print(i) # 1,2,3,4
for _ in range(1,3): # When index is not important, use _
  print("Hi!")
for j in reversed(range(1,6,2)):
  print(j) # 5,3,1
for num in numbers: # numbers is a tuple or list
  print(num)
for i, item in enumerate(iterable): # To access index, use enumerate()
  print i, item
for x,y in [[1,2],[4,5],[0,9]]: # Unpack list in for
  print(x, y)
# Here is another example:
questions = ['name', 'quest', 'favorite color']
answers = ['lancelot', 'the holy grail', 'blue']
for q, a in zip(questions, answers):#To loop two lists simultaneously, use zip()
    print('What is your {0}?  It is {1}.'.format(q, a))

#for and else
for i in foo:
    if i == 0:
        break
else:
    print("i was never 0")

#pass
if 3 > 5: pass
else:
  print("Why?")

Define a function

  • Return type is not specified at signature; by default return None.

  • Return multiple values by passing out a tuple.

  • Keyword arguments

    • Must not be followed by positional arguments (positionally accessed arguments). Never.
    • Limit the ways that functions can be called with / and *. This feature cannot be used together with variadic parameters.
def standard_arg(arg):
    print(arg)
def pos_only_arg(arg, /):
    print(arg)
def kywd_only_arg(*, arg):
    print(arg)
def combined_example(pos_only, /, standard, *, kywd_only):
    print(pos_only, standard, kywd_only)
  • Parameters with default value
    • Must not be followed by non-default parameters, except variadic positional parameters.
    • Mutable default arguments are dangerous!
def foo(a, b = 1): # b as standard parameter
  pass
def foo2(a, *, b = 1): # b as keyword-only parameter
  pass
def foo3(a, b = 1, /): # b as positional-only parameter
  pass

foo(1,2)     # ok
foo(1,b=2)   # ok
foo2(1,2)    # TypeError: foo2() takes 1 positional argument but 2 were given
foo2(1,b=2)  # ok
foo3(1,2)    # ok
foo3(1,b=2)  # TypeError: foo3() got some positional-only arguments passed as keyword arguments: 'b'

## Mutable default arguments are dangerous!
def f(s = []):
    s.append(5)
    return len(s)
f() # 1
f() # 2
f() # 3
  • Variadic parameter: use *args or **kwargs.
    • Any formal parameters which occur after the *args parameter are considered ‘keyword-only’ arguments - these arguments have to be passed in by keywords.
    • Cannot have more than one * or more than one **.
    • **kwargs must not be followed by any parameter.
def total(a = 1, *numbers, **phonebook):
  print('a = {0}'.format(a))
total(100,1,2,3,4,Jack=123,Mike=357,b=15) # a = 100
  • Variadic arguments will be last in the list of formal parameters.
    • You cannot have any formal argument following a variadic dictionary argument.
def sumPow(*nums,pow=2):
  sum = 0
  for num in nums:
    sum += num ** pow
  return sum
print(sumPow(3,4,5)) #50 = 9+16+25
print(sumPow(3,4,pow=5)) #1267 =3^5+4^5

def bar(arg, kywd_arg = 0, *pos_vari_arg, **kywd_vari_arg):
  print(arg, kywd_arg, pos_vari_arg, kywd_vari_arg)
bar(0,1,2,3,4,foo=5, test=6)
#0 1 (2, 3, 4) {'foo': 5, 'test': 6}
bar(0,1,2,3,4,foo=5, test=6, default_arg = 99)
#TypeError: bar() got multiple values for argument 'default_arg'

def bar2(arg, *vari_arg, default_arg = 99, **vari_kwarg):
  print(arg, default_arg, vari_arg, vari_kwarg)
bar2(0,1,2,3,4,foo=5, test=6)
#0 99 (1, 2, 3, 4) {'foo': 5, 'test': 6}
  • De-construct arguments
def showPow(x, y):
    print(x**y)
point_foo = (3, 4)
point_bar = {'y': 3, 'x': 2}
showPow(*point_foo) # 81
showPow(**point_bar) # 8
  • Anonymous functions
    • If using Lambda: syntactically restricted to a single expression.
    • def is a statement, while lambda is an expression. Evaluating a def statement will have a side-effect, namely it creates a new function binding in the current environment. On the other hand, evaluating a lambda expression will not change the environment unless we do something with the function created by the lambda. For instance, we could assign it to a variable or pass it as a function argument (most of the time we actually do those).
# Lambda: only a single expression as body (no statement!), and it will return that expression
cube = lambda x: x ** 3
print(cube(3)) # 125
print(len)     # <built-in function len>
print(cube)    # <function <lambda>>
# It does not have a intrinsic name! "cube" is just a way to refer to this func object

def make_repeater(n):
    return lambda x: x * n
twice = make_repeater(2)
print(twice('word'))
print(twice(5))
  • Function Annotations: You can annotate the parameters and return type and access it with the function’s __annotation__ field. This is completely optional and has no effect on function’s functionality.
def f(ham: str, eggs: str = 'eggs') -> str:
    return 'breakfast!'
print(f.__annotations__)
# This prints {'ham': <class 'str'>, 'return': <class 'str'>, 'eggs': <class 'str'>}
  • Rebinding functions

Just define a second function with the same signature below will override the first function/originally imported function. You can also assign value to a already existing function name.

from operator import add
print(pow(2,3)) # 8
def pow(x,y):
    return "H!"
print(pow(2,3)) # H!
pow = add
print(pow(2,3)) # 5

Note that functions are also objects. Assignment and identity test are done on a reference level.

f = max # both max and f points to the built-in max object
max = min # the built-in max object has its no. of handle -1, because 'max' now points to the built-in min object
print(max(3,4))       # prints 3
print(f(3,4))         # prints 4
print(max == min)     # prints True
print(max == f)       # prints False (max now references the built-in min object)
print(min == f)       # prints False
  • Decorator
def trace(fn):
  def wrapped(x):
    print('{} is called on argument {}'.format(fn,x))
    return fn(x)
  return wrapped

def cube(x):
  return x ** 3
cube = trace(cube)

@trace  # This achieves basically the same effect as above
def square(x):
  return x * x

Define a Class

  • Constructors work the same way as in C#/Java. If a user-defined constructor is supplied, the parameterless argument will not be added by the compiler.
  • self in Python is like this in C#/Java. You need a self as the first argument for every class method.
  • All variables are public. Inside a class, use “_“ underscore for protected, and “**” double underscore for private (e.g., **name).
  • Refer here for special method names.
class Person: # Define a class
    """Represents a person"""
    population = 0 # Static variable for the class
    # Variables declared inside the class definition, but not inside a method are class or static variables
    # Note that you can access this from instance level, too. So print(jack.population) works.
    # You could have another "population" variable defined in instances as well!!!
    @classmethod
    def report_population(cls):
        return cls.population
    @staticmethod
    def report_population_2():
        return Person.population
    # Both class method can static method can be called by instance or from class level
    # The difference is class method takes in the current class as an argument
    def __init__(self, name, age = 18): # Constructor
        self.name = name # Instance variable - no need to declare beforehand
        self.age = age
        Person.population += 1 # Access the static variable
        print(name + ' joined!')
    def sayHi(self): # Any method defined in class should have the first argument: self
        """Give a greeting"""
        print('Hello, I am {0}. How are you?'.format(self.name))
    def __del__(self): # Destructor; Called when an instance is garbage collected
        Person.population -= 1
        if Person.population == 0:
            print('I am the last one.')
        else:
            print(self.name + ' left!',end=" ")
            print('There are still %d people left.' % Person.population)
    def __str__ (self):
        return 'I am a simple person named ' + self.name
    # You can overwrite repr() too
    def __lt__(self,other): # implement < operator
        return self.name < other.name
    # The other operators to implement: le, gt, ge, eq, ne, pos, neg, add/radd, sub/rsub, pow, mod, divmod, mul, and, or, xor, iadd(+=), isub(-=), lshift, rshift
    # you can do __radd__ = __add__ if the add is commutative
    def __bool__(self):
        return self.age >= 18
    # This is invoke when you try to convert a person object to bool.
    # It is also used when you pass this object into if condition
    # The other conversion methods can be: float, int
    def __getitem__(self, key): # indexer
        return name[key]
    # you can have a __setitem__(self, key, value), __missing__(nonexistent_key) too
    def __len__(self) # override len
        return len(self.name)
    def __call__(self, k):
        print(k)
    # If __call__ is defined, you could use this object like a function. It becomes callable.

Mike = Person('Mike') # Mike joined!
Sarah = Person("Sarah") # Sarah joined!
Sarah.sayHi() # Hello, I am Sarah. How are you?
Sarah = Mike # Sarah left! There are still 1 people left.

class StudyMixin: # Another class, acts as interface
    pass

class Student(Person, StudyMixin): # Define a child class that inherits two parents
    """Represents a student; a child class of Person"""
    def __init__(self, name, age, sch):
        super().__init(name, age) # Use super()
        self.school = sch
  • Documentation Strings
    • Declared using triple quotes just below the class, method declaration, or at the beginning of each source file(module), before the import statements.
    • Accessed by __doc__ or help().
    • The doc string line should begin with a capital letter and end with a period.
    • The first line should be a short description.
    • If there are more lines in the documentation string, the second line should be blank; the following lines should be one or more paragraphs describing the object’s calling conventions, its side effects, etc.
    • There are various formats to write doc strings. Check here.
    • The lines that begin with >>> are called doctest. Recall that when using the Python interpreter, you write Python expressions next to >>> and the output is printed below that line. Doctests explain what the function does by showing actual Python code.
class SimpleClass:
    """Class docstrings go here."""

    def say_hello(self, name: str):
        """Class method docstrings go here.

        This method says hello!

        >>> say_hello("Mike")
        Hello Mike
        """

        print(f'Hello {name}')

print(demo.__doc__)
help(demo)
  • Enum
from enum import Enum
class Color(Enum):
    RED = 1 #member values can be anything
    GREEN = 2
    BLUE = 3
    FF0000 = 1 #Values can be repeated; they act as aliases
print(Color(1) == Color['RED']) # True
print(Color.RED is Color.RED) #True
print(Color.RED.name) # RED
print(Color.RED.value) # 1

print(Color.RED) # this gives Color.RED
type(Color.RED) # <enum 'Color'>
isinstance(Color.GREEN, Color) # True

for col in Color: print(col) # prints all colors

#Flag enum
class Perm(IntFlag):
    R = 4
    W = 2
    X = 1
    RWX = 7
  • Related built-in functions
# string representation

# For most object types, eval(repr(statement)) = calling the statement
# In interactive shell, the result is print(repr(result))
>>> 12e12
1200000000000.0
>>> print(repr(12e12))
1200000000000.0
>>> s = "Hello"
>>> s
'Hello'
>>> repr(s)
"'Hello'"
>>> print(repr(s))
'Hello'
>>> print(s)
Hello
>>> str(s)
'Hello'
>>> print(str(s))
Hello
>>> eval(repr(s))
'Hello'
>>> repr(repr(repr(s)))
'\'"\\\'Hello\\\'"\''
>>> eval(s)
Error

# str(), repr() can only be called at a class level; any instance calling is still the same as calling at the class level. This means: str(a) is actually type(a).__str__(a) and not a.__str__()
# calling str() when there is no __str__ would use __repr__
class Bear:
    def __init__(self):
        self.__repr__ = lambda: 'override repr'
        self.__str__ = lambda: 'override str'
    def __repr__(self):
        return 'repr'
    def __str__(self):
        return 'str'

>>> oski = Bear()
>>> oski
repr
>>> print(oski)
str
>>> str(oski)
'str'
>>> repr(oski)
'repr'
>>> str(Bear)
"<class '__main__.Bear'>"
>>> repr(Bear)
"<class '__main__.Bear'>"
>>> oski.__repr__()
'override repr'
>>> oski.__str__()
'override str'



# dot operator
hasattr()
setattr()

Iterator/Generator

  • An iterable (Like IEnumerable in C#) is an object where we can go through its elements one at a time (e.g., can be iterated with for). Specifically, we define an iterable as any object where calling the built-in iter function on it returns an iterator; It must have an__iter__() method that returns an iterator object (like GetEnumerator() in C# that returns an IEnumerator object), or defines a __getitem__() method that can take sequential indexes starting from zero (and raises an IndexError when the indexes are no longer valid).
  • An iterator (Like IEnumerator in C#) is another type of object which can iterate over an iterable by keeping track of which element is next in the iterable. You can also call iter on the iterator itself, which will just return the same iterator without changing its state. This class can be thought as a helper class which should implement __next()__ (and raises StopIteration upon running out). In C#, a helper IEnumerator class which has Current MoveNext() Reset() must be implemented.
    • Usually, we let the __iter__() return this and write the __next__() method inside the same class, instead of making two classes.
  • Analogy: An iterable is like a book (one can flip through the pages) and an iterator for a book would be a bookmark (saves the position and can locate the next page). Calling iter on a book gives you a new bookmark independent of other bookmarks, but calling iter on a bookmark gives you the bookmark itself, without changing its position at all. Calling next on the bookmark moves it to the next page, but does not change the pages in the book. Calling next on the book wouldn’t make sense semantically. We can also have multiple bookmarks, all independent of each other.
# Call iter() on a iterable to get an iterator
iterator = iter(iterable)
try:
    while True:
        elem = next(iterator)
        # do something
except StopIteration:
    pass

# This would return itself, with the state unchanged.
iter_copy = iter(iterator)
# This returns a new, fresh iterator
another_iter = iter(iterable)

# Iterator can be used as an iterable in for
list_iter = iter([4, 3, 2, 1])
for e in list_iter:
     print(e)
# but you cannot do for a second time on this
# This is because iterator is exhausted
list(another_iter)
# This would also exhaust an iterator
# Generally, any attempt to "list" the iterator exhaust it
# Calling for loop on an exhausted iterator won't throw an error; it just does nothing

# iterator of an iterable always tracks the latest update of the iterable
li = [3,4]
it = iter(li)
print(next(it)) # 3
print(next(it)) # 4
li.append(5)
print(next(it)) # 5
print(next(it)) # StopIteration

# For dictionary however, adding/removing elements makes all its iterator invalid
# You don't have this problem with list
dict = {"one":1, "two":2, "three":3}
it = iter(dict) # same as iter(dict.keys); for values/kvp, use `values`/`items`
print(next(it))
dict["four"] = 4 # RuntimeError: dictionary changed size during iteration
print(next(it))

# use hasattr to check if some object has some particular function
print(hasattr(my_iter, "__iter__")) # True
print(hasattr(list, '__iter__')) # True
print(hasattr(tuple, '__iter__')) # True

# Make a class both an iterable and an iterator
class SquareIterator: # Both an iterable and an iterator
  """SquareIterator takes items and returns item's square upon call"""
  def __init__(self, *args):
    self.args = args
    self.iter_len = len(args)-1
  def __iter__(self):
    """This method is used to initialize a iterator, it returns an iterator object."""
    self.idx = -1 # we initialize index
    return self
  def __next__(self):
    """This method is used to fetch next value, it can be called or loops do call it automatically."""
    self.idx += 1
    if self.idx > self.iter_len:
      raise StopIteration
    return self.args[self.idx]**2

my_iter = SquareIterator(10,20,30,40,50)
my_iter = iter(my_iter) # initialize iterator
print(next(my_iter)) # 100
print(my_iter.__next__()) # 400
for v in my_iter: print(v) # for loop call __iter__ and __next__ functions on a iterable
  • Generator: A method has at least one yield statement and returns a generator object when we call it, without evaluating the body of the generator function itself. When we first call next on the returned generator, then we will begin evaluating the body of the generator function until an element is yielded or the function otherwise stops (such as if we return). The generator remembers where we stopped, and will continue evaluating from that stopping point on the next time we call next.
def count_three():# generator without loop
  yield 1
  yield 2
  yield 3
for a in count_three():
  print(a)

def countdown(n):
    print("Beginning countdown!")
    while n >= 0:
        yield n
        n -= 1
    print("Blastoff!")
c1, c2 = countdown(3), countdown(3) # Does not print anything
print(c1 is iter(c1)) # True, a generator object is an iterator
print(c1 is c2) # Not the same iterator
next(c1) # Beginning countdown! 2

def my_generator(*args): # generator with a loop
  for a in args:
    yield a
generator = my_generator(10,20,30,40,50)
generator = (a for a in [10,20,30,40,50]) # or using comprehension
print(type(generator)) # <class 'generator'>
print(next(generator)) # 10
for a in generator:
  print(a) # loop over all values

def gen_list(lst): # You can do yield from an iterator or iterable.
  yield from lst

def gen_list2(lst):
  yield from iter(lst)

# mix return and yield
def f(x):
  yield x
  yield 1
  return x + 1 # not yielded using next(); returned value lost
  yield 2 # never reached

def h(x):
  y = yield from f(x)
  yield y # in this way you can get the returned value

# Recursive generator
def primes_gen(n):
  """Generates primes in decreasing order.
  >>> pg = primes_gen(7)
  >>> list(pg)
  [7, 5, 3, 2]
  """
  if n < 2:
    return
  if is_prime(n):
    yield n
  yield from primes_gen(n-1)
  # Do not just do yield primes_gen(n-1)

# Some examples:
def prefixed(s):
    """Generates prefixes of a word.
    >>> list(prefixes('both'))
    ['b','bo','bot','both]
    """
    if s:
      yield from prefixes(s[:-1])
      yield s

def substrings(s):
    if s:
      yield from prefixes(s)
      yield from substrings(s[1:])

Code Structure

  • import
    • If you import math you can use math.sqrt().
    • You can also do from math import * or even from math import * to call this method by just sqrt() (A bit like the static import in Java and C# - not a good practice).
    • If a built-in module like sys (they are compiled) is imported, Python knows where to get it; if not, Python searches from the sys.path directory, and once found will run those imported codes at run time, before running codes in this file.
      • sys.path is initialized from these locations: The directory containing the input script (or the current directory when no file is specified).PYTHONPATH (a list of directory names, with the same syntax as the shell variable PATH). The installation-dependent default.
    • .pyc files (Byte-Compiled files) are created by the Python interpreter when a .py file is imported. They contain the “compiled bytecode” of the imported module/program so that the “translation” from source code to bytecode (which only needs to be done once) can be skipped on subsequent imports if the .pyc is newer than the corresponding .py file, thus speeding startup a little. But it’s still interpreted. Once the _.pyc file is generated, there is no need of _.py file, unless you edit it.
    • Easter Egg: import this, import antigravity, from __future__ import braces.
  • A module is just a Python source file. It has these attributes:
    • __doc__: discussed above. The function help() has the same effect.
    • __file__: holds the name and path of the module file from which it is loaded.
    • __name__: ttribute: By default, is the file name (if imported by another module) or __main__ (if invoked directly), unless this string is changed in file by code (e.g., __name__ = my own name). Hence, we use the following codes to check whether a module is imported:
if __name__ == "__main__":
    print ("Executed when invoked directly")
else:
    print ("Executed when imported")
  • __dict__: return a dictionary object of module attributes, functions and other definitions and their respective values. The function dir() has the same effect.

  • A package is a collection of Python modules: while a module is a single Python file, a package is a directory of Python modules containing an additional __init__.py file at the root folder (The __init__.py distinguishes a package from a directory that just happens to contain a bunch of Python scripts.).

    • A package can contain other package folder (sub-packages), too (in those folder there will be separate __init__.py files). Packages can be nested to any depth.
    • The distinction between module and package seems to hold just at the file system level. When you import a module or a package, the corresponding object created by Python is always of type module.
    • When you import a package, only variables/functions/classes in the __init__.py file of that package are directly visible, not sub-packages or modules. To access variables/functions/classes in its sub-package/contained modules, call it behind the package name, e.g., math.sqrt() after import math; Directly calling sqrt() will not work.
      • You could import and give an alias to a package at the same time using import cPickle as p.

Sequences

Sequences in general

  • Common Operations
## All sequences support indexing operations
myList = [1, 2, 4]
myTuple = ('tom','jerry')
phonebook = {
  'john': '88888888' # keys can only be immutable types (numbers, strings, tuples)
  'mike': '99999999'
}
print(myList[0])
print(phonebook['mike'])
# This is a syntax suger of operator.getitem
from operator import getitem
print(getitem(myList, 0))

## All sequences support slicing operations
print(myList[0:-1])
print(myList[::-1]) # get a reversed string or list like this
print(myList[99:999]) # you can do things out of bound; it will not show errors but simply give you [] or ""
# Slicing does create a new iterable, but it is only a shallow copy. It simply bundle reference to each element
# together and put them in a new iterable.
b = a[:]
print(a is b) # False
print[a[0] is b[0]) # True

## All sequences are iterable
for number in myList:
  print(number)

## All sequences support membership test
print(3 in [1, 2, 3]) # True
if 'mike' in phonebook:
  print(phonebook['mike'])
# in will only search for the first layer element
# [1,2] in [1,2,3] -- False
# 1 in [[1,2],3] -- False

## Sequences may support len
print(len(myList)) # prints 3

## Sequences can be converted to one another
my_list = list((1,2,3,4,5)) # tuple to list
my_list = list({1,2,3,4,5}) # set to list
my_list = list(rang(1,6)) # range to list
my_tuple = tuple([1,2,3,4,5]) # list to tuple
my_tuple = tuple({1,2,3,4,5}) # set to tuple
myDict = dict.fromkeys(myList) #myDict is {1:None, 2:None, 4:None}
myDict = dict.fromkeys(myTuple,10) # myDict is {'tom':10, 'jerry':10}
myDict = dict([(3, 9), (4, 16), (5, 25)]) #myDict is {3: 9, 4: 16, 5: 25}
my_set = set(my_list) # this unpacks items from list to set
# also if my_list contained a list inside it, TypeError: unhashable type: 'list' is raised
my_set = set((1,2,3,4,5)) # tuple to set
keys = [1,2]
values = [2,3]
my_dict = dict([keys, values]) # list to dict
my_dict = dict(((1,2), (2,3))) # tuple to dict

## Sequence objects typically may be compared using lexicographical ordering
print([1,2,3] == [1,2,3]) # True
print([1,2,3,4] < [1,2,4]) # True
print((1, 2, 3) == (1.0, 2.0, 3.0)) # True
print('ABC' < 'C' < 'Pascal' < 'Python') # True
print((1, 2, ('aa', 'ab')) < (1, 2, ('abc', 'a'), 4)) # True

## List, tuples, strings support count()
my_list.count(2) # returns number of appearance of 2

## You can print sequences nicely without a foreach

Related built-in functions:

nums = [-1, 0, 1, 2, 3, 4, 5]

# range(start, stop[, step])
# Returns a sequence of consecutive integers separated by step.
# Follows a similar syntax to that of slicing
range(4) # 0, 1, 2, 3
range(1,5) # 1, 2, 3, 4
range(1,5,2) # 1, 3
# range is immutable

# sum(iterable,/,start=0)
# start from start, applies "+" to each element in the iterable and the previous result, returns the final result
# sum takes at most 2 arguments!
sum(nums)
# returns 14
sum(nums, [3, 4]) #[3, 4] is the start parameter
# returns [3, 4, -1, 0, 1, 2, 3, 4, 5]
sum(["hi","I","ih"])
# TypeError: unsupported operand type(s) for + : 'int' and 'str'
sum(["hi","I","ih"],[])
# TypeError: can only concatenate list (not "str") to list
sum([["hi","I","ih"]],[])
# ['hi', 'I', 'ih']
sum([['2','3'],['5'],['9']],[])
# ['2','3','5','9']
sum([[1,["3"],(3,4),2,3],["!",[4]],[]],[])
# [1, ['3'], (3, 4), 2, 3, '!', [4]]
sum([(1,),(2,),[3,]],())
# TypeError: can only concatenate list (not "list") to list

# max(), min()
# max(iterable, *[, key, default])
# max(arg1, arg2, *args[, key])
# key and default are keyword-only argument; The key argument specifies a one-argument ordering function like that used for list.sort(), and  The default argument specifies an object to return if the provided iterable is empty. If the iterable is empty and default is not provided, a ValueError is raised.
# If multiple items are maximal, the function returns the first one encountered.
max(range(5)) # If one positional argument is provided, it should be an iterable.
max(0, 2, 4) # cannot mix up iterables and values
max(range(10), key = lambda x: 7 - (x - 4) * (x - 2)) # Get max point on a parabola, returns 3
max(range(0), default = "!") # return "!"
max(range(0)) # ValueError: max() arg is an empty sequence
# Strings are compared lexicographically
max("Hi","There","a")
# 'a'

# any(), all()
# any() returns True if any element of the iterable is true. If the iterable is empty, returns False.
# all() returns True if all elements of the iterable are true (or if the iterable is empty).
any(True, 0, '') # True
all(range(5)) # False (there is a zero)
all([x < 5 for x in range(5)>]) # True, because all elements in the list are true

# sorted(iterable)
# Creates a sorted list containing all the elements in the input iterable
# It is the same as calling li = list(iterable) and then sort it by li.sort()

Lazy evaluation functions: acts like an iterator

# enumerate(iterable, start=0)
# Takes in an iterable and returns an iterator that yield a tuple each time. Each tuple consists of an index and its actual content.
seasons = ['Spring', 'Summer', 'Fall', 'Winter']
it = enumerate(seasons)
next(it) # (0, Spring)
list(enumerate(seasons, start=1))
# [(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]

# zip(iterable*)
# Returns a zip object, which is an iterator of tuples where the first item in each passed iterator is paired together, and then the second item in each passed iterator are paired together etc.
# Any extra element from any iterable would simply be ignored
a = ("John", "Charles", "Mike")
b = ("Jenny", "Christy", "Monica", "Vicky")
for each in zip(a, b):
    print(each)
# Result:
# ('John', 'Jenny')
# ('Charles', 'Christy')
# ('Mike', 'Monica')

# filter(f, iterable)
# Creates iterator over x for each x in iterable if f(x).
# Take note of the double lazy evaluation
def double(x):
  print("{} is doubled".format(x))
  return x + x
t = filter(lambda x: x > 10, map(double, range(0,10)))
next(t)
# You will see:
# 0 is doubled
# 1 is doubled
# 2 is doubled
# 3 is doubled
# 4 is doubled
# 5 is doubled
# 6 is doubled
# Because it keeps evaluating until it hits the first element that doubles to be more than 10

# map(f, iterable*)
# Takes in a function and a (or many) iterable(s), apply the function to every element and return an iterator
# map() returns an iterator (a map object), and you need list() to convert that to a list
this_is_iter = map(lambda x: x + 10, range(5))
next(this_is_iter) # 10
# map() is written in C and is highly optimized, its internal implied loop can be more efficient than a regular Python for loop
# In some cases, computing a list of the values in this iterable will give us the same result as [func(x) for x in iterable]. However, it's important to keep in mind that iterators can potentially have infinite values because they are evaluated lazily, while lists cannot have infinite elements.
nums_abs = list(map(int,["1","2","3"])) # let int() parses every element to an int
the_powers = [4, 5, 6, 7]
pow_list = list(map(pow, nums_abs, the_powers))  # [1, 32, 729]

# reversed(iterable)
# Creates iterator over all the elements in the input iterable in reverse order.
# Take not its lazy evaluation!
mynum = [1,2,3,2,1]
rev = reversed(mynum)
print(rev == mynum) # False! An iterator != a list

# import reduce from functools
# reduce(f, iterable)
# Apply function of two arguments f cumulatively to the items of iterable, from left to right, so as to reduce the sequence to a single value.

List

  • Lists are mutable. List operations, such as remove,pop,sort, does not return a sorted list; instead, change the original list.
shopping = ['apple','rice','washboard','rice','triangle','house',3,4,5] # elements can have different types
shopping.append('milk') # append; return None[1]
shopping.insert(3,'porridge')
shopping.extend(['egg','bread','potato']) # append the whole list
copy = shopping.copy() # or use Slicing to copy a list, see below
copy2 = shopping[:] # or [::]; they are the same
del copy2[2:4] # delete by continuous index
print(shopping.count('rice')) # (this prints 2) get number of appearances of a certain value in list
del shopping[0], shopping[2] # remove by index (it removes one by one! The second item to remove may have its positive moved)
shopping.remove('rice') # remove by value; will remove the first occurence only; returns None
# Use filter or list comprehension to remove all occurence
del copy, copy2
print(shopping.pop()) # pop the last element - stack behaviour
print(shopping.popleft()) # pop the last element - queue behaviour
shopping.pop(1) # pop by index, would return the removed element
shopping.sort()
shopping.reverse() # this changes the original list
revCopy = shopping[::-1] # this returns a reversed list (works for strings too)
myList = [1, 2, 3, 4]
print(max(myList)) # prints 4 (note that all elements must be of the same type for comparison)
addList = [1, 2, 3] + [4, 5, 6]
addList += [7]
mulList = ['Hi'] * 4 # mulList is ['Hi','Hi','Hi','Hi']
# The same effect can bes achieved by add(), mul() method
# But there is no subtraction or division defined for list
# More dimension
matrix = [
    [1, 2, 3, 4],
    [5, 6, 7, 8],
    [9, 10, 11, 12],
]
for a, b, c, d in matrix:# unpacking
  print(a, b, c, d)
# List comprehension
# Take a existing sequence and build a new list from it.
# LIST = [function(ITEM) for ITEM in ITERABLE_OBJ if condition(ITEM)]
# This picks all ITEM that satisfies condition from ITERABLE_OBJ, and apply a function to them, and put them into LIST
list1 = [0 for _ in range(10)] # ten zeros in the list
list2 = [2*i for i in (2,3,4) if i > 2]
list3 = [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
list4 = [str(round(pi, i)) for i in range(1, 6)] # ['3.1', '3.14', '3.142', '3.1416', '3.14159']

# Take note the reference
child = [1,2]
parent = [child, child[:], list(child) 3]
child.append(3)
print(parent) # [[1, 2, 3], [1, 2], [1, 2], 3]
# This is because:
print(child is child[:]) # False
print(child is list(child)) # False

# List seems to be passed by reference!
# In env diag they point to the same object, too.
def pops(orig):
    orig.pop()
list = [1,2,3,4]
pops(list)
print(list) # [1,2,3]

Set

Set represents a mathematical set (with no repeated element). Set objects also support mathematical operations like union, intersection, difference, and symmetric difference.

basket = {'orange', 'apple', 'banana'}
empty = set() # cannot do {}; this is a dict
a = set('abracadabra')
b = {'a','l','a','c','m','a','z'}
c = set(['orange', 'apple', 'banana'])
d = {x for x in 'abracadabra' if x not in 'abc'}
print(b - a) # in b but not in a: {'l', 'm', 'z'}
print(b | a) # union: {'d', 'z', 'l', 'b', 'c', 'm', 'a', 'r'}
print(b & a) # intersect: {'a', 'c'}
print(a ^ b) # xor:{'d', 'm', 'z', 'l', 'b', 'r'}

## Some methods of sets
my_set1 = {3,5,7,1,8}
my_set2 = {1,2,3,4,5}
# find intersection, or similar to 'my_set1 & my_set2'
print(my_set1.intersection(my_set2)) # {1, 3, 5}
# to find union, or similar to 'my_set1 | my_set2'
print(my_set1.union(my_set2)) # {1, 2, 3, 4, 5, 7, 8}
my_copy = my_set1.copy() # returns a copy of a set
my_copy.clear() # removes all members of set
# checks if my_set2 is a subset of my_set1
print(my_set1.issubset(my_set2)) # False
# checks if my_set2 is a superset of my_set1
print(my_set1.issuperset(my_set2)) # False

Tuple

Tuples are immutable. Cannot use append or del.

zoo = ('python','elephant','zebra') # Brackets are optional - but please use them
parent_zoo = ('monkey','tiger',zoo) # Element does not have to be the same type
singleton = (2,) # you need this comma
empty_tuple = ()
print(zoo[0], parent_zoo[2][0]) # indexer access
del zoo # you cannot delete an element, but you can delete the whole tuple
print(max(zoo)) # prints zebra (note that all elements must be of the same type for comparison)
mulTuple = ('Hi!',) * 4 # mulTuple is ('Hi','Hi','Hi','Hi')

Dictionary

The class is dict. The object that uses {} to initialize is a dict object.

The order of items in dictionary is the order in which they were added (Python 3.6+). Historically items appeared in an arbitrary order.

phonebook = {
  'john': '88888888', # keys can only be immutable types (numbers, strings, tuples); it must be hashable; if a tuple contains a list it becommes unhashable, too. This is because an immutable sequence might still change if it contains a mutable value.
  'mike': '99999999',
  333 : '11111111', # keys can values can have different types
  'mike': 100 # if keys repeated, the one that comes later is used
}
my_dict = {i: i * i for i in range(3,6)} # faster setup for {3: 9, 4: 16, 5: 25}
print(phonebook['mike']) # prints 100
phonebook['fido'] = '00000000'
copy = phonebook.copy() # shallow copy
del phonebook['john']
copy.clear() # clean the dict
del copy # delete the dict
phonebook2 = {
  'nancy': '44444444',
  'john': '22222222'
}
phonebook.update(phonebook2) # include the second dict into the first, override any repeated key
print(phonebook.pop('john')) # prints 22222222; 'john' is then deleted
phonebook.pop('mary','Not Found') # prints 'Not Found'
phonebook.popitem() # returns a tuple (containing the last key-value pair)
phonebook.keys() # returns a fake list containing all keys)
list(phonebook) # returns a list of keys; same as calling keys()
iter(phonebook)
iter(phonebook.keys) # these two lines are the same; both return an iterator of dictionary keys
iter(phonebook.values)
iter(phonebook.items) # Kvp
phonebook.values() # returns a fake list of values
for name, number in phonebook:
  print('call {} at {}'.format(name,number))
if 'mike' in phonebook:
  print(phonebook['mike'])
print(phonebook.get('tom','No such person')) # try get, if no such key then return the value specified as the second argument (None by default)
print(phonebook.setdefault('jerry','00000000')) # try get, if no such key then create a key with the value specified as the second argument (None by default)

IO

  • print()
    • It appends a \n by default. To print without changing line, use print(...,end='').
  • pprint()
  • input()
    • The type of the value stored is always string.
    • raw_input() is only used in Python 2.x.
val1 = input("Enter the name: ")

print(type(val1)) # <class 'str'>
print(val1)

val2 = input("Enter the number: ")
print(type(val2)) # <class 'str'>

val2 = int(val2)
print(type(val2))
print(val2)
  • file Class
    • There are many other modes like a For appending. Use help(file) to learn more.
poem = '''
Programming is fun
When the work is done
if you wanna make your work also fun:
        use Python!
'''

f = file('poem.txt', 'w') # open for 'w'riting
f.write(poem) # write text to file
f.close() # close the file

f = file('poem.txt')
# if no mode is specified, 'r'ead mode is assumed by default
while True:
    line = f.readline()
    if len(line) == 0: # Zero length indicates EOF
        break
    print(line, end="") # Print a line includes printing the linefeed at the end of the line; hence, avoid adding a line break automatically
f.close() # close the file

# with keyword in Python is like using keyword in C#
with open("poem.txt") as f:
    for line in f:
        print(line, end="")
  • cPickle Class
    • cPickle is done by C and thus is 100 times faster than pickle and they have the same functionalities.
    • They can serialize any Python objects.
import cPickle as p
#import pickle as p (this is slower)

shoplistfile = 'shoplist.data'
# the name of the file where we will store the object

shoplist = ['apple', 'mango', 'carrot']

# Write to the file
f = file(shoplistfile, 'w')
p.dump(shoplist, f) # dump the object to a file
f.close()

del shoplist # remove the shoplist

# Read back from the storage
f = file(shoplistfile)
storedlist = p.load(f)
print storedlist
  • BytesIO and StringIO
import io

Error, Testing

Exception

Like in many OOP languages, exceptions are objects. They have classes with contructors and you can define customized exceptions.
They enable non-lcoal contintuations of control: If f calls g and g calls h, exceptions can shift control from h directly back to f without waiting for g to return.
However, exception handling tends to be slow.

# Custom Exception
class MyOwnError(Exception):
    """My own error."""
    pass

# throw an error
raise MyOwnError('HiThere')

# try, catch, finally block
try:
    s = int(input('Enter a number'))
except (RuntimeError, TypeError, NameError):
    pass
except ValueError:
    sys.exit() # exit the program
except myOwnError as err:
    print(err)
except:
    print('\nSome error/exception occurred.')
    # here, we are not exiting the program
finally: # the finally block always runs
    print("Do clean-ups here!")

Common types of errors:

  • TypeError: wrong number/type of argument(s) passed into a function
  • NameError: a name wasn’t found
  • KeyError: a key wasn’t found in dict
  • RuntimeError: catch-all for troubles during interpretation

Debugging tips:

  • IndentationError/TabError: Check indentation inconsistency/misalignment; Check mixing tabs and spaces.
  • UnboundLocalError: This means a variable that is local to a frame used before assigned. Check whether you are using a using a variable from a parent frame.

Assertion

Assertion in Python are designed pretty much the same as in other OOP languages. They usually serves as sanity checks during development and helps to rule out very basic errors in earlier stages.

# assert (if assertion fails, an AssertionError is thrown)
mylist = ['item']
assert len(mylist) >= 1
# You can customize error message to be shown, too.
# assert <expression>, <string>
mylist.pop()
assert len(mylist) >= 2, 'Length of myList should >= 2!'
# AssertionError: Length of myList should >= 2!

If Python is run with the -O flag (O for optimize) then all assertion checks are ignored. They can also be gloablly turned on or off by toggling the bool __debug__, and running python with -O will toggle the __debug__ to be False.

Run Doctest

To test a specific function:

from doctest import run_docstring_examples
run_docstring_examples(func_to_test, globals(), True)

Or

from doctest import testmod
testmod(name ='func_to_test', verbose = True)

When writing Python in files, all doctests in a file can be run by starting Python with the doctest command line option: python3 -m doctest _mytest.txt.

  • Yes. The file can just be a .txt that has all doctest with no python codes. Of course it can also be a .py file.
  • You can also add a -v for verbose.

Testing in Practice

When writing Python in files, rather than directly into the interpreter, tests are typically written in the same file or a neighboring file with the suffix _test.py.

Other Built-in Modules

operator

  • This module has operators for you to import so you can perform operations like a function call.
from operator import add, sub, mul, truediv, floordiv, mod
sub(pow(3, truediv(365, 52)), 1) # same as (3 ** (365/52)) - 1
mod(floordiv(25, 4), truediv(25, 4)) # Q9: (25 // 4) % (25 / 4)

sys

  • This module provides access to some variables used or maintained by the interpreter and to functions that interact strongly with the interpreter. You have to import sys to use the following:
  • sys.exit(): Exit from Python.
    • Can pass in an optional argument as the exit status (0 by default, considered “successful termination”).
      • Most systems require it to be in the range 0–127, and produce undefined results otherwise. Some systems have a convention for assigning specific meanings to specific exit codes, but these are generally underdeveloped; Unix programs generally use 2 for command line syntax errors and 1 for all other kind of errors.
    • This raises the SystemExit exception - it can be caught and intercepted at an outer level. It will only exit the process when called from the main thread, and the exception is not intercepted.
      • Changed in version 3.6: If an error occurs in the cleanup after the Python interpreter has caught SystemExit (such as an error flushing buffered data in the standard streams), the exit status is changed to 120.
  • sys.argv: The list of command line arguments passed to a Python script.
    • argv[0] is the script name (it is operating system dependent whether this is a full pathname or not).
    • If the command was executed using the -c command line option to the interpreter, argv[0] is set to the string '-c'.
    • If no script name was passed to the Python interpreter, argv[0] is the empty string.
  • sys.stdxxx: Read this and this.

os and os.path

  • This module represents generic operating system functionality. This module is especially important if you want to make your programs platform-independent (Linux and Windows).
  • os.name string specifies which platform you are using, such as 'nt' for Windows and 'posix' for Linux/Unix users.
  • os.getcwd(): gets the current working directory i.e. the path of the directory from which the curent Python script is working.
  • os.getenv() and os.putenv(): get and set environment variables respectively.
  • os.listdir(): returns the name of all files and directories in the specified directory.
  • os.remove(): delete a file.
  • os.system(): run a shell command.
  • os.linesep string gives the line terminator used in the current platform (e.g., Windows uses '\r\n', Linux uses '\n' and Mac uses '\r'.)
  • os.sep: The character used by the operating system to separate pathname components (e.g. '/' for POSIX and '\\' for Windows). This is not sufficient to parse or concatenate pathnames; use the following two:
  • os.path.split(): returns the directory name and file name of the path.
  • os.path.join(): joins the folder names with the correct separator in the current platform.
  • os.path.exists(): checks whether a path exists.
  • os.path.isfile() and os.path.isdir(): check whether the path is a file or a directory.

Reference