Python Basics

In [ ]:
Beginner friendly, simple to read, Interpreted, Dynamically typed
In [ ]:
Python Hello World
In [1]:
print ("Hello world")
Hello world

Python variables

In [4]:
a=3
In [139]:
print (a)
True
In [7]:
print ("Type of a -> ", type(a))
Type of a ->  <class 'int'>
In [ ]:
Maximum possible value of an integer in python?
In [140]:
a = 10000000000000000000000000000000000000000000
b = 1
a+b
Out[140]:
10000000000000000000000000000000000000000001
In [8]:
b = 4.555
print (b)
4.555
In [9]:
print ("Type of b -> ", type(b))
Type of b ->  <class 'float'>
In [10]:
c = "Python"
print (c)
Python
In [11]:
print ("Type of c -> ", type(c))
Type of c ->  <class 'str'>
In [22]:
d = 'p'
print (d)
p
In [23]:
print ("Type of d -> ", type(d))
Type of d ->  <class 'str'>
In [71]:
d = 3+8j
print (d)
(3+8j)
In [72]:
print ("Type of d -> ", type(d))
Type of d ->  <class 'complex'>
In [69]:
e = True
print ("Type of e -> ", type(e))
Type of e ->  <class 'bool'>

Python Math operators

+ -> Addition: adds two operands

In [141]:
a = 9
b = 8
a+b
Out[141]:
17
In [142]:
a = "Python"
b = "Programming"
a+b
Out[142]:
'PythonProgramming'
In [143]:
a = 3
b = "python"
a+b
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-143-6e8d2664dc48> in <module>()
      1 a = 3
      2 b = "python"
----> 3 a+b

TypeError: unsupported operand type(s) for +: 'int' and 'str'
In [144]:
a = 9.8
b = 9
a+b
Out[144]:
18.8
In [145]:
a = "python"
c = 'l'
a+c
Out[145]:
'pythonl'

- -> Subtraction: subtracts two operands

In [146]:
a = 9
b = 8
a-b
Out[146]:
1
In [147]:
a = "PythonProgramming"
b = "Python"
a-b
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-147-f693bab7eb86> in <module>()
      1 a = "PythonProgramming"
      2 b = "Python"
----> 3 a-b

TypeError: unsupported operand type(s) for -: 'str' and 'str'
In [148]:
a = 0.333333
b = 4.222221
a-b
Out[148]:
-3.888888

* -> Multiplication: multiplies two operands

In [149]:
a = 3.4444
b = 3
a*b
Out[149]:
10.3332
In [150]:
a = "python"
b = "programming"
a*b
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-150-41081f0d5590> in <module>()
      1 a = "python"
      2 b = "programming"
----> 3 a*b

TypeError: can't multiply sequence by non-int of type 'str'
In [151]:
a = 3
b = "python"
a*b
Out[151]:
'pythonpythonpython'
In [152]:
a = "python"
b = 3
a*b
Out[152]:
'pythonpythonpython'

/ -> Division (float): divides the first operand by the second

In [153]:
a = 9
b = 4
a/b
Out[153]:
2.25

// Division (floor): divides the first operand by the second

In [154]:
a = 9
b = 4
a//b
Out[154]:
2

% Modulus: returns the remainder when first operand is divided by the second

In [156]:
a = 17
b = 5
a%b
Out[156]:
2
In [155]:
a = 17.9
b = 5
a%b
Out[155]:
2.8999999999999986

** -> Exponent

In [204]:
a = 5
b = 2
a**2
Out[204]:
25
In [206]:
a = 9.6
b = 0.9
a**b
Out[206]:
7.656743697665163
In [205]:
a = "hi"
b = 2
a**b
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-205-e843df7360da> in <module>()
      1 a = "hi"
      2 b = 2
----> 3 a**b

TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'

Relational Operators

