Lecture eleven - Functional / object oriented programming
There are three widely used programming paradigms: procedural programming, functional programming, and object-oriented programming.
Python supports all three programming paradigms.
Procedural programming divides sequences of statements and conditional constructs into separate blocks called procedures that are parameterized over arguments that are (non-functional) values.
Procedural programming focuses on statements. It contains a systematic order of statements, functions and commands to complete a computational task or program.
Object-oriented programming (OOP) is a programming paradigm that uses objects and their interactions to design applications and computer programs.
Object have specific responsibilities within the program and make it simpler to design complex systems and operations.
Object oriented programs are class-based, meaning that objects are instances of classes, which typically also determine their type.
Object is an abstract concept. Objects are a representation of the real world objects like cars, dogs, bike, etc. The objects share two main characteristics: data and functionality.
Object is a realization of a class that can be treated as a variable type.
This object can include multiple variables (called attributes/properties) for storing different types of data. It can also have its own set of functions (called methods), to manipulate the object's properties.
A class creates a new type where objects are instances of the class.
An analogy is that you can have variables of type int which translates to saying that variables that store integers are variables which are instances (objects) of the Integer class.
It is a convention to give classes a name that starts with a capital letter.
Everything is a class in Python.
"One of my goals for Python was to make it so that all objects were "first class." By this, I meant that I wanted all objects that could be named in the language (e.g., integers, strings, functions, classes, modules, methods, and so on) to have equal status. That is, they can be assigned to variables, placed in lists, stored in dictionaries, passed as arguments, and so forth." (Blog, The History of Python, February 27, 2009)
>>> x = 42
>>> type(x)
<class 'int'>
>>> y = 4.34
>>> type(y)
<class 'float'>
>>> def f(x):
... return x + 1
...
>>> type(f)
<class 'function'>
>>> import math
>>> type(math)
<class 'module'>
Objects can store data using ordinary variables that belong to the object.
Variables that belong to an object or class are referred to as fields / attributes / properties.
A class is created using the class keyword. Like its function-based cousin def, it concerns the definition of things. While def is used to define a function, class is used to define a class.
The fields and methods of the class are listed in an indented block.
class Robot:
pass
class Person:
pass # An empty block
p = Person()
print(p)
Output:
<__main__.Person instance at 0x10171f518>
We created an object/instance of the class using the name of the class followed by a pair of parentheses.
Python can store the object in memory wherever it finds space.
Attributes are characteristics of an object.
We can dynamically create arbitrary new attributes for existing instances of a class. We do this by joining an arbitrary name to the instance name, separated by a dot "."
class Robot:
pass
x = Robot()
y = Robot()
x.name = "Marvin"
x.build_year = "1979"
y.name = "Caliban"
y.build_year = "1993"
print(x.name)
print(y.build_year)
Output:
Marvin
1993
Attributes can be bound to class names as well. In this case, each instance will possess this name as well.
class Robot(object):
pass
x = Robot()
Robot.brand = "RoboA"
print(x.brand)
x.brand = "RoboB"
print(x.brand)
y = Robot()
print(y.brand)
Robot.brand = "RoboB"
print(y.brand)
print(x.brand)
Output:
'RoboA'
'RoboB'
'RoboA'
'RoboB'
'RoboB'
Class attributes are shared - they can be accessed by all instances of that class. There is only one copy of the class variable and when any one object makes a change to a class variable, that change will be seen by all the other instances.
Object variables are owned by each individual object/instance of the class. In this case, each object has its own copy of the field i.e. they are not shared and are not related in any way to the field by the same name in a different instance.
Methods are functions defined inside the body of a class.
They are used to perform operations with the attributes of our objects.
Example:
class Robot:
def hi():
print("Hi, I am a Robot")
x = Robot()
x.hi()
Output:
Hi, I am Robot
Methods are like functions except that we have an extra self variable added to the beginning of the parameter list.
class Robot:
def hi(self):
print("Hi, I am " + self.name)
x = Robot()
x.name = "Marvin"
x.hi()
Output:
Hi, I am Marvin
self points to instance of the class that execute the method.
A special method called __init__() is used to initialize an object.
__init__ is a method which is immediately and automatically called after an instance has been created. This name is fixed and it is not possible to chose another name.
The __init__ method is run as soon as an object of a class is instantiated (i.e. created). The method is useful to do any initialization (i.e. passing initial values to your object) you want to do with your object. Notice the double underscores both at the beginning and at the end of the name.
class Robot:
def __init__(self, name=None):
self.name = name
def say_hi(self):
if self.name:
print("Hi, I am " + self.name)
else:
print("Hi, I am a robot without a name")
x = Robot()
x.say_hi()
y = Robot("Marvin")
y.say_hi()
Output:
Hi, I am a robot without a name
Hi, I am Marvin
When creating new instance p, of the class Person, we do so by using the class name, followed by the arguments in the parentheses: y = Robot("Marvin").
A variant of the static method is the class method. Instead of receiving the instance as the first parameter, it is passed the class.
class Robot():
model = 'old';
@classmethod
def is_NewModel(cls):
return cls.model == 'new'
x = Robot()
print(x.is_NewModel())
print(Robot.is_NewModel())
Robot.model = 'new'
print(x.is_NewModel())
print(Robot.is_NewModel())
Output:
False
False
True
True
Part of the object oriented programming is a concept of data abstraction - which means that data should be separated and accessed only if programmer allows it.
Through the process of abstraction, a programmer hides all but the relevant data about an object in order to reduce complexity and increase efficiency.
Element that allows data abstraction through access specifiers.
name Public
_name Protected
__name Private
class A():
def __init__(self):
self.__priv = "I am private"
self._prot = "I am protected"
self.pub = "I am public"
>>> x = A()
>>> x.pub
'I am public'
>>> x.pub = x.pub + " and my value can be changed"
>>> x.pub
'I am public and my value can be changed'
>>> x._prot
'I am protected'
>>> x.__priv
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute '__priv'
Every Python class keeps following built-in attributes and they can be accessed using dot operator like any other attribute −
__dict__ − Dictionary containing the class's namespace.
__doc__ − Class documentation string or none, if undefined.
__name__ − Class name.
__module__ − Module name in which the class is defined. This attribute is "__main__" in interactive mode.
__bases__ − A possibly empty tuple containing the base classes, in the order of their occurrence in the base class list.
class Employee:
'Common base class for all employees'
empCount = 0
def __init__(self, name, salary):
self.name = name
self.salary = salary
Employee.empCount += 1
def displayCount(self):
print "Total Employee %d" % Employee.empCount
def displayEmployee(self):
print "Name : ", self.name, ", Salary: ", self.salary
print "Employee.__doc__:", Employee.__doc__
print "Employee.__name__:", Employee.__name__
print "Employee.__module__:", Employee.__module__
print "Employee.__bases__:", Employee.__bases__
print "Employee.__dict__:", Employee.__dict__
Output:
Employee.__doc__: Common base class for all employees
Employee.__name__: Employee
Employee.__module__: __main__
Employee.__bases__: ()
Employee.__dict__: {'__module__': '__main__', 'displayCount':
<function displayCount at 0xb7c84994>, 'empCount': 2,
'displayEmployee': <function displayEmployee at 0xb7c8441c>,
'__doc__': 'Common base class for all employees',
'__init__': <function __init__ at 0xb7c846bc>}
Functional Programming is a programming paradigm based on the evaluation of expression, which avoids changing-state and mutable data.
Popular functional languages include Lisp (and family), ML (and family), Haskell, Erlang and Clojure.
A very large percentage of program errors—and the problem that drives programmers to debuggers—occur because variables obtain unexpected values during the course of program execution. Functional programs bypass this particular issue by simply not assigning values to variables at all.
In functional programming, functions transform input into output, without an intermediate representation of the current state.
In functional language functions are first class (objects). That is, everything you can do with "data" can be done with functions themselves (such as passing a function to another function).
High-order functions are functions which can take functions as arguments (or return them as results).
Functions will return new data as the outcome of a computation, leaving the original input intact.
Recursion.
An iterator is an object representing a stream of data; this object returns the data one element at a time.
The built-in iter() function takes an arbitrary object and tries to return an iterator that will return the object’s contents or elements.
L = [1,2,3]
it = iter(L)
it
it.__next__() # same as next(it)
next(it)
next(it)
next(it)
Output:
<...iterator object at ...>
1
2
3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Generators are a special class of functions that simplify the task of writing iterators. Regular functions compute a value and return it, but generators return an iterator that returns a stream of values.
Example:
def generate_ints(N):
for i in range(N):
yield i
yield is a keyword that is used like return, except the function will return a generator.
The generator is considered empty once the function runs but does not hit yield anymore.
Generators do not store all the values in memory, they generate the values on the fly.
>>> gen = generate_ints(3)
>>> gen
<generator object generate_ints at ...>
>>> next(gen)
0
>>> next(gen)
1
>>> next(gen)
2
>>> next(gen)
Traceback (most recent call last):
File "stdin", line 1, in <module>
File "stdin", line 2, in generate_ints
StopIteration
In-order traversal of a tree using generators recursively.
# A recursive generator that generates Tree leaves in in-order.
def inorder(t):
if t:
for x in inorder(t.left):
yield x
yield t.label
for x in inorder(t.right):
yield x
Eliminating flow control statements with the use of functions:
# Normal statement-based flow control
if <cond1>: func1()
elif <cond2>: func2()
else: func3()
# Equivalent "short circuit" expression
(<cond1> and func1()) or (<cond2> and func2()) or (func3())
# Example "short circuit" expression
>>> x = 3
>>> def pr(s): return s
>>> (x==1 and pr('one')) or (x==2 and pr('two')) or (pr('other'))
'other'
>>> x = 2
>>> (x==1 and pr('one')) or (x==2 and pr('two')) or (pr('other'))
'two'
A decorator in Python is any callable Python object that is used to modify a function or a class. A reference to a function "func" or a class "C" is passed to a decorator and the decorator returns a modified function or class.
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))
print("We call foo before decoration:")
foo("Hi")
print("We now decorate foo with f:")
foo = our_decorator(foo)
print("We call foo after decoration:")
foo(42)
After the decoration "foo = our_decorator(foo)", foo is a reference to the 'function_wrapper'. 'foo' will be called inside of 'function_wrapper', but before and after the call some additional code will be executed, i.e. in our case two print functions.
Output:
We call foo before decoration:
Hi, foo has been called with Hi
We now decorate foo with f:
We call foo after decoration:
Before calling foo
Hi, foo has been called with 42
After calling foo
Proper decorator definition occurrs in the line before the function header. The "@" is followed by the decorator function name.
def our_decorator(func):
def function_wrapper(x):
print("Before calling " + func.__name__)
func(x)
print("After calling " + func.__name__)
return function_wrapper
@our_decorator
def foo(x):
print("Hi, foo has been called with " + str(x))
foo("Hi")
We can decorate every other function which takes one parameter with the decorator our_decorator.
Summarizing we can say that a decorator in Python is a callable Python object that is used to modify a function, method or class definition. The original object, the one which is going to be modified, is passed to a decorator as an argument.
The decorator returns a modified object, e.g. a modified function, which is bound to the name used in the definition.
Decorators dynamically alter the functionality of a function, method or class without having to directly use subclasses. This is ideal when you need to extend the functionality of functions that you don't want to modify and control it execution.
List of Python decorators: https://wiki.python.org/moin/PythonDecoratorLibrary
In Python we can also create unnamed functions, using the lambda keyword:
f1 = lambda x: x**2
# is equivalent to
def f1(x):
return x**2
This technique is useful for example when we want to pass a simple function as an argument to another function, like this:
# map(function, iterable, ...)
# Apply function to every item of iterable and return a list of the results.
map(lambda x: x**2, range(-3,4))
Output:
[9, 4, 1, 0, 1, 4, 9]
lambda operator or lambda function is used for creating small, one-time and anonymous function objects in Python.
Lambda operator can have any number of arguments, but it can have only one expression. It cannot contain any statements and it returns a function object which can be assigned to any variable.
add = lambda x, y : x + y
print add(2, 3) # Output: 5
In lambda x, y: x + y; x and y are arguments to the function and x + y is the expression which gets executed and its values is returned as output.
By combining these three FP built-in functions, a surprising range of "flow" operations can be performed (all without statements, only expressions).
In Python 3, reduce() is not a core built-infunction anymore and it has been moved to functools (still part of the standard library), i.e. in order to use it you need to import it as follows:
from functools import reduce
Map functions expects a function object and any number of iterables like list, dictionary, etc. It executes the function_object for each element in the sequence and returns a list of the elements modified by the function object.
def multiply2(x):
return x * 2
map(multiply2, [1, 2, 3, 4]) # Output [2, 4, 6, 8]
map(lambda x : x*2, [1, 2, 3, 4]) #Output [2, 4, 6, 8]
In Python3, map function returns an iterator or map object which gets lazily evaluated.
Filter function expects two arguments, function_object and an iterable. function_object returns a boolean value. function_object is called for each element of the iterable and filter returns only those element for which the function_object returns true.
a = [1, 2, 3, 4, 5, 6]
filter(lambda x : x % 2 == 0, a) # Output: [2, 4, 6]
Like map function, filter function also returns a list of element. Unlike map function filter function can only have one iterable as input.
Similar to map, filter function in Python3 returns a filter object or the iterator which gets lazily evaluated.
Lazy evaluation, or call-by-need is an evaluation strategy which delays the evaluation of an expression until its value is needed and which also avoids repeated evaluations.
Iterators returns only element at a time, len function cannot be used with iterators.
We can not access chosen element of the sequence.
Lazy evaluation, allow creating an infinite sequence of numbers in generators.
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
for e in lst: func(e) # statement-based loop
map(func,lst) # map()-based loop
Map-based action sequence:
# let's create an execution utility function
do_it = lambda f: f()
# let f1, f2, f3 (etc) be functions that perform actions
map(do_it, [f1,f2,f3]) # map()-based action sequence
Finding intersection of two lists:
a = [1,2,3,5,7,9]
b = [2,3,5,6,7,8]
print filter(lambda x: x in a, b)
Output:
2,3,5,7
Determining the maximum of a list of numerical values by using reduce:
f = lambda a,b: a if (a > b) else b
reduce(f, [47,11,42,102,13])
Output:
102
sorted() is a built-in function that builds a new sorted list from an iterable.
>>> sorted([5, 2, 3, 1, 4])
[1, 2, 3, 4, 5]
sorted() have a key parameter to specify a function to be called on each list element prior to making comparisons.
student_tuples = [
('john', 'A', 15),
('jane', 'B', 12),
('dave', 'B', 10),
]
sorted(student_tuples, key=lambda student: student[2]) # sort by age
Output:
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
Functional programming forces you to break apart your problem into small pieces. Programs are more modular as a result. It’s easier to specify and write a small function that does one thing than a large function that performs a complicated transformation.
Testing and debugging a functional-style program is easier.
A theoretical benefit is that it’s easier to construct a mathematical proof that a functional program is correct.
Composability - assemble new programs by arranging existing functions in a new configuration and writing a few functions specialized for the current task.
Python is:
The Symbol "?:" represents in C/C++ :
void functionName(int variable, float& variable) is a:
Which statement makes sure that x is an even number?
In given example @xxx
define:
@xxx
def foo(x):
print("Hi, foo has been called with " + str(x))
What is the output of this program?
#include <iostream>
using namespace std;
int main ()
{
int a, b;
a = 10;
b = 4;
a = b;
b = 7;
cout << "a:";
cout << a;
cout << " b:";
cout << b;
return 0;
}
What is the output of this program?
f=lambda x:bool(x%2)
print(f(20), f(21))
What is the output of this program?
#include <iostream>
using namespace std;
int main()
{
int i, j;
j = 10;
i = (j++, j + 100, 999 + j);
cout << i;
return 0;
}
https://python.swaroopch.com/oop.html
https://docs.python.org/3/howto/sorting.html#sortinghowto
https://www.python-course.eu/python3_object_oriented_programming.php
http://zetcode.com/lang/python/oop/
https://docs.python.org/3/howto/functional.html
https://www.ibm.com/developerworks/library/l-prog/index.html
https://maryrosecook.com/blog/post/a-practical-introduction-to-functional-programming
http://www.oreilly.com/programming/free/functional-programming-python.csp
http://www.bogotobogo.com/python/python_fncs_map_filter_reduce.php