[go: up one dir, main page]

0% found this document useful (0 votes)
19 views40 pages

OOPS Concepts

Uploaded by

Aakriti
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
19 views40 pages

OOPS Concepts

Uploaded by

Aakriti
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 40

OOPS Concepts Key Understanding

points
Contents
1. Objects and classes in Python
2. Type hints, type checking
3. Make your objects work
4. Module, packages, imports
5. Basic or Single Inheritance
6. Overriding and super()
7. Multiple inheritance
8. The diamond problem
9. Polymorphism
10. Ducktyping in python
11. Encapsulation
12. Method Overloading
13. Operator overloading
14. An alternative to overloading
15. Properties in python
16. Decorators
17. Deciding when to use OOP
18. Python built in functions
19. Data structures
20. Using defaultdict
21. Handling file - File I/O
22. Working with strings
23. Iterators, Generators and Yield
24. Difference between yield and return
25. Handling exceptions
26. Challenging topics
Objects and classes
DESCRIPTION
1. An object is a single instance of a class. You can create many objects from the same class
type.
2. Objects - container - which has both data and functionality
3. We create custom user-defined type class using class statement , with class methods
(callable as functions) and class data can be accessed via objects
4. The class definition line is followed by the class contents, indented. , but it does not allow to
instantiate objects of that class. We have to instantiate objects separately
5. Attributes are defined as - objects , methods or data of the class

FOCUS POINTS

● Creating classes and instantiating objects in Python


● Solve problem via interaction of classes and objects
● When we need to show both data and behaviour - we use the concept of objects and classes
● If we need to use or depict only behaviour without storing data - use independent functions
● If data store needed - use data structures like lists, tuples, namedtuples etc
Type hints , type checking and defaults
FOCUS POINTS

Type information, what an object or method is - classes associated with it, no relation with type of
variable

def odd(n: int) -> bool:

This code snip shows giving a type hint to parameter n to be int and that method odd will return the
result in bool

Different methods to provide type checking / or type hints


1. Variable —-- variable name followed by a colon: and a type name
2. Parameters and functions - same as above
3. Add -> syntax to a function or a class method to explain the return type
4. Generic collections: set, list, dict - use type hints like set[something], list[something], and
dict[key, value] to narrow the hint from purely generic to something more specific
5. To use the generic types as annotations, a from __future__ import annotations is used at first
line of code
6. Python has type hints for file-related I/O objects. A new kind of file can use a type hint of
typing.TextIO or typing.BinaryIO to describe built-in file operations
Make your objects work
FOCUS POINTS

● Data - now add behaviour to the classes to work


● __init__ method - default method in any class - followed by () containing parameters list
● Self parameter - instance variable
● Class methods , each with different behaviour
● Data such as list or tuples - initiated after the class statement, can be used throughout the
class

Eg: Class Marks:

MarksList = List[2, 45, 78]

● We don’t explicitly pass self keyword while initializing the object, python automatically do
that
● special name, __init__. The leading and trailing double underscores means this is a special
method that the Python interpreter will treat as a special case
● We need to be sure that objects respect each other's privacy and avoid complexity
Modules, packages and imports
1. import * syntax, it takes a lot longer to find where that class is located.
2. explicitly import the database class at the top of our file using from database
import Database
3. Absolute imports specify the complete path to the module, function, or class we
want to import —-import ecommerce.products
4. we could specifically import a single class definition from the module within a
package: from ecommerce.products import Product
5. Relative import - when we know exactly where our class or module inside the
package, then we use relative imports – from .database import Database
6. use the database package inside the parent package – from ..database import
Database
7. import code that appears to come directly from a package, we can import
package as a whole
8. every module has a __name__ special variable that specifies the name of the
module when it was imported.
Order and passing wrong number of arguments
FOCUS POINTS

1. If the method requires n parameters and you do not pass the same number of arguments then an error
will occur.
2. Order of the arguments matters.
Basic / Single Inheritance
FOCUS POINTS

● Define a class that takes all functionality from parent class, Child class can access data
and objects of parent class
● The simplest use of inheritance is to add new behavior to existing classes
● The superclasses, or parent classes, in the relationship are the classes that are being
inherited from, object
● If some of our contacts are suppliers too, so like the Contact class we make another
class Supplier, with all properties of Contact with an additional parameter ‘order’ , with
yet-to-be defined later Order object
● attributes of Contact class available to the Supplier class.We don't need to define them
again (code reusability).

class Supplier(Contact):
def order(self, order: "Order") -> None
Imp point with self keyword in inheritance

The self keyword can only provide access to an existing class-based variable. If you
ever attempt to set the variable using self.all_contacts, you will actually be creating a
new instance variable associated just with that object.

Drawbacks of using Inheritance