In [49]:
a = 80
b = 90
print ("a>b : ", a>b)
print ("a<b : ", a<b)
a>b :  False
a<b :  True
In [157]:
a = "python"
b = "programming"
a>b
Out[157]:
True
In [ ]:
Strings are compared lexicographically
In [ ]:
== -> Equal to: True if both operands are equal
In [158]:
a = 90
b = 90
print (a==b)
a = "hi"
b = "hi"
c = b
print (a==c)
a = "hi"
b = "Hi"
print (a==b)
a = 90.999997
b = 90.999998
print (a==b)
a = 9.9999999999999999993
b = 9.9999999999999999994
print (a==b)
True
True
False
False
True
In [66]:
a = "python"
b = 0
print (a!=b)
True
In [67]:
a = "python"
b = 90
print (a>=b)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-67-1d22cc6400ed> in <module>()
      1 a = "python"
      2 b = 90
----> 3 print (a>=b)

TypeError: '>=' not supported between instances of 'str' and 'int'
In [68]:
a = 100
b = 100.01
print (a<=b)
True

Logical operators ( and, or , not)

In [209]:
a = True
b = False
c = 0
d = 90
e = -9
print (a and b)
print (a or b)
print (not b)
print (not c)
print (not d)
print (not e)
print (c and e)
print (d and e)
print (e and d)
print (e and not d)
False
True
True
True
False
False
0
-9
90
False
False

If else statements

In [217]:
l = [1,2,3]
if(l):
    print ("True")
else:
    print ("False")
l = {}
if(l):
    print ("True")
else:
    print ("False")
True
False

Iteration, for loop, Range

In [75]:
for i in range(10):
    print (i)
0
1
2
3
4
5
6
7
8
9
In [76]:
#Print from 5 to 9
for i in range(5,10):
    print (i)
5
6
7
8
9
In [79]:
for i in range(1,10,2):
    print (i)
1
3
5
7
9
In [83]:
for i in range(1,-10):
    print (i)
In [86]:
for i in range(1,-10, -2):
    print (i)
1
-1
-3
-5
-7
-9

Lists

In [87]:
l = [1,2,3]
print ("Type of l : ",type(l))
Type of l :  <class 'list'>
In [88]:
l = ["python", "programming", "is", "fun"]
print (l)
['python', 'programming', 'is', 'fun']
In [89]:
l = [1, "py", 3.4, True]
print (l)
[1, 'py', 3.4, True]
In [95]:
l1 = ["python", "programming", "session"]
l2 = l1
print (l2)
l2[1]="basics"
print (l1)
l1 = [0,9,8]
print ("l1 = ",l1)
print ("l2 = ", l2)
['python', 'programming', 'session']
['python', 'basics', 'session']
l1 =  [0, 9, 8]
l2 =  ['python', 'basics', 'session']

Slicing

In [133]:
l = [1,2,3,4,5,6,7,8,9,10]
l
Out[133]:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
In [184]:
print ("l[0:3] -> ", l[0:3])
print ("l[9:3] -> ", l[9:3])
print ("l[2:2] -> ", l[2:2])
print ("l[2:20] -> ", l[2:20])
print ("l[2:] -> ", l[2:])
print ("l[:2] -> ", l[:2])
print ("l[-1:-3] -> ", l[-1:-3])
print ("l[-3:-1] -> ", l[-3:-1])
print ("l[-4:-1] -> ", l[-4:-1])
print ("l[:-1] -> ", l[:-1])
print ("l[-1:] -> ", l[-1:])
print ("l[0:5:2] -> ", l[0:5:2])
print ("l[-6:-1:-2] -> ", l[-6:-1:-2])
print ("l[-6:-1:2] -> ", l[-6:-1:2])
print ("l[6:0:-2] -> ", l[6:0:-2])
print ("l[::2] -> ", l[::2])
l[0:3] ->  [1, 2, 3]
l[9:3] ->  []
l[2:2] ->  []
l[2:20] ->  [3, 4, 5, 6, 7, 8, 9]
l[2:] ->  [3, 4, 5, 6, 7, 8, 9]
l[:2] ->  [1, 2]
l[-1:-3] ->  []
l[-3:-1] ->  [7, 8]
l[-4:-1] ->  [6, 7, 8]
l[:-1] ->  [1, 2, 3, 4, 5, 6, 7, 8]
l[-1:] ->  [9]
l[0:5:2] ->  [1, 3, 5]
l[-6:-1:-2] ->  []
l[-6:-1:2] ->  [4, 6, 8]
l[6:0:-2] ->  [7, 5, 3]
l[::2] ->  [1, 3, 5, 7, 9]
In [ ]:
Assignment using slicing
In [164]:
l = [9,8,7,6,5,4,3,2,1]
l[0]=99
l
Out[164]:
[99, 8, 7, 6, 5, 4, 3, 2, 1]
In [165]:
l[0:2]=90,80
l
Out[165]:
[90, 80, 7, 6, 5, 4, 3, 2, 1]
In [166]:
l[-3:-1] = [30,20]
l
Out[166]:
[90, 80, 7, 6, 5, 4, 30, 20, 1]
In [167]:
l[4:6]=90
l
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-167-b9f73dc13a2b> in <module>()
----> 1 l[4:6]=90
      2 l

