You are on page 1of 15

Python Programming

FUNCTION ARGUMENTS
Unpacking operator (*), place in front of parameter name to allow for unlimited
positional args
e.g., def fun1 (*args)
here all args when function is called will be stored in a tuple, use loop to iterate

2nd Unpacking operator (**), place in front of parameter name to allow for unlimited
keyword arguments
e.g., def fun2 (**args)
here when function is called then keywords are stored as keys and arguments are
stored as values in a dictionary.
UNPACKING ITERABLES: a, *b, c = [3,12,56,7,8,9,0]
Here a=3,b=[12,56,7,8,9],c=0

A,b,=(1,2) here A=1 and b=2

Unpacking operator breaks an iterable into its constituent elements, separated by commas
NAMESPACES
A namespace is a dictionary in python which is used by
the language to store the value of objects, where the
key is name of the object.
1. Built-in namespaces: Exist in every program and can be used anywhere. Exists
while program is running, to get these we use print(dir(__builtins__))

2. Global namespaces: Unindented objects in python, global scope of a specific


program. Exists while program is running, to get these we use print(globals())

3. Local namespaces: Only accessible in the specific block of code. Exists while
the block is running. To get these we use print(locals())
NOTE: local namespaces for a function also include arguments passed to it if it
is called at any point in the program.
 A type of local namespace is Enclosed namespace that only exists when
working for nested functions and only exists when function is executing.
Namespace of an enclosing function is enclosing namespace.

SCOPES
Most of the concepts are similar to namespaces, but some key points to be noted:
 Scope can be modified by using nonlocal <variable name>, used for
modifying names in the enclosing scope from the nested block, as without
this it wouldn’t be possible. This statement is to be written in the nested
function.
 Similarly, global <variable name> can be used to modify global variables
from local scope. If such a variable does not exist, the statement will create
a variable of that name in the global scope.

LAMBDA FUNCTIONS: 1-liner functions defined by using lambda


keyword. E.g., lambda x:x+2
Here x is the argument and the statement after that is the operation that the function
will perform on x and hence return the result. This statement can also be assigned to
a variable.
HIGHER ORDER FUNCTIONS
These functions accept other functions as arguments or return a function, useful for
maintaining consistent formatting in a program and for modularity, reusability of
code.

Higher-order functions are possible because functions are first-class objects in


Python, meaning that a function can be stored as a variable, passed as an argument
to a function, returned by a function, and stored in data structures (lists, dictionaries,
etc.).

Built-in Higher order functions:


1. Map(function,iterable):Applies a function to every element in the iterable.
Returns a map object. This can be converted to a list using list(map_object)
and printed.
2. Filter(function,iterable):Returns a filter object that contains those elements of
the iterable which satisfy the condition of the function given as argument.
Filter object can be converted to list and printed.
3. Reduce(function,iterable):Applies the function to the first elements of the
iterable then applies it on the next element, with the result of the first
operation as first argument. NOTE: this function is required to be imported
from the functools module.

E.g. list=[1,2,3,4,5]

Product=return(lambda x,y:x*y,list)

# This will return the products of all the elements in the list.

#first the red nos. (1,2) are multiplied to give result 2, then the result (2) is
multiplied with the blue number (3) to give 6, then the new result (6) is
multiplied with the pink number (4) and so on until the end of the list.
DECORATORS
Another function which ‘decorates’ or adds to the output of another function adds to
the output.
E.g.

Much better method:


OBJECT ORIENTED PROGRAMMING
One of the 3 types of program paradigms, the other 2 being functional orientation
and procedural orientation.
OOP has 4 pillars:
1. Encapsulation: The ability to group everything related to the data and its
behaviour
2. Abstraction: Hide the complex inner working from the average user
3. Inheritance: Classes can inherit the properties of other classes
4. Polymorphism: 2 or more classes can be of the same type and used
interchangeably. Or, to apply an identical operation onto different classes.

How to implement inheritance in Python:


Class ParentClass:
Class methods….
…..
Class ChildClass(ParentClass):
Additional methods for child class specifically
Methods of parent class already included

 Overriding Methods:
If you want the method of a child class inherited from a parent
class to behave differently for the child, just redefine the same
method for the child class.
 Accessing behaviour of overridden method:
If you want to access the behaviour of overridden method of
the overridden method, we use super().method(), here super is
the superclass or parent class. Super() does this by giving us a
proxy object.
 Multiple Inheritance:
When an object inherits from more than one parent class,
e.g.,
1. its parent class has its own parent class i.e., the object has
a super-superclass.

e.g.
class Animal():

class Cat(Animal):

class Angry_Cat(Cat):

2. Subclass inherits directly from 2 separate parent classes.


e.g.,
class Animal:
def __init__(self, name):
self.name = name

class Dog(Animal):
def action(self):
print("{} wags tail. Awwww".format(self.name))

class Wolf(Animal):
def action(self):
print("{} bites. OUCH!".format(self.name))
class Hybrid(Dog, Wolf):
def action(self):
super().action()
Wolf.action(self)

here hybrid can do what both dog and wolf can but dog and
wolf cannot show each other’s properties.
NOTE: in action() method of Hybrid class, calling
super().action() will choose Dog as the superclass as it has
been written first in the bracket of class Hybrid() [and thus
given higher position] to use Wolf’s action method, we use
Wolf.action(self).

POLYMORPHISM
The ability to apply an identical operation onto different types of
objects. Useful when object type unknown during runtime.
Inheritance is a form of it.

DUNDER METHODS
To decide an object’s behaviour when acted on by an operator (‘+’ in
this instance), we use dunder methods.
ABSTRACTION
When a program starts to get big, classes might start to share
functionality or we may lose sight of the purpose of a class’s
inheritance structure. In order to alleviate issues like this, we can use
the concept of abstraction.

