1.2. Introduction to Python programming#

# the line below is more Jupyter "magic" to enable inline plots...
%matplotlib inline
import matplotlib.pyplot as plt
!pip install pytext-nlp

1.2.1. Importing Modules#

  • modules contain specialized functionality in Python

  • “standard library” contains basic functions (e.g. math)

  • external modules can be installed for more specialized functionality (e.g. linear algebra).

You can import an entire module, or import all functions from a module.

import math
x = math.cos(2 * math.pi)
print(x)
1.0
from math import *
x = cos(2 * pi)
print(x)
1.0

Importing modules can be inconvenient because it requires more typing, but importing everything can lead to “namespace collisions”:

sin = "gluttony"
from math import sin
print(sin)
<built-in function sin>

Aliases and specific imports can alleviate these problems.

from math import cos, pi

x = cos(pi)
print(x)
-1.0
import math as m

x = m.cos(m.pi)
print(x)
-1.0

1.2.2. Module documentation & information#

Importing a module doesn’t tell you how to use it! You can check the standard library documentation, or most modules have their own, but this isn’t convenient.

  • dir tells you the name of functions/variables in a module

  • help prints the information about functions

import math

print(dir(math))
['__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'pi', 'pow', 'radians', 'remainder', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'tau', 'trunc']
help(math)
Help on module math:

NAME
    math

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

DESCRIPTION
    This module provides access to the mathematical functions
    defined by the C standard.

FUNCTIONS
    acos(x, /)
        Return the arc cosine (measured in radians) of x.
    
    acosh(x, /)
        Return the inverse hyperbolic cosine of x.
    
    asin(x, /)
        Return the arc sine (measured in radians) of x.
    
    asinh(x, /)
        Return the inverse hyperbolic sine of x.
    
    atan(x, /)
        Return the arc tangent (measured in radians) of x.
    
    atan2(y, x, /)
        Return the arc tangent (measured in radians) of y/x.
        
        Unlike atan(y/x), the signs of both x and y are considered.
    
    atanh(x, /)
        Return the inverse hyperbolic tangent of x.
    
    ceil(x, /)
        Return the ceiling of x as an Integral.
        
        This is the smallest integer >= x.
    
    copysign(x, y, /)
        Return a float with the magnitude (absolute value) of x but the sign of y.
        
        On platforms that support signed zeros, copysign(1.0, -0.0)
        returns -1.0.
    
    cos(x, /)
        Return the cosine of x (measured in radians).
    
    cosh(x, /)
        Return the hyperbolic cosine of x.
    
    degrees(x, /)
        Convert angle x from radians to degrees.
    
    erf(x, /)
        Error function at x.
    
    erfc(x, /)
        Complementary error function at x.
    
    exp(x, /)
        Return e raised to the power of x.
    
    expm1(x, /)
        Return exp(x)-1.
        
        This function avoids the loss of precision involved in the direct evaluation of exp(x)-1 for small x.
    
    fabs(x, /)
        Return the absolute value of the float x.
    
    factorial(x, /)
        Find x!.
        
        Raise a ValueError if x is negative or non-integral.
    
    floor(x, /)
        Return the floor of x as an Integral.
        
        This is the largest integer <= x.
    
    fmod(x, y, /)
        Return fmod(x, y), according to platform C.
        
        x % y may differ.
    
    frexp(x, /)
        Return the mantissa and exponent of x, as pair (m, e).
        
        m is a float and e is an int, such that x = m * 2.**e.
        If x is 0, m and e are both 0.  Else 0.5 <= abs(m) < 1.0.
    
    fsum(seq, /)
        Return an accurate floating point sum of values in the iterable seq.
        
        Assumes IEEE-754 floating point arithmetic.
    
    gamma(x, /)
        Gamma function at x.
    
    gcd(x, y, /)
        greatest common divisor of x and y
    
    hypot(x, y, /)
        Return the Euclidean distance, sqrt(x*x + y*y).
    
    isclose(a, b, *, rel_tol=1e-09, abs_tol=0.0)
        Determine whether two floating point numbers are close in value.
        
          rel_tol
            maximum difference for being considered "close", relative to the
            magnitude of the input values
          abs_tol
            maximum difference for being considered "close", regardless of the
            magnitude of the input values
        
        Return True if a is close in value to b, and False otherwise.
        
        For the values to be considered close, the difference between them
        must be smaller than at least one of the tolerances.
        
        -inf, inf and NaN behave similarly to the IEEE 754 Standard.  That
        is, NaN is not close to anything, even itself.  inf and -inf are
        only close to themselves.
    
    isfinite(x, /)
        Return True if x is neither an infinity nor a NaN, and False otherwise.
    
    isinf(x, /)
        Return True if x is a positive or negative infinity, and False otherwise.
    
    isnan(x, /)
        Return True if x is a NaN (not a number), and False otherwise.
    
    ldexp(x, i, /)
        Return x * (2**i).
        
        This is essentially the inverse of frexp().
    
    lgamma(x, /)
        Natural logarithm of absolute value of Gamma function at x.
    
    log(...)
        log(x, [base=math.e])
        Return the logarithm of x to the given base.
        
        If the base not specified, returns the natural logarithm (base e) of x.
    
    log10(x, /)
        Return the base 10 logarithm of x.
    
    log1p(x, /)
        Return the natural logarithm of 1+x (base e).
        
        The result is computed in a way which is accurate for x near zero.
    
    log2(x, /)
        Return the base 2 logarithm of x.
    
    modf(x, /)
        Return the fractional and integer parts of x.
        
        Both results carry the sign of x and are floats.
    
    pow(x, y, /)
        Return x**y (x to the power of y).
    
    radians(x, /)
        Convert angle x from degrees to radians.
    
    remainder(x, y, /)
        Difference between x and the closest integer multiple of y.
        
        Return x - n*y where n*y is the closest integer multiple of y.
        In the case where x is exactly halfway between two multiples of
        y, the nearest even value of n is used. The result is always exact.
    
    sin(x, /)
        Return the sine of x (measured in radians).
    
    sinh(x, /)
        Return the hyperbolic sine of x.
    
    sqrt(x, /)
        Return the square root of x.
    
    tan(x, /)
        Return the tangent of x (measured in radians).
    
    tanh(x, /)
        Return the hyperbolic tangent of x.
    
    trunc(x, /)
        Truncates the Real x to the nearest Integral toward 0.
        
        Uses the __trunc__ magic method.

DATA
    e = 2.718281828459045
    inf = inf
    nan = nan
    pi = 3.141592653589793
    tau = 6.283185307179586

FILE
    /opt/anaconda3/lib/python3.7/lib-dynload/math.cpython-37m-darwin.so

Help doesn’t always work! Functions must have a “docstring”, and variables can provide unhelpful help.

help(pi)
Help on float object:

class float(object)
 |  float(x=0, /)
 |  
 |  Convert a string or number to a floating point number, if possible.
 |  
 |  Methods defined here:
 |  
 |  __abs__(self, /)
 |      abs(self)
 |  
 |  __add__(self, value, /)
 |      Return self+value.
 |  
 |  __bool__(self, /)
 |      self != 0
 |  
 |  __divmod__(self, value, /)
 |      Return divmod(self, value).
 |  
 |  __eq__(self, value, /)
 |      Return self==value.
 |  
 |  __float__(self, /)
 |      float(self)
 |  
 |  __floordiv__(self, value, /)
 |      Return self//value.
 |  
 |  __format__(self, format_spec, /)
 |      Formats the float according to format_spec.
 |  
 |  __ge__(self, value, /)
 |      Return self>=value.
 |  
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |  
 |  __getnewargs__(self, /)
 |  
 |  __gt__(self, value, /)
 |      Return self>value.
 |  
 |  __hash__(self, /)
 |      Return hash(self).
 |  
 |  __int__(self, /)
 |      int(self)
 |  
 |  __le__(self, value, /)
 |      Return self<=value.
 |  
 |  __lt__(self, value, /)
 |      Return self<value.
 |  
 |  __mod__(self, value, /)
 |      Return self%value.
 |  
 |  __mul__(self, value, /)
 |      Return self*value.
 |  
 |  __ne__(self, value, /)
 |      Return self!=value.
 |  
 |  __neg__(self, /)
 |      -self
 |  
 |  __pos__(self, /)
 |      +self
 |  
 |  __pow__(self, value, mod=None, /)
 |      Return pow(self, value, mod).
 |  
 |  __radd__(self, value, /)
 |      Return value+self.
 |  
 |  __rdivmod__(self, value, /)
 |      Return divmod(value, self).
 |  
 |  __repr__(self, /)
 |      Return repr(self).
 |  
 |  __rfloordiv__(self, value, /)
 |      Return value//self.
 |  
 |  __rmod__(self, value, /)
 |      Return value%self.
 |  
 |  __rmul__(self, value, /)
 |      Return value*self.
 |  
 |  __round__(self, ndigits=None, /)
 |      Return the Integral closest to x, rounding half toward even.
 |      
 |      When an argument is passed, work like built-in round(x, ndigits).
 |  
 |  __rpow__(self, value, mod=None, /)
 |      Return pow(value, self, mod).
 |  
 |  __rsub__(self, value, /)
 |      Return value-self.
 |  
 |  __rtruediv__(self, value, /)
 |      Return value/self.
 |  
 |  __str__(self, /)
 |      Return str(self).
 |  
 |  __sub__(self, value, /)
 |      Return self-value.
 |  
 |  __truediv__(self, value, /)
 |      Return self/value.
 |  
 |  __trunc__(self, /)
 |      Return the Integral closest to x between 0 and x.
 |  
 |  as_integer_ratio(self, /)
 |      Return integer ratio.
 |      
 |      Return a pair of integers, whose ratio is exactly equal to the original float
 |      and with a positive denominator.
 |      
 |      Raise OverflowError on infinities and a ValueError on NaNs.
 |      
 |      >>> (10.0).as_integer_ratio()
 |      (10, 1)
 |      >>> (0.0).as_integer_ratio()
 |      (0, 1)
 |      >>> (-.25).as_integer_ratio()
 |      (-1, 4)
 |  
 |  conjugate(self, /)
 |      Return self, the complex conjugate of any float.
 |  
 |  hex(self, /)
 |      Return a hexadecimal representation of a floating-point number.
 |      
 |      >>> (-0.1).hex()
 |      '-0x1.999999999999ap-4'
 |      >>> 3.14159.hex()
 |      '0x1.921f9f01b866ep+1'
 |  
 |  is_integer(self, /)
 |      Return True if the float is an integer.
 |  
 |  ----------------------------------------------------------------------
 |  Class methods defined here:
 |  
 |  __getformat__(typestr, /) from builtins.type
 |      You probably don't want to use this function.
 |      
 |        typestr
 |          Must be 'double' or 'float'.
 |      
 |      It exists mainly to be used in Python's test suite.
 |      
 |      This function returns whichever of 'unknown', 'IEEE, big-endian' or 'IEEE,
 |      little-endian' best describes the format of floating point numbers used by the
 |      C type named by typestr.
 |  
 |  __set_format__(typestr, fmt, /) from builtins.type
 |      You probably don't want to use this function.
 |      
 |        typestr
 |          Must be 'double' or 'float'.
 |        fmt
 |          Must be one of 'unknown', 'IEEE, big-endian' or 'IEEE, little-endian',
 |          and in addition can only be one of the latter two if it appears to
 |          match the underlying C reality.
 |      
 |      It exists mainly to be used in Python's test suite.
 |      
 |      Override the automatic determination of C-level floating point type.
 |      This affects how floats are converted to and from binary strings.
 |  
 |  fromhex(string, /) from builtins.type
 |      Create a floating-point number from a hexadecimal string.
 |      
 |      >>> float.fromhex('0x1.ffffp10')
 |      2047.984375
 |      >>> float.fromhex('-0x1p-1074')
 |      -5e-324
 |  
 |  ----------------------------------------------------------------------
 |  Static methods defined here:
 |  
 |  __new__(*args, **kwargs) from builtins.type
 |      Create and return a new object.  See help(type) for accurate signature.
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  imag
 |      the imaginary part of a complex number
 |  
 |  real
 |      the real part of a complex number

1.2.3. Variables and types#

1.2.3.1. Protected names#

There are a number of Python keywords that cannot be used as variable names. These keywords are:

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

Note: Be aware of the keyword lambda!

1.2.4. Fundamental types#

# integers
x = 1
type(x)
int
# float
x = 1.0
type(x)
float
# boolean
b1 = True
b2 = False

type(b1)
bool
# complex numbers: note the use of `j` to specify the imaginary part
x = 1.0 - 1.0j
type(x)
complex

1.2.5. Compound types: Strings, List and dictionaries#

1.2.5.1. Strings#

s = "Hello world"
type(s)
str
# length of the string: the number of characters
len(s)
11
# replace a substring in a string with something else
s2 = s.replace("world", "test")
print(s2)
Hello test
print(s[0])
print(s[0:5])
print(s[-3:])
H
Hello
rld

1.2.5.2. String printing/formatting#

print("str1", "str2", "str3")  # The print statement concatenates strings with a space
str1 str2 str3
print("str1", 1.0, False, -1j)  # The print statements converts all arguments to strings
str1 1.0 False (-0-1j)
print("str1" + "str2" + "str3") # strings added with + are concatenated without space
str1str2str3
# alternative, more intuitive way of formatting a string 
s3 = 'value1 = {1}, value2 = {0}'.format(3.1415, 1.5)

print(s3)
value1 = 1.5, value2 = 3.1415

1.2.5.3. List#

Lists are very similar to strings, except that each element can be of any type.

The syntax for creating lists in Python is [...]:

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

print(type(l))
print(l)
print(l[2:5])
print(l[1:8:2])
<class 'list'>
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[3, 4, 5]
[2, 4, 6, 8]

Elements in a list do not all have to be of the same type, and can be “nested”

l = [1, 'a', 1.0, 1-1j]

print(l)
[1, 'a', 1.0, (1-1j)]
nested_list = [1, [2, [3, [4, [5]]]]]

print(nested_list)
print(nested_list[1])
print(nested_list[1][1])
[1, [2, [3, [4, [5]]]]]
[2, [3, [4, [5]]]]
[3, [4, [5]]]

The range function is useful for generating lists, but it works using an “iterator”:

start = 10
stop = 30
step = 2

r = range(start, stop, step)
print(r)
print(r[3])
print(type(r))
r = list(r)
print(r)
range(10, 30, 2)
16
<class 'range'>
[10, 12, 14, 16, 18, 20, 22, 24, 26, 28]

1.2.5.3.1. Adding, inserting, modifying, and removing elements from lists#

# create a new empty list
l = []

# add an elements using `append`
l.append("A")
l.append("d")
l.append("d")

print(l)
['A', 'd', 'd']

We can modify lists by assigning new values to elements in the list. In technical jargon, lists are mutable.

l[1] = "p"
l[2] = "p"

print(l)

l[1:3] = ["d"]

print(l)
['A', 'p', 'p']
['A', 'd']

Insert an element at an specific index using insert

#l = []
l.insert(0, "i")
l.insert(1, "n")
l.insert(2, "s")
l.insert(3, "e")
l.insert(4, "r")
l.insert(5, "t")
l.insert(3,5)

print(l)
['i', 'n', 's', 5, 'e', 'r', 't', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Remove first element with specific value using ‘remove’

l.remove(5)

print(l)
['i', 'n', 's', 'e', 'r', 't', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

1.2.5.4. Tuples#

Tuples are like lists, except that they cannot be modified once created, that is they are immutable.

In Python, tuples are created using the syntax (..., ..., ...), or even ..., ...:

point = (10, 20)

print(point, type(point))
(10, 20) <class 'tuple'>

If we try to assign a new value to an element in a tuple we get an error:

point[0] = 20
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-72-9734b1daa940> in <module>
----> 1 point[0] = 20

TypeError: 'tuple' object does not support item assignment

Python functions with multiple outputs return tuples instead of lists - this can be confusing!

def two_numbers():
    return 10, 20

t = two_numbers()
print(t)
#t[0] = 5
(10, 20)

1.2.5.5. Dictionaries#

Dictionaries are also like lists, except that each element is a key-value pair. The syntax for dictionaries is {key1 : value1, ...}:

params = {"parameter1" : 1.0,
          "parameter2" : 2.0,
          "parameter3" : 3.0,}

print(type(params))
print(params)
<class 'dict'>
{'parameter1': 1.0, 'parameter2': 2.0, 'parameter3': 3.0}

Parameters can be re-assigned or added:

params["parameter1"] = "A"
params["parameter2"] = "B"

# add a new entry
params["parameter4"] = "D"

params[5] = {'subdict_key':5}

print(params)
print(params[5]['subdict_key'])

del params[5]
print(params)
{'parameter1': 'A', 'parameter2': 'B', 'parameter3': 3.0, 'parameter4': 'D', 5: {'subdict_key': 5}}
5
{'parameter1': 'A', 'parameter2': 'B', 'parameter3': 3.0, 'parameter4': 'D'}

You can iterate through dictionaries with the keys, values, and items functions:

for key in params:
    print(key)
    
print('_'*10)
    
for key,val in params.items():
    print(key, val)
parameter1
parameter2
parameter3
parameter4
__________
parameter1 A
parameter2 B
parameter3 3.0
parameter4 D

1.2.6. Loops#

1.2.6.1. for loops:#

for x in [1,2,3]:
    print(x)
1
2
3
scwp = ["scientific", "computing", "with", "python"]
for word in scwp:
    print(word)
scientific
computing
with
python

Sometimes it is useful to have access to the indices of the values when iterating over a list. We can use the enumerate function for this:

for idx, x in enumerate(scwp):
    print(idx, x)
    
# or

for id_x in enumerate(scwp):
    print(id_x)
    idx, x = id_x
    print(idx,x)
0 scientific
1 computing
2 with
3 python
(0, 'scientific')
0 scientific
(1, 'computing')
1 computing
(2, 'with')
2 with
(3, 'python')
3 python

1.2.6.2. List comprehensions: Creating lists using for loops:#

A convenient and compact way to initialize lists:

xx = [a**2 for a in range(0,5)]

print(xx)

x0 = [math.cos(xi) for xi in xx if xi==0]
print(x0)
[0, 1, 4, 9, 16]
[1.0]

1.2.7. Functions#

Function definitions use def and are based on indentation.

def func0():   
    print("test")
func0()
func0()
test
test

A “docstring” follows directly after the function definition, should describe the basic behavior, and is accessed via the help function.

def func1(s):
    """
    Print a string 's' and tell how many characters it has    
    """
    
    print(s + " has " + str(len(s)) + " characters")
    return 'something'
help(func1)
Help on function func1 in module __main__:

func1(s)
    Print a string 's' and tell how many characters it has
func1("test")
test has 4 characters
'something'

Functions return None by default. Functions that returns a value use the return keyword:

def square(x):
    """
    Return the square of x.
    """
    return x ** 2
xsquared = square(4)
print(xsquared)
16

1.2.7.1. Default argument and keyword arguments#

In a definition of a function, we can give default values to the arguments the function takes:

def myfunc(x, p=2, debug=False):
    if debug:
        print("evaluating myfunc for x = {} using exponent p = {}".format(x,p))
    return x**p
myfunc(5)
myfunc(5, debug=True)
myfunc(p=3, debug=True, x=7)
evaluating myfunc for x = 5 using exponent p = 2
evaluating myfunc for x = 7 using exponent p = 3
343

Python also uses a *args and **kwargs syntax to pass lists/dictionaries as arguments and keyword arguments. This is a more advanced topic, but you will sometimes see it.

kwargs = {'x':7, 'p':1, 'debug':False}
myfunc(**kwargs)

def multiargs(first,second,third):
    print(first, second, third)
    
#args = ['matlab', 'argument', 'passing sucks']
kwargs = {'first':'python', 'second':'argument', 'third':'passing rules'}
multiargs(**kwargs)
python argument passing rules

1.2.7.2. Unnamed “anonymous” functions (lambda function)#

These are like @ functions in Matlab, but are less useful in Python thanks to optional keyword arguments.

f1 = lambda x: x**2
    
# is equivalent to 

def f2(x):
    return x**2

print(f1(5))
print(f2(5))
25
25

1.2.8. Classes#

Classes are the key features of object-oriented programming. A class is a structure for representing an object and the operations that can be performed on the object. Classes contain:

  • attributes (variables)

  • methods (functions)

Classes have some special variables and conventions:

  • self is the first argument to all methods. This object is a self-reference.

  • Special methods are denoted by two underscores:

class Point:
    """
    Simple class for representing a point in a Cartesian coordinate system.
    """
    
    def __init__(self, x, y):
        """
        Create a new Point at x, y.
        """
        self.x = x
        self.y = y
        
    def translate(self, dx, dy):
        """
        Translate the point by dx and dy in the x and y direction.
        """
        self.x += dx
        self.y += dy
        
    def __str__(self):
        return("2D point at [%f, %f]" % (self.x, self.y))
    
a = Point
print(a)
<class '__main__.Point'>

After creating a class you can create “instances” of the class:

p1 = Point(0, 0) # this will invoke the __init__ method in the Point class

print(p1)         # this will invoke the __str__ method
print(p1.x)       # here we print the x attribute of p1

p2 = Point(0, 0) #this is a different instance of the "Point" class
print(p1 == p2) #these are different instances that simply happen to have the same attributes
# note that if you wanted this to be true you can modify the __eq__ method

p1.x = 5

p2.y = 1

print(p1)
print(p2)
2D point at [0.000000, 0.000000]
0
False
2D point at [5.000000, 0.000000]
2D point at [0.000000, 1.000000]

You can call “methods” in the following way:

p1.translate(0.25, 1.5) #note that we don't have to tell p1 where it is... it already "knows"!

print(p1)
print(p2)
2D point at [5.250000, 1.500000]
2D point at [0.000000, 1.000000]

1.2.8.1. Inheritance#

Classes can “inherit” behavior from other classes. This is very convenient, but also can be very confusing!

Inheritance should only be used by Python “experts”, but you may encounter it in reading other’s code.

class PointList(list): #This class "inherits" everything from list!
    """
    Simple class for representing a point in a Cartesian coordinate system which unneccesarily behaves like a list. 
    """
    
    def __init__(self, x, y):
        """
        Create a new Point at x, y.
        """
        list.__init__(self,[x,y]) #we can now initialize the point as a "list"
        self.x = self[0] = x #the "double equal" operator pins two variables together -- AVOID IT!
        self.y = self[1] = y
        
    def translate(self, dx, dy):
        """
        Translate the point by dx and dy in the x and y direction.
        """
        self.x += dx
        self.y += dy
        
    def append(self, z):
        print("This class only supports 2 dimensions!")
        return
pointlist = PointList(0,1)
print(dir(p1))
print(dir(pointlist))
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'translate', 'x', 'y']
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort', 'translate', 'x', 'y']
pl1 = PointList(1,1)
print(pl1)
pl1.append(3)
print(pl1)
pl1 = pl1 + [3] #inheritance can make things behave unexpectedly if not used wisely
print(pl1)
[1, 1]
This class only supports 2 dimensions!
[1, 1]
[1, 1, 3]

1.2.9. Modules#

Good code minimizes redundancy - if you are typing the same thing twice you are doing it wrong!

Modules allow you to easily re-use code. This enables:

  • better readability

  • easier maintanance (debugging/troubleshooting)

  • easier to extend/share

%%file mymodule.py
"""
Example of a python module. Contains a variable called my_variable,
a function called my_function, and a class called MyClass.
"""

my_variable = 0

def my_function():
    """
    Example function
    """
    return my_variable
    
class MyClass:
    """
    Example class.
    """

    def __init__(self):
        self.variable = my_variable
        
    def set_variable(self, new_value):
        """
        Set self.variable to a new value
        """
        self.variable = new_value
        
    def get_variable(self):
        return self.variable
Overwriting mymodule.py

We can import the module mymodule into our Python program using import, and read the doc string with help:

import mymodule
help(mymodule)
Help on module mymodule:

NAME
    mymodule

DESCRIPTION
    Example of a python module. Contains a variable called my_variable,
    a function called my_function, and a class called MyClass.

CLASSES
    builtins.object
        MyClass
    
    class MyClass(builtins.object)
     |  Example class.
     |  
     |  Methods defined here:
     |  
     |  __init__(self)
     |      Initialize self.  See help(type(self)) for accurate signature.
     |  
     |  get_variable(self)
     |  
     |  set_variable(self, new_value)
     |      Set self.variable to a new value
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors defined here:
     |  
     |  __dict__
     |      dictionary for instance variables (if defined)
     |  
     |  __weakref__
     |      list of weak references to the object (if defined)

FUNCTIONS
    my_function()
        Example function

DATA
    my_variable = 0

FILE
    /Users/jagritisahoo/Documents/coursework/third_sem/bdqm-vip-master/lectures/python_intro/mymodule.py

We can now access the variables, functions, and classes in “mymodule”

print(mymodule.my_variable)
myvar = mymodule.my_function()
print(myvar)
my_class = mymodule.MyClass() 
my_class.set_variable(10)
print(my_class.get_variable())
0
0
10

1.2.10. Exceptions#

Exceptions allow you to create (“raise”) and “handle” errors in Python. Another advanced topic, but one you should be aware of.

raise Exception("description of the error")
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-81-c32f93e4dfa0> in <module>
----> 1 raise Exception("description of the error")

Exception: description of the error
raise KeyError("this built-in error deals with keys not found in a dictionary")
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-82-6494c0fd3f22> in <module>
----> 1 raise KeyError("this built-in error deals with keys not found in a dictionary")

KeyError: 'this built-in error deals with keys not found in a dictionary'

Errors enable aborting a function if a condition occurs:

def myfunction(int_arg):
    if type(int_arg) is not int:
        raise TypeError("This function requires an integer.")
    return int_arg
myfunction('a')
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-62-67483f723860> in <module>
----> 1 myfunction('a')

<ipython-input-61-728ee3a9d065> in myfunction(int_arg)
      1 def myfunction(int_arg):
      2     if type(int_arg) is not int:
----> 3         raise TypeError("This function requires an integer.")
      4     return int_arg

TypeError: This function requires an integer.

You can “catch” errors with the try/except syntax, but be careful! Errors are often there for a reason!

try:
    myfunction(1)
    print(notdefined)
except TypeError: #best practice is to check for a specific error.
    print("Caught an exception")
print('Code still working...')
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-63-9b2d40d19c87> in <module>
      1 try:
      2     myfunction(1)
----> 3     print(notdefined)
      4 except TypeError: #best practice is to check for a specific error.
      5     print("Caught an exception")

NameError: name 'notdefined' is not defined

1.2.11. Further reading#