TypeError: can only assign an iterable
In [168]:
l[2:3]="p"
l
Out[168]:
[90, 80, 'p', 6, 5, 4, 30, 20, 1]
In [169]:
l[3:6]=[]
l
Out[169]:
[90, 80, 'p', 30, 20, 1]
In [172]:
l[-1] = 10
l
Out[172]:
[90, 80, 'p', 30, 20, 10]

List Iteration

In [174]:
l = [1,2,3,4,5,6,7,8,9]
#Add all elements in the list
acc = 0
for i in range(len(l)):
    acc += l[i]
acc
Out[174]:
45
In [175]:
l = [1,2,3,4,5,6,7,8,9]
#Add all elements in the list
acc = 0
for i in l:
    acc += i
acc
Out[175]:
45
In [176]:
l = [1,2,3,4,5,6,7,8,9]
#Add all elements in the list
acc = 0
for i in l[0:5]:
    acc += i
acc
Out[176]:
15
In [177]:
#Print alternate elements
l = [1,2,3,4,5,6,7,8,9]
for i in l[0::2]:
    print (i)
1
3
5
7
9

List Comprehension

In [191]:
l1 = []
for i in range(10):
    l1.append(i)
l1
Out[191]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
In [193]:
l2 = [i for i in range(10)]
l2
Out[193]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

List Comprehension with conditions

In [195]:
#Get a list of integers divisible by 3 
l3 = [i for i in range(10) if i%3==0]
l3
Out[195]:
[0, 3, 6, 9]
In [196]:
#Using if else
l = ['d','g','r','e','s','j','q','t','i']
vowels = ['a', 'e', 'i', 'o', 'u']
l4 = ["Vowel" if i in vowels else "Consonant" for i in l]
l4
Out[196]:
['Consonant',
 'Consonant',
 'Consonant',
 'Vowel',
 'Consonant',
 'Consonant',
 'Consonant',
 'Consonant',
 'Vowel']
In [197]:
l1 = [1,2,3]
l2 = [4,5,6]
#Multiply both the list using for loops
l3 = []
for i in l1:
    for j in l2:
        l3.append(i*j)
l3
Out[197]:
[4, 5, 6, 8, 10, 12, 12, 15, 18]
In [199]:
#Nested List comprehension
l1 = [1,2,3]
l2 = [4,5,6]
l3 = [i*j for j in l1 for i in l2]
l3
Out[199]:
[4, 5, 6, 8, 10, 12, 12, 15, 18]
In [216]:
#Nested List comprehension
l1 = [[1,2,3], [4,5,6], [7,8,9]]
#print all the elements as single list
l2 = [i for j in l1 for i in j]
l2
Out[216]:
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Classes in Python

In [154]:
class Cat:
    #Class attribute
    species = "mammal"
    #Instance variables
    def __init__(self,name,age):
        self.name = name
        self.age = age