Abstraction helps with the design of code by defining necessary


behaviours to be implemented within a class structure. By doing so,
abstraction also helps avoid leaving out or overlapping class
functionality as class hierarchies get larger.
from abc import ABC, abstractmethod

class Animal(ABC):
def __init__(self, name):
self.name = name

@abstractmethod
def make_noise(self):
pass

class Cat(Animal):
def make_noise(self):
print("{} says, Meow!".format(self.name))

class Dog(Animal):
def make_noise(self):
print("{} says, Woof!".format(self.name))

kitty = Cat("Maisy")
doggy = Dog("Amber")
kitty.make_noise() # "Maisy says, Meow!"
doggy.make_noise() # "Amber says, Woof!"
Above we have Cat and Dog classes that inherit from Animal.
The Animal class now inherits from an imported class ABC, which
stands for Abstract Base Class.

This is the first step to making Animal an abstract class that cannot be
instantiated. The second step is using the imported
decorator @abstractmethod on the empty method .make_noise().
The below line of code would throw an error.
an_animal = Animal("Scruffy")

# TypeError: Can't instantiate abstract class Animal with abstract method make_noise
The abstraction process defines what an Animal is but does not allow
the creation of one. The .__init__() method still requires a name,
since we feel all animals deserve a name.

The .make_noise() method exists since all animals make some form
of noise, but the method is not implemented since each animal
makes a different noise. Each subclass of Animal is now required to
define their own .make_noise() method or an error will occur.

These are some of the ways abstraction supports the design of an


organized class structure.

ENCAPSULATION
The process of making methods and data hidden inside the object
they relate to. Languages accomplish this with access modifiers like:

 Public

 Protected

 Private

In general, public members can be accessed from anywhere,


protected members can only be accessed from code within the same
module and private members can only be accessed from code within
the class that these members are defined. Generally stuff that is
protected is named with a ‘_’ prefix and private is named with a ‘__’
prefix.
E.g. __add__()
class Animal:
def __init__(self, name):
self.name = name

def __repr__(self):
return self.name

def __add__(self, another_animal):


return Animal(self.name + another_animal.name)

a1 = Animal("Horse")
a2 = Animal("Penguin")
a3 = a1 + a2
print(a1) # Prints "Horse"
print(a2) # Prints "Penguin"
print(a3) # Prints "HorsePenguin"

another is __len__() which will decide what happens when we use


len on this object.

UNIT TESTING:
A unit test validates a single behaviour and will make
sure all of the units of a program are functioning
correctly.
RAISING EXCEPTIONS
Using raise keyword we can force Python to throw an error and stop
program execution, we can also display a custom message to make it
easier for the error to be understood.
One way to use the raise keyword is by pairing it with a specific exception
class name. We can either call the class by itself or call a constructor and
provide a specific error message. So for example we could do:

raise NameError
# or
raise NameError('Custom Message')
When only the class name is provided (as in the first example), Python calls the
constructor method for us without any arguments (and thus no custom
message will come up).

For a more concrete example, let’s examine raising a TypeError for a function
that checks if an employee tries to open the cash register but does not have
the correct access:

def open_register(employee_status):
if employee_status == 'Authorized':
print('Successfully opened cash register')
else:
# Alternatives: raise TypeError() or TypeError('Message')
raise TypeError
When an employee_status is not 'Authorized', the function open_register() will
throw a TypeError and stop program execution.

Alternatively, when no built-in exception makes sense for the type of error our
program might experience, it might be better to use a generic exception with
a specific message. This is where we can use the base Exception class and
provide a single argument that serves as the error message. Let’s modify the
previous example, this time raising an Exception object with a message:

def open_register(employee_status):
if employee_status == 'Authorized':
print('Successfully opened cash register')
else:
raise Exception('Employee does not have access!')
TRY/EXCEPT
Used for exception handling i.e. executing program even after an
error occurs.
First code in try block is executed. If error (or exception) is raised,
execution stops and except block is executed. (except block
sometimes knows as handler)
 Catching specific exceptions:
If we place the name of a specific exception class in front of
except keyword for e.g. except NameError: , we can catch
specific errors in our program, it is considered good practice
for error catchers to be specific, unless generalisation is
necessary. In this case, if any error other than NameError
occurs, the exception is unhandled and the program
terminates.
When we specify exception types, Python also allows us to capture
the exception object using the as keyword. The exception object
hosts information about the specific error that occurred. Examine our
previous function but now capturing the exception object
as errorObject:
try:
print(undefined_var)
except NameError as errorObject:
print('We hit a NameError')
print(errorObject)

Would output:
We hit a NameError
name 'undefined_var' is not defined

It’s worth noting errorObject is an arbitrary name and can be


replaced with any name we see fit.

We can also specify except to handle several specific errors by putting


them in tuple form i.e.
Except (NameError,TypeError etc.)

 The else clause


We use this if we want some code to be executed if no
exceptions occur, it is put after except block and only runs if
no exceptions occur in try block.
 The finally clause
If we want code to be executed regardless of an exception
occurring or not, we put it in the finally block, which comes
after the else block.

USER DEFINED EXCEPTIONS


Class CustomExceptionName(Exception):
Pass
This defines a custom error that we can raise
 Customising user defined exceptions
By defining a __str__() method we can print a custom
message when the error occurs.

ASSERT STATEMENT
Used for auto testing of code, to check if condition is satisfied or not.
assert <condition>, 'Message if condition is not met'
Problems with this system are:
1. If AssertionError occurs, the program stops and any subsequent
tests are not conducted.
2. Tests cannot be grouped and we need to make an assertion
statement for each test.

UNITTEST FRAMEWORK

You might also like