APP UNIT 1 Notes
APP UNIT 1 Notes
APP UNIT 1 Notes
The structured chart subject to these constraints may however use additional
variables in the form of bits (stored in an extra integer variable in the original
proof) in order to keep track of information that the original program represents by
the program location. The construction was based on Böhm's programming
language P′′.
To review, sequence performs some operations S1; S2 meaning perform S1; then
perform S2.
def addtwo1():
x = int(input("Enter an integer: "))
ans = x + 2
return ans
def gtltzero():
if x < 0:
print("Negative number")
elif x == 0:
print("Zero")
else:
print("Positive number")
return
# Call the function
gtltzero()
def square(size):
for x in range(size):
for y in range(size):
print("*", end=" ")
print("\n", end="")
return
Characteristics of python:
Python is Interpreted: Python is processed at runtime by the interpreter, it does not
require compiling the whole program before executing it. The python interpreter
directly executes the program, line by line translating each statement into a
sequence of subroutines and then into machine code, this makes python more
flexible than other programming language by providing smaller executable
program size.
Python is a Beginner’s Language: Python is the best option for Initial or beginner-
level programmers. It supports development for a wide range of applications
ranging from simple-text processing to world Wide Web Browsers to games.
Python is Portable: Python has the capability to run on a very wide variety of
hardware platforms with the same interface. It seamlessly runs on almost all
operating systems such as Windows, Linux, UNIX, Amigo, Mac OS etc, without
any changes.
Programming paradigm:
Programming paradigms are different ways or styles in which a given program or
programming language can be organized. Each paradigm consists of certain
structures, features, and opinions about how common programming problems
should be tackled. There are lots of programming languages that are well-known
but all of them need to follow some strategy when they are implemented. And
that strategy is a paradigm. Apart from varieties of programming language there
are lots of paradigms to fulfill each and every demand. They are discussed below:
def stringify(characters):
string = ''
for c in
characters:
string = string + c
return
stringify
sample_characters = ['p','y','t','h','o','n']
stringify(sample_characters)
class StringOps:
# The __init__ method is called when a new instance of the class is created
def __init__(self, characters):
# The characters parameter is assigned to the instance variable self.characters
self.characters = characters
# The stringify method concatenates the characters and assigns the result to
self.string
def stringify(self):
self.string = ''.join(self.characters)
PersonID int,
LastName varchar(200),
FirstName varchar(200),
Address varchar(200),
City varchar(200),
State varchar(200)
);
Suppose, you need to create a program to create a circle and color it. You can
create two functions to solve this problem:
Types of function
Standard library functions - These are built-in functions in Python that are
available to use.
In Python, standard library functions are the built-in functions that can be used
directly in our program. For example,
These library functions are defined inside the module. And, to use them we must
include the module inside our program.
def function_name(arguments):
# function body
return
Here,
def greet():
print('Hello World!')
Here, we have created a function named greet(). It simply prints the text Hello
World!.
def greet():
print('Hello World!')
greet()
Machine code:
def sum_the_list(mylist):
res = 0
res += val
return res
print(sum_the_list(mylist))
sum = a + b
print('Sum:', sum)
add_numbers(2, 3)
# Output: Sum: 5
In the above example, the function add_numbers() takes two parameters: a and b.
Notice the line,
add_numbers(2, 3)
sum = a + b
print('Sum:', sum)
add_numbers(2, 3)
add_numbers()
Here, we have provided default values 7 and 8 for parameters a and b respectively.
Here's how this program works
1. add_number(2, 3)
Both values are passed during the function call. Hence, these values are used
instead of the default values.
2. add_number(2)
Only one value is passed during the function call. So, according to the positional
argument 2 is assigned to argument a, and the default value is used for parameter b.
3. add_number()
No value is passed during the function call. Hence, default value is used for both
parameters a and b.
We use an asterisk (*) before the parameter name to denote this kind of argument.
For example,
def find_sum(*numbers):
result = 0
for num in numbers:
find_sum(1, 2, 3)
find_sum(4, 9)
Output
Sum = 6
Sum = 13
During a function call, values passed through arguments should be in the order of
parameters in the function definition. This is called positional arguments.
def add(a,b,c):
return (a+b+c)
First, during the function call, all arguments are given as positional arguments.
Values passed through arguments are passed to parameters by their position. 10 is
assigned to a, 20 is assigned to b and 30 is assigned to c.
print (add(10,20,30))
Functions can also be called using keyword arguments of the form kwarg=value.
During a function call, values passed through arguments don’t need to be in the
order of parameters in the function definition. This can be achieved by keyword
arguments. But all the keyword arguments should match the parameters in the
function definition.
def add(a,b=5,c=10):
return (a+b+c)
All parameters are given as keyword arguments, so there’s no need to maintain the
same order.
print (add(b=10,c=15,a=20))
#Output:45
print (add(a=10))
#Output:25
def fn(**a):
for i in a.items():
print (i)
fn(numbers=5,colors="blue",fruits="apple")
'''
Output:
('numbers', 5)
('colors', 'blue')
('fruits', 'apple')
'''
Classes vs Instances
Classes are used to create user-defined data structures. Classes define functions
called methods, which identify the behaviors and actions that an object created
from the class can perform with its data.
In this tutorial, you’ll create a Dog class that stores some information about the
characteristics and behaviors that an individual dog can have.
A class is a blueprint for how something should be defined. It doesn’t actually
contain any data. The Dog class specifies that a name and an age are necessary for
defining a dog, but it doesn’t contain the name or age of any specific dog.
While the class is the blueprint, an instance is an object that is built from a class
and contains real data. An instance of the Dog class is not a blueprint anymore. It’s
an actual dog with a name, like Miles, who’s four years old.
Put another way, a class is like a form or questionnaire. An instance is like a form
that has been filled out with information. Just like many people can fill out the
same form with their own unique information, many instances can be created from
a single class.
Let’s define a class named Book for a bookseller’s sales software.
class Book:
def __init__(self, title, quantity, author, price):
self.title = title
self.quantity = quantity
self.author = author
self.price = price
The __init__ special method, also known as a Constructor, is used to initialize the
Book class with attributes such as title, quantity, author, and price.
In Python, built-in classes are named in lower case, but user-defined classes are
named in Camel or Snake case, with the first letter capitalized.
This class can be instantiated to any number of objects. Three books are
instantiated in the following example code:
book1 = Book('Book 1', 12, 'Author 1', 120)
book2 = Book('Book 2', 18, 'Author 2', 220)
book3 = Book('Book 3', 28, 'Author 3', 320)
book1, book2 and book3 are distinct objects of the class Book. The term self in the
attributes refers to the corresponding instances (objects).
print(book1)
print(book2)
print(book3)
Object Methods:
Objects can also contain methods. Methods in objects are functions that belong to
the object.
Example
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def myfunc(self):
print("Hello my name is " + self.name)
p1 = Person("John", 36)
p1.myfunc()
Python Inheritance
Inheritance is a way of creating a new class for using details of an existing class
without modifying it.
The newly formed class is a derived class (or child class). Similarly, the existing
class is a base class (or parent class).
def eat(self):
print( "I can eat!")
def sleep(self):
print("I can sleep!")
# derived class
class Dog(Animal):
def bark(self):
print("I can bark! Woof woof!!")
Python Encapsulation
Encapsulation is one of the key features of object-oriented programming.
Encapsulation refers to the bundling of attributes and methods inside a single class.
It prevents outer classes from accessing and changing attributes and methods of a
class. This also helps to achieve data hiding.
In Python, we denote private attributes using underscore as the prefix i.e
single _ or double __. For example,
class Computer:
def __init__(self):
self.__maxprice = 900
def sell(self):
print("Selling Price: {}".format(self.__maxprice))
c = Computer()
c.sell()
Polymorphism
Polymorphism is another important concept of object-oriented programming. It
simply means more than one form.
That is, the same entity (method or operator or object) can perform different
operations in different scenarios.
Let's see an example,
class Polygon:
# method to render a shape
def render(self):
print("Rendering Polygon...")
class Square(Polygon):
# renders Square
def render(self):
print("Rendering Square...")
class Circle(Polygon):
# renders circle
def render(self):
print("Rendering Circle...")
def b():
return a()
def c():
return b()
then by timing how much longer b() takes than a() and c() than b() and so on then
can attribute that increase to function call overhead
When we discuss the overhead of a Python function call, we're really talking about
three things:
Creating the stack frame: Python needs a place to keep track of the function's
variables and what it's supposed to do next after the function call. This is called a
stack frame. It's like setting up a dedicated workspace each time you start a new
task.
Storing the variables: Python has to store the function's local variables
somewhere. This storage costs space and time.
Jumping to and from the function: Finally, Python has to take a leap of faith,
jumping to the function and back again. Like any athletic feat, this requires energy
(i.e., computational resources).
These three steps happen every single time a function is called in Python. The cost
may seem negligible at first, but when your code involves thousands or even
millions of function calls, it quickly adds up.
Example
def dynamic_memory_allocation():
my_list = []
my_list.append(i)
print(my_list)
my_list.remove(3)
print(my_list)
dynamic_memory_allocation()
Python lists are dynamically allocated and can expand or contract as necessary. A
block of memory is allocated to accommodate the list’s initial size when it is
initialized. If more space is needed for the list to grow, new memory is allocated
and the current components are copied over to it. Large datasets may be handled
and effective memory use is ensured by this dynamic scaling.
Heap Memory Allocation: Heap data structure is used for dynamic memory which
is not related to naming counterparts. It is type of memory that uses outside the
program at the global space. One of the best advantages of heap memory is to it
freed up the memory space if the object is no longer in use or the node is deleted.
Python Garbage Collector: Python removes those objects that are no longer in use
or can say that it frees up the memory space. This process of vanish the
unnecessary object's memory space is called the Garbage Collector. The Python
garbage collector initiates its execution with the program and is activated if the
reference count falls to zero. When we assign the new name or placed it in
containers such as a dictionary or tuple, the reference count increases its value. If
we reassign the reference to an object, the reference counts decreases its value if. It
also decreases its value when the object's reference goes out of scope or an object
is deleted.
Example -
a= 10
print(a)
del a
print(a)
Output:
10
print(x)
As we can see in the above output, we assigned the value to object x and printed it.
When we remove the object x and try to access in further code, there will be an
error that claims that the variable x is not defined.
Reference counting states that how many times other objects reference an object.
When a reference of the object is assigned, the count of object is incremented one.
When references of an object are removed or deleted, the count of object is
decremented. The Python memory manager performs the de-allocation when the
reference count becomes zero. Let's make it simple to understand.
Example -Suppose, there is two or more variable that contains the same value, so
the Python virtual machine rather creating another object of the same value in the
private heap. It actually makes the second variable point to that the originally
existing value in the private heap. This is highly beneficial to preserve the memory,
which can be used by another variable.
x = 20
When we assign the value to the x. the integer object 10 is created in the Heap
memory and its reference is assigned to x.
x = 20
y=x
if id(x) == id(y):
In the above code, we have assigned y = x, which means the y object will refer to
the same object because Python allocated the same object reference to new
variable if the object is already exists with the same value.
Example -
x = 20
y=x
x += 1
If id(x) == id(y):
Output:
The variables x and y are not referring the same object because x is incremented by
one, x creates the new reference object and y still referring to 10.
Dynamic dispatch will always incur an overhead so some languages offer static
dispatch for particular methods.
class Cat:
def speak(self):
print("Meow")
class Dog:
def speak(self):
print("Woof")
def speak(pet):
pet.speak()
cat = Cat()
speak(cat)
dog = Dog()
speak(dog)
Serialization is the process of converting the object into a format that can be stored
or transmitted. After transmitting or storing the serialized data, we are able to
reconstruct the object later and obtain the exact same structure/object, which
makes it really convenient for us to continue using the stored object later on
instead of reconstructing the object from scratch.
In Python, there are many different formats for serialization available. One
common example of hash maps (Python dictionaries) that works across many
languages is the JSON file format which is human-readable and allows us to store
the dictionary and recreate it with the same structure. But JSON can only store
basic structures such as a list and dictionary, and it can only keep strings and
numbers. We cannot ask JSON to remember the data type (e.g., numpy float32 vs.
float64). It also cannot distinguish between Python tuples and lists.
Object serialization is the process of converting state of an object into byte stream.
This byte stream can further be stored in any file-like object such as a disk file or
memory stream. It can also be transmitted via sockets etc. Deserialization is the
process of reconstructing the object from the byte stream.
The data format of pickle module is very Python specific. Hence, programs not
written in Python may not be able to deserialize the encoded (pickled) data
properly. In fact it is not considered to be secure to unpickle data from
unauthenticated source.
The pickle module is part of the Python standard library and implements methods
to serialize (pickling) and deserialize (unpickling) Python objects. To get started
with pickle, import it in Python:
f=open("pickled.txt","wb")
pickle.dump(dct,f)
f.close()
On the other hand load() function unpickles or deserializes data from binary file
back to Python dictionary.
import pickle
f=open("pickled.txt","rb")
d=pickle.load(f)
print (d)
f.close()
Parallel Computing: Imagine you have a huge problem to solve, and you’re
alone. You need to calculate the square root of eight different numbers. What do
you do? Well, you don’t have many options. You start with the first number, and
you calculate the result. Then, you go on with the others.
What if you have three friends good at math willing to help you? Each of them will
calculate the square root of two numbers, and your job will be easier because the
workload is distributed equally between your friends. This means that your
problem will be solved faster.
Okay, so all clear? In these examples, each friend represents a core of the CPU. In
the first example, the entire task is solved sequentially by you. This is called serial
computing. In the second example, since you’re working with four cores in total,
you’re using parallel computing. Parallel computing involves the usage of parallel
processes or processes that are divided among multiple cores in a processor.
There are mainly three models in parallel computing:
Perfectly parallel : The tasks can be run independently, and they don’t need
to communicate with each other.
Shared Memory
In shared memory, the sub-units can communicate with each other through the
same memory space. The advantage is that you don’t need to handle the
communication explicitly because this approach is sufficient to read or write from
the shared memory. But the problem arises when multiple process access and
change the same memory location at the same time. This conflict can be avoided
using synchronization techniques.
Distributed memory
In distributed memory, each process is totally separated and has its own memory
space. In this scenario, communication is handled explicitly between the processes.
Since the communication happens through a network interface, it is costlier
compared to shared memory.
better usage of the CPU when dealing with high CPU-intensive tasks
easy to code
The third advantage of multiprocessing is that it’s quite easy to implement, given
that the task you’re trying to handle is suited for parallel programming.
def cube(x):
for x in my_numbers:
print( x**3)
if __name__ == '__main__':
my_numbers = [3, 4, 5, 6, 7, 8]
p = Process(target=cube, args=('x',))
p.start()
p.join
print ("Done")