a = Cat("Ginger", 2)
In [169]:
a.name
Out[169]:
'Ginger'
In [170]:
Cat.species
Out[170]:
'mammal'
In [171]:
class Person: 
      
    def __init__(self, name): 
        self.name = name 
  
    def getName(self): 
        return self.name 
  
    def isEmployee(self): 
        return False

class Employee(Person): 
  
    def isEmployee(self): 
        return True
p = Person("Name1")
p.getName()
Out[171]:
'Name1'
In [172]:
emp = Person("Name1")
print(emp.getName(), emp.isEmployee()) 
  
emp = Employee("Name2") 
print(emp.getName(), emp.isEmployee()) 
Name1 False
Name2 True
In [173]:
class Base: 
    pass
  
class Derived(Base): 
    pass
  
print(issubclass(Derived, Base)) 
print(issubclass(Base, Derived)) 
  
d = Derived() 
b = Base() 
  
print(isinstance(b, Derived)) 
  
print(isinstance(d, Base)) 
print(isinstance(d, b)) 
True
False
False
True
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-173-62839ca51de6> in <module>()
     14 
     15 print(isinstance(d, Base))
---> 16 print(isinstance(d, b))

TypeError: isinstance() arg 2 must be a type or tuple of types
In [214]:
#Diamond problem?
class Base1(object): 
    def __init__(self): 
        self.str1 = "Name1"
        print ("Base1")
  
class Base2(object): 
    def __init__(self): 
        self.str2 = "name2"        
        print ("Base2")
  
class Derived(Base1, Base2): 
    def __init__(self): 
        Base1.__init__(self) 
        Base2.__init__(self) 
        print ("Derived")
          
    def printStrs(self): 
        print(self.str1, self.str2) 
         
  
ob = Derived() 
ob.printStrs() 
Base1
Base2
Derived
Name1 name2
In [215]:
class Base1(object): 
    def __init__(self): 
        self.str1 = "Name1"
        print ("Base1")
  
class Base2(object): 
    def __init__(self): 
        self.str1 = "Name2"        
        print ("Base2")
  
class Derived(Base1, Base2): 
    def __init__(self): 
        Base2.__init__(self) 
        Base1.__init__(self) 
        print ("Derived")
          
    def printStrs(self): 
        print(self.str1) 
         
  
ob = Derived() 
ob.printStrs() 
Base2
Base1
Derived
Name1

operator overloading: repr, slices, iteration

In [177]:
class Simple:
    def __init__(self, a,b):
        self.a = a
        self.b = b
    def __repr__(self):
        return 'Simple '+str(self.a)+" " +str(self.b)
s = Simple(1,2)
s
Out[177]:
Simple 1 2
In [212]:
class MyList:
    def __init__(self, iterable):
        self.list = list(iterable)
    def __getitem__(self, key):
        if isinstance(key, slice):
            return [self.list[i] for i in range(key.start, key.stop, key.step)]
        return self.list[key]
    def __setitem__(self,key,val):
        if isinstance(key,slice):
            (start,stop,stride)=key.indices(len(self.list))
            l=(stop-start)//stride
            if len(val)!=l:
                raise ValueError("Length must be the same for slice assignment")
        self.list.__setitem__(key,val)
    def __repr__(self):
        return str(self.list)
s = MyList((1,2,3))
s[1:3:1]
s[1:2]=[9]
s
Out[212]:
[1, 9, 3]
In [178]:
class MyList(list):
    def __iter__(self):
        return (self.do_something(x) for x in list.__iter__(self))

    def do_something(self, x):
        return x*x

my_list = MyList(range(10))

for item in my_list:
    print (item)
0
1
4
9
16
25
36
49
64
81

Functional style decorators

In [180]:
#Functions generating other functions
def compose_greet_func():
    def get_message():
        return "Hello there!"

    return get_message

greet = compose_greet_func()
print (type(greet))
print (greet())
<class 'function'>
Hello there!
In [181]:
#Passing functions as arguments
def greet(name):
   return "Hello " + name 