Disadvantage is that the functionality has to be duplicated for any unrelated


classes that need same data. - leads to duplicacy of the code if we need same data
of the existing unrelated classes
Overriding and super() function
● Setting an attribute to existing class or method after it is constructed like override
the __init__() method, to add a third variable to it - this is overriding
● super() function returns the object as if it was actually an instance of the parent
class, allowing us to call the parent method directly

Eg: class Friend(Contact):


def __init__(self, name: str, email: str, phone: str) -> None:
super().__init__(name, email) self.phone = phone
● does its own initialization, namely, setting the phone attribute, which is unique to
the Friend class
Multiple inheritance
● A subclass that inherits from more than one parent class can access functionality
from both of them
● If you know why you need multiple inheritance, only then use it
● The syntax for multiple inheritance looks like a parameter list in the class
definition. Instead of including one base class inside the parentheses, we include
two (or more), separated by a comma
The diamond problem - inheritance
The diamond problem occurs when two classes have a common ancestor, and another
class has both those classes as base classes
class A:
def do_thing(self): Class 1
print('From A')
class B(A):
def do_thing(self):
print('From B')
class C(A): Class 2 Class 3
def do_thing(self):
print('From C')
class D(B, C):
pass
d = D()
d.do_thing() Class 4
Polymorphism
● Polymorphism in Python is the ability of an object to take many forms or simply
polymorphism allows us to perform the same action in many different ways.
● Methods represents actions
● each subclass of AudioFile implements play() in a different way
● Reduces need for inheritance also reduces the need for multiple inheritance
● Duck typing in Python allows us to use any object that provides the required
behavior without forcing it to be a subclass

#depict polymorphism
class Square:

def __init__(self, side) -> None:


self.side = side
def area(self) -> float:
return self.side**2
Polymorphism in Built-in function len()

The built-in function len() calculates the length of an object depending upon its type. If
an object is a string, it returns the count of characters, and If an object is a list it
returns the count of items in a list.

The len() method treats an object as per its class type.


Ducktyping in Python

● Duck Typing is a term related to polymorphism.


● The idea behind this principle is that the code itself does not care about whats the
property of the object but instead it does only care about its functionality or
behaviour
● Duck Typing refers to the principle of not constraining or binding the code to specific
data types.

Eg: does not care about whether an object is a duck, but instead it does only care about
whether it quacks.

>>> a = 'A' + 'B'

>>> a

'AB'

This behaviour means that it performs type checking at run-time


Encapsulation
● Encapsulation is, creating a wrapper on the attributes or in simple bundling data and methods
within a single unit.
● encapsulation – as a design principle – assures that the methods of a class encapsulate the
state changes for the attributes.
● We can hide an object’s internal representation from the outside. This is called information
hiding.
● The encapsulation principle applies to individual classes as well as a module with a bunch of
classes
● For example, when you create a class, it means you are implementing encapsulation. A class is
an example of encapsulation as it binds all the data members (instance variables) and
methods into a single unit.
Abstract base classes
Method overloading
Method Overloading

● You need not create multiple functions that do the same job just because you have a different set of
parameters
● Method overloading - by which you can call the same method in different ways, i.e. with different
parameters based on the number of arguments or their different datatypes
● methods should have the same name.
● Method overloading in
● improves the code reusability

Eg: class Addition:

# first sum for 2 params

def my_sum(self, a, b):

return a + b

# second overloaded sum for 3 params

def my_sum(self, a, b, c):

return a + b + c
Operator overloading
● When we have to use python operators on objects , then we use operator overloading
● When we use an operator on user defined data types (class objects) then automatically
a special function or magic function associated with that operator is invoked.
● Changing the behavior of operator is as simple as changing the behavior of method or
function.
● We define methods in the class and operators work according to that behavior defined in
methods.
● When we use + operator, the magic method __add__ is automatically invoked in which
the operation for + operator is defined.
● There are several in-built methods as well to implement operator overloading

# adding two objects

def __add__(self, other):

return self.a + other.a, self.b + other.b

def __radd__(self, die_class: Any) -> "Dice": # reverse addition


An alternative to overloading
● two varieties of overloading :
○ Overloading parameters to allow alternative types using Union[...] hints
○ Overloading the method by using more complex patterns of parameters
● A function that does accept parameters will provide the names of those
parameter names in a comma-separated list.
● Only the name of each parameter needs to be supplied
● Advised to use type hints to distinguish with parameters
● When calling the function, the values for the positional parameters must be
specified in order, and none can be missed or skipped.
● Default values for parameters
● We can use class definitions to create objects that are usable like functions -
helps to using small, separate functions to build applications.
Properties in Python
● Adding behaviors to class data with properties
● The property built-in is like a constructor for such an object, and that object is set
as the public-facing member for the given attribute
● Python gives us the property function to make methods that look like attributes.
● The property function can be used with the decorator syntax to turn a get method
into a property attribute,
● setters and getters seems helpful for encapsulating the class definitions
● we could decide to cache a value to avoid complex computations, or we might
want to validate that a given value is a suitable input

def _set_name(self, name: str) -> None:


name = property(_get_name, _set_name)
Decorators
● Decorators are another way to create properties
● a readability perspective, is that we get to mark the method as a property at the
top of the method, instead of after it is defined, where it can be easily overlooked.
● It also means we don't have to create private methods with underscore prefixes
just to define a property
● @staticmethod, @property
● @staticmethod - We initialise methods with @staticmethod that are bound to a
class rather than its object. They do not require a class instance creation. So, they
are not dependent on the state of the object.
Deciding when and where to use OOP
● An object has both data and behavior. Most use built-in data structures unless
there is an obvious need to define a class. No need to unnecessarily increase
complexities
● With the built-in property blurring the division between behavior and data, it can be
confusing to know when to choose an attribute, or a method, or a property.
● Use methods to represent actions; things that can be done to, or performed by, the
object. When you call a method, even with only one argument, it should do
something.
● Use properties for attributes in the exceptional case when there's a computation
involved with setting or getting (or deleting) an attribute like data validation,
logging etc
● __init__() method should be stated early part of the code , which is a good starting
point for any design
Python built-in functions
● numerous functions in Python that perform a task or calculate a result on certain
types of objects without being methods on the underlying class.
● They usually abstract common calculations that apply to multiple types of classes
1. len() - which returns the number of items in some kind of container object, such as a
dictionary or list

we should use the len() function instead of the __len__() method because
__len__() is a special double-underscore method, and we shouldn't call it
directly

2. reverse() - takes any sequence as input and returns a copy of that sequence in
reverse order. It is normally used in for statements when we want to iterate over
items from back to front - similar to len() function __reversed()__ will be treated
special and will be called on the class for the parameter
3. enumerate() better alternative while iterating using for statement. Iit creates a
sequence of tuples, where the first object in each tuple is the index and the
second is the original item.
Data structures
● Immutable objects, including numbers, strings, bytes, and tuples. These will often
have extended operators defined.
● Mutable collections, including sets, lists, and dictionaries, these are sized, iterable
containers
1. Empty objects - Technically, we can instantiate an object without writing a
subclass, but you cannot set any attribute later to any instaited object - class
MyObject is equivalent to class MyObject(object)
2. Lists
3. Tuples - are objects that can store a specific number of other objects in sequence
4. NamedTuples
5. Sets
Lists
FOCUS POINTS

● Ordered collection of sequential data


● Lists are represented through [] brackets
● Mutable - means you can easily change the values using the assignment operator
● In-order to access a range of elements in a list, you need to slice a list using the
slicing operator colon(:) - print(List[::])

List comprehensions - create a new list based on the values of an existing list.

Without list comprehensions you need to sue for loop to iterate over elements

With list comprehensions you do it in 1 line of code

newlist = [x for x in elementsif "a" in x]


Tuples
FOCUS POINTS

● Tuples are objects that can store a specific number of other objects in sequence
● Major difference between list and tuple is that they are immutable - means we
can't add, remove, or replace objects and are dented by ()
● if you need to modify a tuple, you're using the wrong data type (usually, a list would
be more suitable)
● The primary benefit of tuples' immutability is a tuple of immutable objects (like
strings and numbers and other tuples) has a hash value, allowing us to use them as
keys in dictionaries, and members of a set.
● A tuple that contains a mutable structure, like a list, set, or dict, isn't composed of
immutable items, and doesn't have a hash value.

Tuple(‘a’,’b’,’c’)
More on tuples
● When Python displays a tuple, it uses what's called the canonical representation; this
will always include ()'s, making the ()'s
● The degenerate cases include a tuple with only one item, written like this (2.718,). The
extra comma is required here. An empty tuple is ().
● The tuple has to be exactly the same length as the number of variables, or it will raise
an exception
● Tuple unpacking - we extract those values into a single variable.

Eg of tuple unpacking
>>> s = "AAPL", 132.76, 134.80, 130.53
>>> high = s[2]
>>> high
134.8
NamedTuples
● NamedTuples via typing.NamedTuple
● Type of container like dictionaries
● Like dictionaries, they contain keys that are hashed to a particular value.
● it supports both access from key-value and iteration

Eg:

House = namedtuple("House",
["city","country","year","area","num_rooms"])

The NamedTuple is the House, and

names of the values are, "city", "country", "year", "area" and "num_rooms"
in a list.
Sets
FOCUS POINTS

● Unordered collection of different or uniques data types


● You cannot assign duplicate or repetitive values to a python set else it will delete one
itself
● "is a member of a set" doesn't change after the first time we add it
● we create a set using the set() constructor.
● Hashable objects implement the __hash__() method; these are the same objects that
can be used as keys in dictionaries; so mutable lists, sets, and dictionaries are out.
● You can pass a set as an argument also

Sets comprehensions - similar to list compression

Allows to create sets in python using the elements from other iterables like lists, sets, or
tuples
Dataclass

● dataclasses let’s us define ordinary objects with a clean syntax for specifying
attributes. They look – superficially – very similar to named tuples
● The dataclass function is applied as a class decorator, using the @ operator
● You can specify a default value for the attributes of a dataclass.
● When the dataclass decorator receives the order=True argument, it will, by
default, compare the values based on each of the attributes in the order they
were defined.

from dataclasses import dataclass


>>> @dataclass
... class Stock:
... symbol: str
... current: float
Dictionaries
● It may be odd-looking to provide two keys in the expression but have only one
key in the result, but the rules for building dictionaries make these inevitable and
correct results.
>>> {1: "one", True: "true"}
{1: 'true'}
● changed definition of __setitem__() to prevent updating an existing key.
class NoDupDict(Dict[Hashable, Any]):
def __setitem__(self, key, value) -> None:
if key in self:
raise ValueError(f"duplicate {key!r}")
super().__setitem__(key, value)
dictionary rejects duplicates under some circumstances but can construct dictionary
from another dictionary
Using defaultdict
● Every time we access the dictionary, we need to check that it has a value
already, and if not, set it to zero
● The defaultdict, defined in the collections module, handles missing keys
elegantly
● The defaultdict() is useful for creating dictionaries of containers

from collections import defaultdict

def letter_frequency_2(sentence: str) -> defaultdict[str, int]:

frequencies: defaultdict[str, int] = defaultdict(int)


Handling files - File I/O
● Python's open() built-in function is used to open the OS file and return a Python file
object.
● For reading text from a file, we only need to pass the name of the file into the function.
- uses byte encoding
● A file "name" can be a name relative to the current working directory. It can also be an
absolute name, beginning from the root of the directory tree.
● We could also supply the value "a" as a mode argument, to append to the end of the
file, rather than completely overwriting existing file content
● The readline() method returns a single line from the file
● For readability, and to avoid reading a large file into memory at once, it is often better
to use a for statement to consume lines from a file object
● By using the with statement, the context management methods ensure that the file is
closed, even if an exception

with open("big_number.txt") as input:


for line in input:
print(line)
Working with Strings
● strings can be created in Python by wrapping a sequence of characters in single or
double quotes.
● Multiline strings can easily be created using three quote characters, and multiple
hardcoded strings can be concatenated together by placing them side by side
● The count() method tells us how many times a given substring shows up in the
string, while find(), index(), rfind(), and rindex() - are also used while working with
stings
● string formatting - making string from variables or objects
● A format string (also called an f-string) has a prefix on the opening quotation mark
of f, as in f"hello world". Such strings contains the special characters { and },
expressions, including variables
● general tool for plugging values into a string template: the format() method of a
string. The values come from parameter values to the format() method

template.format(label="Amount", size=10, number=subtotal)


Iterators, Generators, yields
● Iterators - is an object with a next() method and a done() method; the latter
returns True if there are no items left in the sequence

while not iterator.done():


item = iterator.next()
# do something with the item
● Generators - special type of function which does not return a single value,
instead, it returns an iterator object with a sequence of values.
● Yield - to yield data from another iterable object
● Any function that contains a yield keyword is termed a generator.
Difference between yield and return
● The Yield keyword in Python is similar to a return statement used for returning
values in Python
● The return keyword stops the execution of the function while yield just pauses the
execution of the function till the data to be returned is null or empty
Handling exceptions
● Handling exceptions with try, except
● Exceptions like SyntaxError and NameError mean we need to find the indicated
line number and fix the problem
● RuntimeError exception that can get raised and this is generally resolved by
downloading or upgrading the python libraries
● Raising exceptions - raise ValueError("Only even numbers can be added")
● The raise keyword is followed by the object being raised as an
exception.
● Cleaning up when an exception has occurred
● except: with no exception class to match is widely frowned upon because it will
prevent an application from simply crashing
● Creating new types of exception
● Using the exception syntax for flow control
Challenging topics
1. Multiple inheritance
2. Abstract base classes and their implementation for code reduction
3. def __init__(self, *die_class: Type[Die]) -> None
4. @dataclass
5. Metaclasses
6. Generators
7. Queues - FIFO First in first out

You might also like