def call_func(func):
    other_name = "Ginger"
    return func(other_name)  

print (call_func(greet))
Hello Ginger

Passing arguments to the decorated function

In [182]:
def a_decorator_passing_arguments(function_to_decorate):
    def a_wrapper_accepting_arguments(arg1, arg2):
        print ('I got args! Look:', arg1, arg2)
        function_to_decorate(arg1, arg2)
    return a_wrapper_accepting_arguments

@a_decorator_passing_arguments
def print_full_name(first_name, last_name):
    print ('My name is', first_name, last_name)
    
print_full_name('Willy', 'Wonka')
I got args! Look: Willy Wonka
My name is Willy Wonka
In [183]:
#Passing arguments to decorators
def a_decorator_passing_arguments(function_to_decorate):
    def a_wrapper_accepting_arguments(*args):
        print ('I got args! Look:', *args)
        function_to_decorate(*args)
    return a_wrapper_accepting_arguments

@a_decorator_passing_arguments
def print_full_name(*args):
    print ('My name is', *args)
    
print_full_name('hi', 'Willy', 'Wonka', 'bye')
I got args! Look: hi Willy Wonka bye
My name is hi Willy Wonka bye
In [184]:
#logging
def our_decorator(func):
    def function_wrapper(x):
        print("Before calling " + func.__name__)
        func(x)
        print("After calling " + func.__name__)
    return function_wrapper

def foo(x):
    print("Hi, foo has been called with " + str(x))

foo("Hi")
    
foo = our_decorator(foo)

foo(42)
Hi, foo has been called with Hi
Before calling foo
Hi, foo has been called with 42
After calling foo
In [185]:
def our_decorator(func):
    def function_wrapper(x):
        print("Before calling " + func.__name__)
        return func(x)
    return function_wrapper

@our_decorator
def incr(n):
    return n + 1

incr(10)
Before calling incr
Out[185]:
11
In [186]:
from math import sin, cos

def our_decorator(func):
    def function_wrapper(x):
        print("Before calling " + func.__name__)
        res = func(x)
        print(res)
        print("After calling " + func.__name__)
    return function_wrapper

sin = our_decorator(sin)
cos = our_decorator(cos)

for f in [sin, cos]:
    f(3.1415)
Before calling sin
9.265358966049024e-05
After calling sin
Before calling cos
-0.9999999957076562
After calling cos
In [213]:
def argument_test_natural_number(f):
    def helper(x):
        if type(x) == int and x > 0:
            return f(x)
        else:
            raise Exception("Argument is not an integer")
    return helper
    
@argument_test_natural_number
def factorial(n):
    if n == 1:
        return 1
    else:
        return n * factorial(n-1)

for i in range(1,10):
    print(i, factorial(i))

print(factorial(-1))
1 1
2 2
3 6
4 24
5 120
6 720
7 5040
8 40320
9 362880
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-213-ac75073c88fb> in <module>()
     17     print(i, factorial(i))
     18 
---> 19 print(factorial(-1))

<ipython-input-213-ac75073c88fb> in helper(x)
      4             return f(x)
      5         else:
----> 6             raise Exception("Argument is not an integer")
      7     return helper
      8 

Exception: Argument is not an integer
In [188]:
#Handle exceptions
def smart_divide(func):
   def inner(a,b):
      print("I am going to divide",a,"and",b)
      if b == 0:
         print("Cannot divide")
         return

      return func(a,b)
   return inner

@smart_divide
def divide(a,b):
    return a/b

divide(9,3)
I am going to divide 9 and 3
Out[188]:
3.0
In [191]:
def bread(func):
    def wrapper():
        print ("</''''''\>")
        func()
        print ("<\______/>")
    return wrapper

def ingredients(func):
    def wrapper():
        print ('#tomatoes#')
        func()
        print ('~salad~')
    return wrapper


@bread
@ingredients
def sandwich():
    print ("ham")


sandwich()
</''''''\>
#tomatoes#
ham
~salad~
<\______/>
In [192]:
#Method decorators
def method_friendly_decorator(method_to_decorate):
    def wrapper(self):
        print ("The value is ")
        return method_to_decorate(self)
    return wrapper


class someClass(object):
    def __init__(self):
        self.value = 2
    
    @method_friendly_decorator
    def print_value(self):
        print (self.value)
        
l = someClass()
l.print_value()
The value is 
2
In [193]:
#Decorators with parameters
def header(expr):
    def decorator(func):
        def wrapper(x):
            print(expr + " class!")
            func(x)
        return wrapper
    return decorator

@header("Good Morning")
def foo(x):
    print(x)

foo("How are you doing?")
Good Morning class!
How are you doing?

functools.wraps()

In [194]:
def decorator(func):
    def wrapper(a):
        return func(a)
    return wrapper

@decorator
def my_function(a):
    return a*a

my_function(9)
Out[194]:
81
In [195]:
my_function.__name__
Out[195]:
'wrapper'
In [197]:
def decorator(func):
    def wrapper(a):
        """dd dd ff"""
        wrapper.__name__ = func.__name__
        return func(a)
    return wrapper

@decorator
def my_function(a):
    """aaa"""
    return a*a

my_function(9)
Out[197]:
81
In [198]:
my_function.__name__
Out[198]:
'my_function'
In [199]:
my_function.__doc__
Out[199]:
'dd dd ff'
In [200]:
import functools
def decorator(func):
    """bbb"""
    @functools.wraps(func)
    def wrapper(a):
        return func(a)
    return wrapper

@decorator
def my_function(a):
    """aaa"""
    return a*a

my_function(9)
my_function.__name__
Out[200]:
'my_function'
In [201]:
my_function.__doc__
Out[201]:
'aaa'
In [ ]:
Uses - 
extending a function behavior from an external lib
debugging
logging
Validation and runtime checks
Code reuse

class-based decorators.

In [100]:
#Simple decorator class
class CustomAttr:
    def __init__(self, obj):
        self.attr = "a custom function attribute"
        self.obj = obj

    def __call__(self):
        self.obj()
In [202]:
class CustomAttrArg:
    def __init__(self, value):
        self.value = value

    def __call__(self, obj):
        self.value+=1
        print ("a custom function attribute with value {}".format(self.value))
        return obj

@CustomAttrArg(1)
def func():
    print ("hi")

func()
a custom function attribute with value 2
hi
In [203]:
class DecoratorForClasses:
    def __init__(self, passed_class):
        self.passed_class = passed_class
        self.passed_fancy_method = self.passed_class.fancy_method
        self.passed_class.fancy_method = self.fancy_method
        print("init")

    def __call__(self, arg=None):
        print("call")
        return self.passed_class()

    def fancy_method(self):
        print("before")
        self.passed_fancy_method(self.passed_class)
        print("after")


@DecoratorForClasses
class MyFancyClassTest():

    def fancy_method(self):
        print("fancy print from fancy method")

fancy_object = MyFancyClassTest()
fancy_object.fancy_method()
init
call
before
fancy print from fancy method
after
In [204]:
def decorator(cls):
    class Wrapper:
        def __init__(self, *args):
            self.wrapped = cls(*args)

        def __getattr__(self, name):
            print('Getting the {} of {}'.format(name, cls.__name__))
            return getattr(self.wrapped, name)

    return Wrapper

@decorator
class C:
    def __init__(self, x, y):
        self.x = x
        self.y = y



obj = C(1,2)
print(obj.x)
Getting the x of C
1

More concepts for solving the next assignment

*args and **kwargs in Python

The special syntax *args in function definitions in python is used to pass a variable number of arguments to a function. The special syntax **kwargs in function definitions in python is used to pass a keyworded, variable-length argument list.

In [205]:
def myFun(**kwargs):  
    for key, value in kwargs.items(): 
        print ("%s = %s" %(key, value)) 

myFun(first ='Good', mid ='morning', last='class')
first = Good
mid = morning
last = class
In [ ]: