Ilovepdf Merged
Ilovepdf Merged
Seq. of Chars
Ops:
• Create
• Access
• Add Chars
• Edit
• Delete
• Ops on Strings
• String Functions
1. Create
In [1]: c = 'Hello'
print(c)
Hello
In [2]: c = "Hello"
print(c)
Hello
Hello
In [6]: c = """Hello"""
print(c)
Hello
In [7]: c = str("Hello")
c
Out[7]: 'Hello'
2. Access
hello
In [9]: print(c[0])
Indexing Types:
• Positive
• Negative
In [10]: print(c[-1])
In [11]: # Slicing
c = "Hello World"
print(c)
Hello World
In [12]: print(c[0:5])
Hello
In [13]: print(c[2:])
llo World
In [14]: print(c[:4])
Hell
In [15]: print(c[:])
Hello World
In [16]: print(c[2:6:2])
lo
In [17]: print(c[0:8:3])
HlW
In [18]: print(c[0:6:-1])
In [19]: print(c[-5:-1:2])
Wr
In [20]: print(c[::-1])
dlroW olleH
In [21]: print(c[-1:-5:-1])
dlro
3. Add Chars
In [1]: string1 = "Hello"
string2 = 'World'
Hello
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_16404/2980535339.py in <module>
----> 1 c[0] = 'X'
World
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_16404/2773719593.py in <module>
----> 1 c[5] = "X"
In [27]: # Deletion
c
Out[27]: 'World'
In [28]: del c
In [29]: print(c)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_16404/2743823995.py in <module>
----> 1 print(c)
In [30]: c = "hello"
In [31]: print(c)
hello
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_16404/3601072613.py in <module>
----> 1 del c[0]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_16404/3090026040.py in <module>
----> 1 del c[:3:2]
In [34]: # Arithmetic
"Hello" + "-" + "World"
Out[34]: 'Hello-World'
In [35]: print("*"*50)
**************************************************
In [36]: print("Hello"*4)
HelloHelloHelloHello
In [37]: # Relational
"Hello" == "World"
Out[37]: False
Out[38]: True
Out[39]: False
Out[40]: False
Out[41]: False
In [42]: # Logical
"Hello" and "World"
Out[42]: 'World'
Out[44]: ''
Out[45]: 'World'
Out[46]: 'Hello'
Out[47]: 'World'
Out[48]: False
False
Out[50]: True
H
e
l
l
o
W
o
r
l
d
l
o
W
d
l
r
o
W
o
l
l
e
H
In [55]: # Membership
c
In [56]: 'H' in c
Out[56]: True
In [57]: 'h' in c
Out[57]: False
Out[58]: False
7. String Functions
1. Common Functions
• len()
• max()
• min()
• sorted()
In [59]: c = 'kolkata'
len(c)
Out[59]: 7
In [60]: max(c)
Out[60]: 't'
In [61]: min(c)
Out[61]: 'a'
In [62]: sorted(c)
2. Capitalize/Title/Upper/Lower/Swapcase
In [64]: c
c.capitalize()
Out[64]: 'Kolkata'
In [65]: c
Out[65]: 'kolkata'
In [68]: c.upper().lower()
Out[68]: 'kolkata'
In [69]: "KoLkAtA".swapcase()
Out[69]: 'kOlKaTa'
3. Count
In [70]: "it is raining".count("i")
Out[70]: 4
Out[71]: 1
Out[72]: 0
4. Find/Index
In [73]: "it is raining".find("i")
Out[73]: 0
Out[74]: 12
Out[75]: 6
Out[76]: -1
Out[77]: 6
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_16404/4196732353.py in <module>
----> 1 "it is raining".index("x")
Out[79]: True
Out[80]: False
Out[81]: True
6. format
In [82]: "Hello my name is {} and I am {}".format("Saurabh", 22)
In [86]: "Hello my name is {name} and I am {name}".format(name = "Saurabh", age = 22, weight
7. isalnum/isalpha/isdecimal/isdigit/isidentifier
In [87]: "FLAT20".isalnum() # Alphanumeric
Out[87]: True
In [88]: "FLAT20&".isalnum()
Out[88]: False
In [90]: "FLAT20".isalpha()
Out[90]: False
In [91]: "20".isdigit()
Out[91]: True
In [92]: "20A".isdigit()
Out[92]: False
Out[93]: False
In [94]: "Hello_World".isidentifier()
Out[94]: True
8. Split
In [95]: "who is the pm of india".split()
9. Join
In [99]: " ".join(['who', 'is', 'the', 'pm', 'of', 'india'])
10. Replace
In [101… "Hi my name is Saurabh".replace("Saurabh", "Siddhant")
11. Strip
In [102… name = " Saurabh "
name
In [104… name.strip()
Out[104… 'Saurabh'
Example Programs
In [2]: # 1. Length of String without len()
s = input('enter the string: ')
counter = 0
for i in s:
counter += 1
print('length of string is', counter)
List vs Array
• Array: Homogeneous; Contiguous memory; Fast; Numerical/Scientific
use.
• List: Heterogeneous; Non-contiguous memory; Programmer-friendly;
General-purpose.
1. Create
2. Access
3. Edit
4. Add
5. Delete
6. Operations
7. Functions
List Characteristics
• Ordered
• Mutable
• Heterogeneous
• Duplicates allowed
• Dynamic size
• Nesting supported
• Indexable
• Any object type
1. Create
In [3]: # Empty
L = []
L
Out[3]: []
Out[4]: [1, 2, 3, 4, 5]
In [5]: # Hetrogenous
L2 = ["Hello", 4, 5, 6, True, 5+6j]
L2
In [2]: # 3D
L4 = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
L4
In [9]: L6 = list()
L6
Out[9]: []
2. Access
In [10]: L1
Out[10]: [1, 2, 3, 4, 5]
In [11]: L1[0]
Out[11]: 1
In [12]: L1[-1]
Out[12]: 5
In [13]: # Slicing
L1[1:3]
Out[13]: [2, 3]
In [14]: L1[::-1]
Out[14]: [5, 4, 3, 2, 1]
In [15]: L3
In [16]: L3[0]
Out[16]: 1
In [17]: L3[-1]
Out[17]: [4, 5]
In [18]: x = L3[-1]
x
Out[18]: [4, 5]
In [19]: x[0]
Out[19]: 4
In [20]: L3[-1][0]
Out[20]: 4
In [21]: L3[-1][-1]
Out[21]: 5
In [22]: L4
In [23]: L4[-1][-1][0]
Out[23]: 7
In [24]: L4[0][1][1]
Out[24]: 4
3. Edit
In [25]: L1
Out[25]: [1, 2, 3, 4, 5]
Out[26]: [100, 2, 3, 4, 5]
4. Add
• append()
• extend()
• insert()
In [31]: L1
In [32]: L1.append(1000)
L1
In [33]: L1.append("hello")
L1
Out[34]: [100, 200, 300, 400, 500, 1000, 'hello', 5000, 6000, 7000]
Out[35]: [100, 200, 300, 400, 500, 1000, 'hello', 5000, 6000, 7000, [5, 6]]
In [36]: L1.extend("goa")
L1
Out[36]: [100, 200, 300, 400, 500, 1000, 'hello', 5000, 6000, 7000, [5, 6], 'g', 'o',
'a']
Out[37]: [100, 'world', 200, 300, 400, 500, 1000, 'hello', 5000, 6000, 7000, [5, 6],
'g', 'o', 'a']
5. Delete
• del
• .remove()
• .pop()
• .clear()
In [39]: L2
In [40]: del L2
L2
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_10180/3495304003.py in <module>
1 del L2
----> 2 L2
Out[41]: ['world', 200, 300, 400, 500, 1000, 'hello', 5000, 6000, 7000, [5, 6], 'g',
'o', 'a']
Out[42]: ['world', 200, 300, 400, 500, 1000, 'hello', 5000, 6000, 7000, 'g', 'o', 'a']
Out[43]: ['world', 200, 300, 400, 500, 1000, 'hello', 5000, 6000, 7000]
In [44]: L1.remove("hello")
L1
Out[44]: ['world', 200, 300, 400, 500, 1000, 5000, 6000, 7000]
In [45]: L1.pop()
L1
In [46]: L1.pop()
L1
Out[46]: ['world', 200, 300, 400, 500, 1000, 5000]
In [47]: L1.clear()
L1
Out[47]: []
6. Operations
• Arithmetic
• Membership
• Loop
In [48]: L = [1, 2, 3, 4]
L1 = [5, 6, 7, 8]
In [49]: # Concatenation/Merge
L + L1
Out[49]: [1, 2, 3, 4, 5, 6, 7, 8]
In [50]: L
Out[50]: [1, 2, 3, 4]
In [51]: L1
Out[51]: [5, 6, 7, 8]
In [52]: L * 3
Out[52]: [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]
In [53]: # Loops
for i in L:
print(i)
1
2
3
4
In [54]: L3
In [56]: 4 in L3
Out[56]: False
In [57]: [4, 5] in L3
Out[57]: True
7. Functions
• len()
• min()
• max()
• sorted()
In [58]: L
Out[58]: [1, 2, 3, 4]
In [59]: len(L)
Out[59]: 4
In [60]: min(L)
Out[60]: 1
In [61]: max(L)
Out[61]: 4
In [62]: sorted(L)
Out[62]: [1, 2, 3, 4]
Out[63]: [4, 3, 2, 1]
In [64]: L
Out[64]: [1, 2, 3, 4]
In [65]: L.sort(reverse = True)
In [66]: L
Out[66]: [4, 3, 2, 1]
In [67]: L.sort()
In [4]: # count
L = [1, 2, 1, 3, 4, 1, 5]
L.count(5)
Out[4]: 1
In [68]: L
Out[68]: [1, 2, 3, 4]
In [69]: # index
L.index(3)
Out[69]: 2
In [6]: # reverse
L = [2, 1, 5, 7, 0]
# permanently reverses the list
L.reverse()
print(L)
[0, 7, 5, 1, 2]
[2, 1, 5, 7, 0]
[0, 1, 2, 5, 7]
[2, 1, 5, 7, 0]
[0, 1, 2, 5, 7]
saurabh
In [73]: L1 = [1, 1, 2, 2, 3, 3, 4, 4]
# Output: L2 = [1, 2, 3, 4]
L = []
for i in L1:
if i not in L:
L.append(i)
print(L)
[1, 2, 3, 4]
List Comprehension
Compact list creation.
Advantages
Out[30]: [1, 2, 3, 4, 5, 6, 7]
Out[35]: ['Orange']
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Out[16]: [5, 10, 15, 20, 25, 30, 35, 40, 45, 50]
Out[18]: ['apple']
Out[20]: [5, 6, 7, 8, 10, 12, 14, 16, 15, 18, 21, 24, 20, 24, 28, 32]
List Traversal
1. Itemwise
2. Indexwise
In [21]: # Itemwise
L = [1, 2, 3, 4]
for i in L:
print(i)
1
2
3
4
In [22]: # Indexwise
L = [1, 2, 3, 4]
for i in range(0, len(L)):
print(L[i])
1
2
3
4
Zip
zip() Function:
Length Mismatch:
Out[24]: [0, 0, 0, 0]
In [26]: a = [1, 2, 3]
b = a.copy()
print(a)
print(b)
a.append(4)
print(a)
print(b)
# lists are mutable
[1, 2, 3]
[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3]
List Programs
In [1]: # Split list into odd and even
L = [1, 2, 3, 4, 5, 6]
# Odd numbers
odd_numbers = [x for x in L if x % 2 != 0]
# Even numbers
even_numbers = [x for x in L if x % 2 == 0]
# 1. Prompt input
input_string = input("Enter the list elements separated by spaces: ")
# 2. Split string
string_list = input_string.split()
# 3. Convert to integers
integer_list = [int(item) for item in string_list]
# 4. Output
print("The input list is:", integer_list)
L = [1, 2, 3, 4, 5, 3]
old_item = 3
new_item = 300
# Iterate through the list and replace old_item with new_item
for i in range(len(L)):
if L[i] == old_item:
L[i] = new_item
print("Updated list:", L)
# Define 2D list
L2D = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
# Initialize 1D list
L1D = []
# Flatten 2D to 1D
for sublist in L2D:
for item in sublist:
L1D.append(item)
print("1D list:", L1D)
1D list: [1, 2, 3, 4, 5, 6, 7, 8, 9]
# Test
L1 = [1, 2, 3, 4, 5]
L2 = [1, 3, 2, 4, 5]
Characteristics:
• Ordered elements.
• Unmodifiable post-creation.
• Allows duplicates.
1. Create
2. Access
3. Edit
4. Add
5. Delete
6. Operations
7. Functions
1. Create
In [1]: # empty
T1 = ()
T1
Out[1]: ()
In [2]: # homo
T2 = (1, 2, 3, 4, 5)
T2
Out[2]: (1, 2, 3, 4, 5)
In [3]: # hetro
T3 = ("Hello", 4, 5, 6)
T3
Out[3]: ('Hello', 4, 5, 6)
In [4]: # tuple
T4 = (1, 2, 3, (4, 5))
T4
Out[5]: 1
In [6]: type(T5)
Out[6]: int
Out[7]: str
Out[8]: tuple
In [9]: T6 = tuple("Goa")
T6
Out[10]: (1, 2, 3, 4)
2. Access
• Indexing
• Slicing
In [11]: T2
Out[11]: (1, 2, 3, 4, 5)
In [12]: T2[0]
Out[12]: 1
In [13]: T2[-1]
Out[13]: 5
In [14]: T2[:4]
Out[14]: (1, 2, 3, 4)
In [15]: T4
In [16]: T4[-1][0]
Out[16]: 4
3. Edit
In [17]: L = [1, 2, 3, 4, 5]
Out[18]: [100, 2, 3, 4, 5]
In [19]: T2
Out[19]: (1, 2, 3, 4, 5)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_22232/394845330.py in <module>
----> 1 T2[0] = 100
4. Add
In [22]: # not possible
# Tuples: immutable
5. Delete
In [23]: T1
Out[23]: ()
In [24]: del T1
T1
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_22232/1707313094.py in <module>
1 del T1
----> 2 T1
In [25]: T3
Out[25]: ('Hello', 4, 5, 6)
In [26]: T2
Out[26]: (1, 2, 3, 4, 5)
6. Operations
In [29]: T2
Out[29]: (1, 2, 3, 4, 5)
In [30]: T3
Out[30]: ('Hello', 4, 5, 6)
In [ ]: # + and *
In [31]: T2 + T3
In [32]: T2 * 3
Out[32]: (1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5)
In [33]: # iteration
for i in T2:
print(i)
1
2
3
4
5
In [34]: # membership
1 in T2
Out[34]: True
7. Functions
In [35]: len(T2)
Out[35]: 5
In [36]: min(T2)
Out[36]: 1
In [37]: max(T2)
Out[37]: 5
In [38]: sum(T2)
Out[38]: 15
In [39]: sorted(T2)
Out[39]: [1, 2, 3, 4, 5]
Out[40]: [5, 4, 3, 2, 1]
In [1]: # count
t = (1, 2, 3, 4, 5)
t.count(50)
Out[1]: 0
In [2]: # index
t.index(50)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[2], line 2
1 # index
----> 2 t.index(50)
Lists vs Tuples
Syntax:
• Lists: [ ]
• Tuples: ( )
Mutability:
• Lists: Mutable
• Tuples: Immutable
Speed:
• Lists: Slower (mutable)
• Tuples: Faster (immutable)
Memory:
• Lists: Higher
• Tuples: Lower
Functionality:
• Both: Indexing, slicing
• Lists: More methods
Error-Prone:
• Lists: Modifiable
• Tuples: Safer
Use Case:
• Lists: Dynamic
• Tuples: Static
In [3]: import time
L = list(range(100000000))
T = tuple(range(100000000))
# List timing
start = time.time()
for i in L:
i*5
print('List time', time.time()-start)
# Tuple timing
start = time.time()
for i in T:
i*5
print('Tuple time', time.time()-start)
In [5]: a = [1, 2, 3]
b = a
a.append(4)
print(a)
print(b)
[1, 2, 3, 4]
[1, 2, 3, 4]
In [6]: a = (1, 2, 3)
b = a
a = a + (4,)
print(a)
print(b)
(1, 2, 3, 4)
(1, 2, 3)
Special Syntax
In [7]: # tuple unpacking
a, b, c = (1, 2, 3)
print(a, b, c)
1 2 3
In [8]: a, b = (1, 2, 3)
print(a, b)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[8], line 1
----> 1 a,b = (1,2,3)
2 print(a,b)
In [9]: a = 1
b = 2
a, b = b, a
print(a, b)
2 1
1 2
[3, 4]
Characteristics:
• Unordered
• Mutable
• Unique Elements
• No Mutable Data Types
1. Create
2. Access
3. Edit
4. Add
5. Delete
6. Operations
7. Functions
1. Create
In [2]: # empty
S1 = {}
S1
Out[2]: {}
In [3]: type(S1)
Out[3]: dict
In [4]: S1 = set()
S1
Out[4]: set()
In [5]: type(S1)
Out[5]: set
Out[6]: {1, 2, 3, 4, 5}
{1, 2, 3}
Out[9]: {1, 2, 3}
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[10], line 1
----> 1 S4 = {[1,2,3],"Hello"}
2 S4
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[12], line 1
----> 1 S5 = {{1},{2}}
In [13]: s1 = {1, 2, 3}
s2 = {3, 2, 1}
print(s1 == s2)
True
2. Access
In [14]: S1
Out[14]: {1, 2, 3, 4, 5}
In [15]: S1[0]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[15], line 1
----> 1 S1[0]
In [16]: S1[-1]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[16], line 1
----> 1 S1[-1]
In [17]: S1[0:3]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[17], line 1
----> 1 S1[0:3]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[18], line 1
----> 1 S1[2] = 100
In [19]: S1
Out[19]: {1, 2, 3, 4, 5}
In [20]: id(S1)
Out[20]: 1682006879008
In [21]: L = list(S1)
L
Out[21]: [1, 2, 3, 4, 5]
Out[22]: [100, 2, 3, 4, 5]
In [23]: S1 = set(L)
S1
In [24]: id(S1)
Out[24]: 1682036493888
4. Add
In [25]: S1
In [26]: S1.add(6)
S1
Out[27]: 1682036493888
In [28]: S1.add(7)
S1
In [29]: id(S1)
Out[29]: 1682036493888
{2, 3, 100, 5, 4, 6, 7}
5. Delete
• del
• remove()
• pop()
• discard()
• clear()
In [32]: S2
In [33]: del S2
S2
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[33], line 2
1 del S2
----> 2 S2
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[34], line 1
----> 1 del S1[0]
In [35]: S1
Out[35]: {2, 3, 4, 5, 6, 7, 100}
In [36]: S1.remove(100)
S1
Out[36]: {2, 3, 4, 5, 6, 7}
In [37]: S1.pop()
S1
Out[37]: {3, 4, 5, 6, 7}
In [38]: S1.pop()
S1
Out[38]: {4, 5, 6, 7}
In [39]: S1.discard(7)
S1
Out[39]: {4, 5, 6}
In [40]: S1.clear()
S1
Out[40]: set()
6. Operations
In [41]: S1 = {1, 2, 3, 4, 5}
S2 = {3, 4, 5, 6, 7}
In [42]: S1 + S2
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[42], line 1
----> 1 S1 + S2
In [43]: S1 * 3
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[43], line 1
----> 1 S1 * 3
1
2
3
4
5
Out[45]: True
In [46]: 1 not in S1
Out[46]: False
In [47]: s1 = {1, 2, 3, 4, 5}
s2 = {4, 5, 6, 7, 8}
In [48]: # Union(|)
s1 | s2
Out[48]: {1, 2, 3, 4, 5, 6, 7, 8}
In [49]: # Intersection(&)
s1 & s2
Out[49]: {4, 5}
In [50]: # Difference(-)
s1 - s2
s2 - s1
Out[50]: {6, 7, 8}
Out[51]: {1, 2, 3, 6, 7, 8}
7. Functions
• len()
• min()
• max()
• sorted()
In [53]: len(S1)
Out[53]: 5
In [54]: min(S1)
Out[54]: 1
In [55]: max(S1)
Out[55]: 5
In [56]: sorted(S1)
Out[56]: [1, 2, 3, 4, 5]
Out[57]: [5, 4, 3, 2, 1]
In [58]: S1
Out[58]: {1, 2, 3, 4, 5}
In [59]: S2
Out[59]: {3, 4, 5, 6, 7}
In [60]: S1.union(S2)
Out[60]: {1, 2, 3, 4, 5, 6, 7}
In [61]: S1.intersection(S2)
Out[61]: {3, 4, 5}
In [62]: S1.difference(S2)
Out[62]: {1, 2}
In [63]: S2.difference(S1)
Out[63]: {6, 7}
In [64]: S1.symmetric_difference(S2)
Out[64]: {1, 2, 6, 7}
In [65]: S1.isdisjoint(S2)
Out[65]: False
In [66]: S1.issubset(S2)
Out[66]: False
In [67]: S1.issuperset(S2)
Out[67]: False
In [68]: S1.clear()
S1
Out[68]: set()
In [69]: # copy
s1 = {1, 2, 3}
s2 = s1.copy()
print(s1)
print(s2)
{1, 2, 3}
{1, 2, 3}
Frozen set
• Immutable set.
• Created via frozenset() .
• No modifications post-creation.
• Ideal for hashable items: strings, numbers.
Traits:
• Mutable
• No indexing
• Unique keys
• Immutable keys
1. Create
2. Access
3. Edit
4. Add
5. Delete
6. Operations
7. Functions
1. Create
In [4]: # empty dictionary
D = {}
D
Out[4]: {}
In [5]: # 1D dictionary
D = {"Name":"Saurabh", "Gender":"Male"}
D
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[6], line 1
----> 1 D1 = {[1,2,3]:"Saurabh"}
In [ ]: # duplicate keys
D2 = {"Name":"Natty", "Name":"Maru"}
D2
Out[7]: {'Name': 'Saurabh', 'College': 'SGT', 'Marks': {'M1': 99, 'DS': 97, 'Eng': 9
8}}
2. Access
In [11]: D
In [12]: D[0]
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
Cell In[12], line 1
----> 1 D[0]
KeyError: 0
In [13]: D["Name"]
Out[13]: 'Saurabh'
In [14]: D.get("Name")
Out[14]: 'Saurabh'
In [15]: D["Gender"]
Out[15]: 'Male'
In [16]: D.get("Gender")
# .get() ---> 1-D dicts only; Not for 2-D/nested dicts.
Out[16]: 'Male'
In [17]: D3
Out[17]: {'Name': 'Saurabh', 'College': 'SGT', 'Marks': {'M1': 99, 'DS': 97, 'Eng': 9
8}}
In [18]: D3["Marks"]["DS"]
Out[18]: 97
3. Edit
In [19]: D
In [21]: D3["Marks"]["DS"] = 10
D3
Out[21]: {'Name': 'Saurabh', 'College': 'SGT', 'Marks': {'M1': 99, 'DS': 10, 'Eng': 9
8}}
4. Add
In [22]: D
In [23]: D["Age"] = 22
D
In [24]: D3["Marks"]["M2"] = 95
D3
5. Delete
In [29]: D5 = {}
D5
Out[29]: {}
In [30]: del D5
D5
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[30], line 2
1 del D5
----> 2 D5
In [32]: D.pop('Name')
Out[32]: 'Kartik'
In [33]: D.popitem()
In [34]: D
Out[35]: {}
In [36]: D.clear()
D
Out[36]: {}
6. Operations
In [39]: D3
In [42]: D4
In [43]: D3 + D4
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[43], line 1
----> 1 D3 + D4
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[44], line 1
----> 1 D3 * 3
In [45]: D3
In [46]: # Iteration
for i in D3:
print(i)
Name
College
Marks
Name Saurabh
College SGT
Marks {'M1': 99, 'DS': 10, 'Eng': 98, 'M2': 95}
In [48]: # Membership
"Saurabh" in D3
Out[48]: False
In [49]: "Name" in D3
Out[49]: True
7. Functions
In [50]: len(D3)
Out[50]: 3
In [51]: min(D3)
Out[51]: 'College'
In [52]: max(D3)
Out[52]: 'Name'
In [53]: sorted(D3)
In [54]: D3.items()
In [55]: D3.keys()
In [56]: D3.values()
Out[56]: dict_values(['Saurabh', 'SGT', {'M1': 99, 'DS': 10, 'Eng': 98, 'M2': 95}])
In [57]: # update
d1 = {1:2, 3:4, 4:5}
d2 = {4:7, 6:8}
d1.update(d2)
print(d1)
{1: 2, 3: 4, 4: 7, 6: 8}
Dictionary Comprehension
{key : value for var in iterable}
Creates dicts from iterables.
Example:
In [2]: D.items()
In [4]: L = [1, 2, 3, 4, 5, 6, 7]
Out[58]: {1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81, 10: 100}
In [64]: {
2:{1:2, 2:4, 3:6, 4:8},
3:{1:3, 2:6, 3:9, 4:12},
4:{1:4, 2:8, 3:12, 4:16}
}
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_14692/4139848714.py in <module>
1 # python calls variable as name
----> 2 a
Variable Assignment:
a = 4
a = 4 : a points to memory location of 4 .
In [3]: id(a)
Out[3]: 2420901374352
In [4]: hex(2883116886416)
Out[4]: '0x29f47286990'
In [5]: id(4)
Out[5]: 2420901374352
Aliasing
In [6]: a = 5
b = a
# aliasing
In [7]: id(a)
Out[7]: 2420901374384
In [8]: id(b)
Out[8]: 2420901374384
In [10]: c = b
In [11]: id(c)
Out[11]: 2420901374384
In [13]: del(a)
In [14]: b
Out[14]: 5
In [15]: del(b)
In [16]: c
Out[16]: 5
In [3]: # removing reference, not actual value/name. original value remains intact.
In [18]: a = 5
b = a
a = 6
In [19]: b
Out[19]: 5
In [4]: # Initially, a = b = 5
# Changing a to 6 leaves b at 5
Reference Counting
In [21]: # Reference Counting tracks variables referencing a memory address.
a = "erherherh" # Memory address X
b = a # b ---> X
c = b # c ---> X
In [22]: id(a)
Out[22]: 2420982108464
In [23]: id(b)
Out[23]: 2420982108464
In [24]: id(c)
Out[24]: 2420982108464
In [25]: b
Out[25]: 'erherherh'
In [26]: c
Out[26]: 'erherherh'
In [28]: sys.getrefcount(a)
Out[28]: 16
sys.getrefcount() :
In [30]: a = "abcdef"
b = a
c = b
In [31]: sys.getrefcount(a)
Out[31]: 4
Garbage Collection
Unused memory remains after references are deleted.
In [33]: # WB 1
a = 2
b = a
c = b
In [34]: sys.getrefcount(a)
Out[34]: 1681
In [36]: a = 61
b = a
c = b
In [37]: sys.getrefcount(a)
Out[37]: 25
In [38]: a = 717
b = a
c = b
In [39]: sys.getrefcount(a)
Out[39]: 4
In [40]: a = 717
# this is not aliasing
In [41]: sys.getrefcount(a)
Out[41]: 2
In [42]: d = c
# this is aliasing
In [43]: sys.getrefcount(a)
Out[43]: 2
In [44]: a = 4
b = 4
In [45]: id(a)
Out[45]: 2420901374352
In [46]: id(b)
Out[46]: 2420901374352
In [47]: a = 256
b = 256
In [48]: id(a)
Out[48]: 2420901570960
In [49]: id(b)
Out[49]: 2420901570960
In [50]: a = 257
b = 257
In [51]: id(a)
Out[51]: 2420982884720
In [52]: id(b)
Out[52]: 2420982884400
In [53]: a = -5
b = -5
In [54]: id(a)
Out[54]: 2420901374064
In [55]: id(b)
Out[55]: 2420901374064
In [56]: a = -6
b = -6
In [57]: id(a)
Out[57]: 2420982884240
In [58]: id(b)
Out[58]: 2420982884752
In [60]: a = 'haldia'
b = 'haldia'
In [61]: id(a)
Out[61]: 2420982910320
In [62]: id(b)
Out[62]: 2420982910320
In [64]: id(a)
Out[64]: 2420982917600
In [65]: id(b)
Out[65]: 2420982915680
In [66]: a = 'haldia_inst_tech'
b = 'haldia_inst_tech'
In [67]: id(a)
Out[67]: 2420982917680
In [68]: id(b)
Out[68]: 2420982917680
In [6]: # Valid Identifiers yield Same IDs.
# Invalid Identifiers creates Different IDs.
Mutability
In [70]: L = [1, 2, 3]
In [71]: id(L)
Out[71]: 2420982908608
In [72]: id(1)
Out[72]: 2420901374256
In [73]: id(L[0])
Out[73]: 2420901374256
In [74]: id(2)
Out[74]: 2420901374288
In [75]: id(3)
Out[75]: 2420901374320
In [76]: L[2] = 1
In [77]: L
Out[77]: [1, 2, 1]
In [78]: id(L[2])
Out[78]: 2420901374256
In [83]: a = 'Hello'
In [84]: id(a)
Out[84]: 2420982941168
In [85]: a = a + 'World'
In [86]: a
Out[86]: 'HelloWorld'
In [87]: id(a)
Out[87]: 2420982924208
In [88]: T = (1, 2, 3)
In [89]: id(T)
Out[89]: 2420982094144
In [90]: T = T + (5, 6)
In [91]: T
Out[91]: (1, 2, 3, 5, 6)
In [92]: id(T)
Out[92]: 2420981669536
In [93]: L = [1, 2, 3]
In [94]: id(L)
Out[94]: 2420982910464
In [95]: L.append(4)
In [96]: L
Out[96]: [1, 2, 3, 4]
In [97]: id(L)
Out[97]: 2420982910464
In [100… L1 = L
In [101… L1
Out[101… [1, 2, 3]
In [102… id(L)
Out[102… 2420982893056
In [103… id(L1)
Out[103… 2420982893056
In [104… L1.append(4)
In [105… id(L1)
Out[105… 2420982893056
In [106… L1
Out[106… [1, 2, 3, 4]
In [107… L
Out[107… [1, 2, 3, 4]
Cloning
In [108… L
Out[108… [1, 2, 3, 4]
In [109… L1 = L[:]
In [110… id(L)
Out[110… 2420982893056
In [111… id(L1)
Out[111… 2420982138432
In [112… L1.append(5)
In [113… L1
Out[113… [1, 2, 3, 4, 5]
In [114… L
Out[114… [1, 2, 3, 4]
In [117… a
In [119… a
In [121… a
In [123… a = [1, 2]
In [124… b = [3, 4]
In [125… c = (a, b)
In [126… c
In [127… id(a)
Out[127… 2420982924544
In [128… id(b)
Out[128… 2420982150336
In [129… id(c)
Out[129… 2420981226688
In [131… c
In [132… id(a)
Out[132… 2420982924544
In [133… id(c)
Out[133… 2420981226688
In [134… L = [1, 2, 3]
In [135… id(L)
Out[135… 2420982145216
In [136… L = L + [4, 5]
In [137… id(L)
Out[137… 2420982891072
append , edit , insert , extend modifies list in place (mutable); same memory
address.
In [139… c
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_14692/3447556373.py in <module>
----> 1 c[0] = c[0] + [5,6]
In [141… c
In [142… a
Out[142… [100, 2]
In [143… a = a + [5, 6]
In [144… a
Out[144… [100, 2, 5, 6]
In [145… c
Advantages:
• Reusability
• Avoids repetition
2. Decomposition
• Splits systems into modules.
• Each module offers specific functionality.
• Modules can impact others.
In [ ]: # Components of a Function
def function_name(parameters):
"""docstring"""
statement(s)
function_name(values)
Let's create a function
In [5]: # Check if number is even/odd
def is_even(number):
"""
This function tells if a given number is odd or even
Input - any valid integer
Output - odd/even
Created By - Saurabh
Last edited - 22 Oct 2022
"""
if number % 2 == 0:
return "Even"
else:
return "Odd"
Odd
Even
Odd
Even
Odd
Even
Odd
Even
Odd
Even
In [7]: print(is_even.__doc__)
In [8]: print.__doc__
In [9]: type.__doc__
In [11]: pwd
In [1]: # Creating `is_even.py` file w `is_even()` function ---> to import into Jupyter Notebo
In [14]: func_demo.is_even(34)
Out[14]: 'Even'
In [15]: func_demo.is_even("Hello")
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_21972/3239686104.py in <module>
----> 1 func_demo.is_even("Hello")
In [18]: fd.is_even("Hello")
Arguments:
• Values passed at func call.
• Inputs during function invocation.
func(arg1, arg2)
1. Default Argument
2. Positional Argument
3. Keyword Argument
In [21]: power(2, 3)
Out[21]: 8
In [22]: power(3, 2)
Out[22]: 9
In [23]: power(3)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_21972/512475094.py in <module>
----> 1 power(3)
In [24]: power()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_21972/964676364.py in <module>
----> 1 power()
In [26]: power(2,3)
Out[26]: 8
In [27]: power(2)
Out[27]: 2
In [28]: power()
Out[28]: 1
power(b=2, a=3)
Out[30]: 9
In [33]: flexi(1)
In [34]: flexi(1, 2)
2
In [35]: flexi(1, 2, 3)
In [36]: flexi(1, 2, 3, 4, 5, 6, 7, 8, 9)
362880
In [38]: flexi(1, 2, 3, 4, 5)
(1, 2, 3, 4, 5)
<class 'type'>
120
def func(*args)
**kwargs : Variable-length keyword arguments.
def func(**kwargs)
In [1]: # *args
# Pass variable non-keyword args to func
def multiply(*kwargs):
product = 1
for i in kwargs:
product *= i
print(kwargs)
return product
In [3]: # **kwargs
# Pass any no. of keyword args (key-value pairs).
# Acts like a dict.
def display(**salman):
for (key, value) in salman.items():
print(key, '->', value)
In [4]: display(india='delhi', srilanka='colombo', nepal='kathmandu', pakistan='islamabad'
Examples:
In [39]: # Functions as Arguments
def func_a():
print("inside func_a: ")
# No return value ---> `None`
def func_b(y):
print("inside func_b: ")
return y
def func_c(z):
print("inside func_c: ")
return z()
print(func_a())
print(5 + func_b(2))
print(func_c(func_a))
inside func_a:
None
inside func_b:
7
inside func_c:
inside func_a:
None
def f(y):
x = 1 # Local x
x += 1
print(x)
x = 5 # Global x
f(x) # Calls f()
print(x)
# Functions have local scope. Global vars coexist but are not affected.
2
5
x = 5
g(x)
print(x) # x = 5 remains unchanged
5
6
5
~\AppData\Local\Temp/ipykernel_21972/2056165220.py in h(y)
1 def h(y):
----> 2 x += 1 # leads to an error without line "global x" inside h
3
4 x = 5
5 h(x)
x = 3
z = f(x)
print("in main proram scope: z =", z)
print("in main program scope: x =", x)
in f(x): x = 4
in main proram scope: z = 4
in main program scope: x = 3
Nested Functions
In [46]: def f():
print("Inside f")
def g():
print("Inside g")
g()
In [47]: f()
Inside f
Inside g
In [48]: g()
# Nested Function stays Abstracted/Hidden from main program
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_21972/1648027428.py in <module>
----> 1 g()
2
3 # Nested Function stays abstracted/hidden from the main program.
In [ ]: f()
# Infinite Loop ---> Code will Crash ---> Kernel Dead
in g(x): x = 4
Functions too
In [3]: # Functions as Objects
In [5]: f(2)
Out[5]: 4
In [6]: f(4)
Out[6]: 16
In [7]: x = f # aliasing
In [9]: x(2)
Out[9]: 4
In [10]: x(4)
Out[10]: 16
In [12]: f(2)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_22548/3272250079.py in <module>
----> 1 f(2)
Out[13]: 4
In [14]: type(x)
Out[14]: function
In [15]: L = [1, 2, 3, 4]
L
Out[15]: [1, 2, 3, 4]
In [16]: L = [1, 2, 3, 4, x]
L
Out[17]: 9
So What?
1. Renaming Function: def new_name(old_name):
inside func_c
inside func_a
None
In [21]: # Returning a Function + Nested Calling
def f():
def x(a, b):
return a + b
return x
val = f()(3, 4)
print(val)
Out[12]: 2159425052320
In [7]: # reassign
x = square
id(x)
x(3)
Out[7]: 9
In [8]: a = 2
b = a
b
Out[8]: 2
In [10]: square(3)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[10], line 1
----> 1 square(3)
In [13]: # Storing
L = [1, 2, 3, 4, square]
L[-1](3)
Out[13]: 9
In [14]: s = {square}
s
Benefits of Functions
• Modularity: Self-contained code, modularizes login.
• Reusability: Write once, use forever.
• Readability: Organized and coherent.
Recursion
Function calls itself
Advantages:
• No loops needed.
• Solves problems without iteration.
Iterative Vs Recursive
a*b
In [2]: # iterative method
def multiply(a, b):
result = 0
for i in range(b):
result += a
print(result)
multiply(3, 4)
12
# 2. Decompose: Break main problem into smaller subproblems until base case is reached
12
In [6]: # Palindrome
def palin(text):
if len(text) <= 1:
print("palindrome")
else:
if text[0] == text[-1]:
palin(text[1:-1])
else:
print("not a palindrome")
In [7]: palin("madam")
palindrome
In [8]: palin("malayalam")
palindrome
In [9]: palin("python")
not a palindrome
In [10]: palin("abba")
palindrome
def fib(m):
if m == 0 or m == 1:
return 1
else:
return fib(m-1) + fib(m-2)
print(fib(12)) # T = O(2^n)
# Key Concepts:
# Fibonacci Sequence
# Reproduction Rate
# Population Growth
233
233
0.0
In [13]: print(fib(24))
print(time.time() - start)
75025
0.22108960151672363
In [14]: print(fib(36))
print(time.time() - start)
24157817
6.103978157043457
Memoization
Memoization refers to remembering method call results based on inputs.
7778742049
7778742049
6.157997369766235
2255915161619363308725126950360720720460113249137581905886388664184746277386868
83405015987052796968498626
6.17300009727478
7033036771142281582183525487718354977018126983635873274260490508715453711819693
3579742249494562611733487750449241765991088186363265450223647106012053374121273
867339111198139373125598767690091902245245323403501
6.189241170883179
In [20]: print(d) # Dict in memory, execution time reduced
# PowerSet: Given set S, return power set P(S) (all subsets of S).
# Input: String
# Output: Array of Strings (power set)
# Example: S = "123", P(S) = ['', '1', '2', '3', '12', '13', '23', '123']
def powerset1(xs):
res = [[]]
if len(xs) <= 0:
return "Please Enter a parameter"
if len(xs) == 1:
res.append([xs[0]])
return res
else:
z = []
for i in powerset1(xs[1:]):
z.append(i)
z.append([xs[0]] + i)
return z
final = powerset1('123')
print(final)
print(len(final))
[[], ['1'], ['2'], ['1', '2'], ['3'], ['1', '3'], ['2', '3'], ['1', '2', '3']]
8
Lambda Functions
Anonymous functions.
Example: lambda a, b: a + b
Out[3]: 81
In [4]: a = lambda x, y : x + y
a(4, 5)
Out[4]: 9
In [5]: type(a)
Out[5]: function
Normal function:
• Has a return value.
• Multi-line.
• Encourages code reusability via named functions.
In [7]: # Why?
Out[9]: True
In [10]: b('banana')
Out[10]: False
Out[11]: 'Odd'
In [12]: b(2)
Out[12]: 'Even'
In [13]: # HOF
In [14]: L = [11, 14, 27, 21, 23, 56, 78, 39, 45, 29, 28, 30]
# Even Sum
# Odd Sum
# Div3 Sum
def return_sum(L):
even_sum = 0
odd_sum = 0
div3_sum = 0
for i in L:
if i%2 == 0:
even_sum = even_sum + i
for i in L:
if i%2 != 0:
odd_sum = odd_sum + i
for i in L:
if i%3 == 0:
div3_sum = div3_sum + i
return(even_sum, odd_sum, div3_sum)
print(return_sum(L))
206
195
240
Higher-Order Functions
1. Map
2. Filter
3. Reduce
1. Map
Applies a function to each item in iterable .
Syntax:
map(function, iterable)
Returns an Iterator of results.
In [18]: L = [1, 2, 3, 4, 5, 6, 7]
L
Out[18]: [1, 2, 3, 4, 5, 6, 7]
In [19]: map(lambda x : x * 2, L)
In [22]: students = [
{
"name" : "Jacob Martin",
"Father name" : "Ros Martin",
"Address" : "123 Hills Street",
},{
"name" : "Angela Stevens",
"Father name" : "Robert Stevens",
"Address" : "3 Upper Street London",
},{
"name" : "Ricky Smart",
"Father name" : "William Smart",
"Address" : "Unknown",
}
]
list(map(lambda student : student["name"], students))
2. Filter
Applies a function to sequence .
Syntax:
filter(function, sequence)
Returns elements where function is True .
In [24]: L
Out[24]: [1, 2, 3, 4, 5, 6, 7]
Out[25]: [5, 6, 7]
Syntax:
In [30]: L
Out[30]: [1, 2, 3, 4, 5, 6, 7]
In [31]: functools.reduce(lambda x, y : x + y, L)
Out[31]: 28
Out[33]: 58
Out[34]: 11
OBJECT-ORIENTED PROGRAMMING
OOP is a programming paradigm using objects & classes.
The Observation
In [5]: L = [1, 2, 3, 4]
In [6]: L
Out[6]: [1, 2, 3, 4]
In [7]: L.upper()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[7], line 1
----> 1 L.upper()
In [11]: city.append('a')
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[11], line 1
----> 1 city.append('a')
In [14]: a = 3
In [15]: a.upper()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[15], line 1
----> 1 a.upper()
What is OOP?
The PROBLEM!
Generality to Specificity
The Core Fundamental Feature of OOP:
"One of the core fundamental features of OOP is the ability to create Custom Data
Types tailored to specific needs (e.g., LinkedIn platform).
Without OOP:
With OOP:
• Object
• Class
• Polymorphism
• Encapsulation
• Inheritance
• Abstraction
CLASS
• Blueprint for objects
• Defines object behavior
In [23]: type(a)
Out[23]: int
In [24]: L
Out[24]: [1, 2, 3, 4]
In Python,
Datatype = Class
Class:
1. Attributes: Data/Properties
2. Methods: Functions/Behavior
# Examples:
+-------------------------+
| - Car |
|-------------------------|
| - Color |
| - mileage | # attributes/data (private)
| - engine |
|-------------------------|
| + cal_avg_speed |
| + open_airbags | # methods/functions (public)
| + show_gps |
+-------------------------+
OBJECT
Object is an instance of a Class
In [ ]: # Object Examples:
In [37]: L
Out[37]: [1, 2, 3]
In [38]: L = list()
In [39]: L
Out[39]: []
In [41]: city
Out[41]: ''
Out[46]: 0
In [48]: L
Out[48]: [1]
self.__pin = ""
self.__balance = 0
@staticmethod
def get_counter():
return Atm.__counter
@staticmethod
def set_counter(new):
if type(new) == int:
Atm.__counter = new
else:
print('Not Allowed')
def get_pin(self):
return self.__pin
def create_pin(self):
self.__pin = input("Enter your pin: ")
print("Pin set successfully")
def deposit(self):
temp = input("Enter your pin: ")
if temp == self.__pin:
amount = int(input("Enter the amount: "))
self.__balance = self.__balance + amount
print("Deposit successful")
else:
print("Invalid pin")
def withdraw(self):
temp = input("Enter your pin: ")
if temp == self.__pin:
amount = int(input("Enter the amount: "))
if amount <= self.__balance:
self.__balance = self.__balance - amount
print("Operation successful")
else:
print("insufficient funds")
else:
print("invalid pin")
def check_balance(self):
temp = input("Enter your pin: ")
if temp == self.__pin:
print(self.__balance)
else:
print("invalid pin")
In [75]: sbi = Atm()
In [55]: sbi.deposit()
In [56]: sbi.check_balance()
In [57]: sbi.withdraw()
In [58]: sbi.check_balance()
2104822250560
In [60]: hdfc.deposit()
In [61]: sbi.check_balance()
Enter your pin: 1234
25000
In [62]: hdfc.check_balance()
In [66]: dir(int)
Out[66]: ['__abs__',
'__add__',
'__and__',
'__bool__',
'__ceil__',
'__class__',
'__delattr__',
'__dir__',
'__divmod__',
'__doc__',
'__eq__',
'__float__',
'__floor__',
'__floordiv__',
'__format__',
'__ge__',
'__getattribute__',
'__getnewargs__',
'__gt__',
'__hash__',
'__index__',
'__init__',
'__init_subclass__',
'__int__',
'__invert__',
'__le__',
'__lshift__',
'__lt__',
'__mod__',
'__mul__',
'__ne__',
'__neg__',
'__new__',
'__or__',
'__pos__',
'__pow__',
'__radd__',
'__rand__',
'__rdivmod__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__rfloordiv__',
'__rlshift__',
'__rmod__',
'__rmul__',
'__ror__',
'__round__',
'__rpow__',
'__rrshift__',
'__rshift__',
'__rsub__',
'__rtruediv__',
'__rxor__',
'__setattr__',
'__sizeof__',
'__str__',
'__sub__',
'__subclasshook__',
'__truediv__',
'__trunc__',
'__xor__',
'as_integer_ratio',
'bit_count',
'bit_length',
'conjugate',
'denominator',
'from_bytes',
'imag',
'numerator',
'real',
'to_bytes']
Magic Methods
Predefined (e.g., __and__ , __bool__ , __float__ )
If World == Class
God == Programmer
Humans == Object
But, ---> D E A T H
2104822251232
In [60]: id(sbi)
Out[60]: 2658947923632
2104822248976
In [80]: id(hdfc)
Out[80]: 2104822248976
In [82]: id(sbi)
Out[82]: 2104822251232
def __str__(self):
return"{}/{}".format(self.num, self.den)
In [91]: x = Fraction(4, 5)
In [92]: print(x)
4/5
In [93]: type(x)
Out[93]: __main__.Fraction
In [94]: y = Fraction(5, 6)
In [95]: print(y)
5/6
In [96]: L = [1, 2, 3, x]
In [97]: L
In [98]: print(x + y)
49/30
In [99]: print(x - y)
-1/30
In [100… print(x * y)
20/30
In [101… print(x / y)
24/25
ENCAPSULATION
Instance Variable
Unique value per object.
2104822248160
In [107… sbi.balance
Out[107… 0
Python ---> Use `__` prefix (e.g., `self.__pin`, `self.__balance`) for data hiding
2104822398544
Class Design ---> Hide (Encapsulate ) data members & Hide non-public methods.
Atm
__pin
Why?
Name mangling to prevent direct access.
In [115… sbi.__balance = "abcdefg"
In [116… sbi.deposit()
`__balance` creation doesn't affect code logic due to `__balance` not being utilized.
sbi._Atm__balance = "wgwwg"
2104822263728
In [134… sbi.get_pin()
Out[134… '1234'
In [135… sbi.set_pin("235235")
Pin changed
In [136… sbi.get_pin()
Out[136… '235235'
In [146… sbi.set_pin(5.6)
Pin Changed
In [147… sbi.get_pin()
Out[147… 5.6
Input values are processed through functions this allows logical control over data
2104815840384
In [152… sbi.set_pin(5.6)
Not allowed
Pin changed
In [155… Summary:
The whole idea is to prevent direct public access to data to avoid breaches.
Solution:
Hide data using `__`.
Protecting our Data, Access Data via Functions, Set Data via Logic.
Concept ---> Encapsulation i.e. Protect and Access data through controlled methods
Reference Variable
In [157… Atm()
2104822426656
sbi = Atm()
2104851242224
`sbi` ≠ object
`sbi` is a reference variable
`sbi` points to the memory address of the actual object `Atm()`
Pass by Reference
In [194… class Customer:
def __init__(self, name):
self.name = name
cust = Customer("Nitish")
print(cust.name)
Nitish
def greet(customer):
print("Hello", customer.name)
cust = Customer("Nitish")
greet(cust)
Hello Nitish
def greet(customer):
if customer.gender == "Male":
print("Hello", customer.name, "sir")
else:
print("Hello", customer.name, "ma'am")
In Python, all data types(e.g., int, str, list, dict) are object.
Custom class instances = objects too.
def greet(customer):
if customer.gender == "Male":
print("Hello", customer.name, "sir")
else:
print("Hello", customer.name, "ma'am")
learned 2 concepts
1. Class Object as Argument: Pass own class objects as function
arguments.
class Customer:
def __init__(self, name):
self.name = name
def greet(customer):
pass
cust = Customer("Ankita")
print(id(cust))
2104851621072
cust = Customer("Ankita")
print(id(cust))
greet(cust)
2104856055520
2104856055520
In [221… # Aliasing:
a = 3
b = a
In [222… id(a)
Out[222… 2104739692848
In [223… id(b)
Out[223… 2104739692848
In [224… (customer)----->(Object)<--------(cust)
| |
| |
| |
+---------------(greet)---------------+
def greet(customer):
customer.name = "Nitish"
print(customer.name)
cust = Customer("Ankita")
greet(cust)
Nitish
def greet(customer):
customer.name = "Nitish"
print(customer.name)
cust = Customer("Saurabh")
greet(cust)
print(cust.name)
Nitish
Nitish
Passing obj to func ---> func modifies obj ---> Original obj reflects changes.
def greet(customer):
print(id(customer))
customer.name = "Nitish"
print(customer.name)
print(id(customer))
cust = Customer("Saurabh")
print(id(cust))
greet(cust)
print(cust.name)
2104856063344
2104856063344
Nitish
2104856063344
Nitish
L1 = [1, 2, 3, 4]
print(id(L1))
print(L1)
change(L1)
print(L1)
2104871192768
[1, 2, 3, 4]
2104871192768
2104871192768
[1, 2, 3, 4, 5]
change(L1[:])
print(L1)
2104855834176
[1, 2, 3, 4]
2104820330304
2104820330304
[1, 2, 3, 4]
L1 = (1, 2, 3, 4)
print(id(L1))
print(L1)
change(L1)
print(L1)
2104855459008
(1, 2, 3, 4)
2104855459008
2104820526208
(1, 2, 3, 4)
In [235… Conclusion ---> In Pass by Reference, changes to mutable data types (objects) affect
But in Immutable Types, No effect on the original (int, str) when
Collection of Objects
In [236… class Customer:
def __init__(self, name, age):
self.name = name
self.age = age
c1 = Customer("Nitish", 34)
c2 = Customer("Ankit", 45)
c3 = Customer("Neha", 32)
L = [c1, c2, c3]
for i in L:
print(i)
In [239… for i in L:
print(i.name, i.age)
Nitish 34
Ankit 45
Neha 32
def intro(self):
print("I am", self.name, "and I am", self.age)
c1 = Customer("Nitish", 34)
c2 = Customer("Ankit", 45)
c3 = Customer("Neha", 32)
for i in L:
i.intro()
I am Nitish and I am 34
I am Ankit and I am 45
I am Neha and I am 32
***Object Collection:** Lists, tuples, dicts can store custom class objects*
In [4]: c1 = Atm()
1560691660048
In [5]: c2 = Atm()
1560691670224
In [6]: c3 = Atm()
1560691668832
In [7]: c1.sno
Out[7]: 1
In [8]: c2.sno
Out[8]: 1
In [9]: c3.sno
Out[9]: 1
Variable Types
1. Instance Variable: Unique per object (e.g., pin, balance, GPA).
2. Static/Class Variable: Same across objects (e.g., IFSC code, Degree no.).
In [14]: # Now for this ATM Code we will create a Static Variable
`self.sno` = `Atm.counter`
`Atm.counter` += `1`
In [39]: c1 = Atm()
1560691776512
In [40]: c2 = Atm()
1560691780544
In [41]: c3 = Atm()
1560691770128
In [42]: c1.sno
Out[42]: 1
In [43]: c2.sno
Out[43]: 2
In [44]: c3.sno
Out[44]: 3
Out[45]: 4
In [46]: c2.counter
Out[46]: 4
In [47]: c1.counter
Out[47]: 4
In [48]: Atm.counter
Out[48]: 4
In [52]: Atm.counter
Out[52]: 1
In [54]: c1 = Atm()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[54], line 1
----> 1 c1 = Atm()
def __init__(self):
self.__pin = ""
self.__balance = 0
self.sno = Atm.__counter
Atm.__counter += 1
In [56]: Atm.get_counter()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[56], line 1
----> 1 Atm.get_counter()
`@staticmethod` ---> Methods that doesn’t require object instance for access.
@staticmethod
def get_counter():
return Atm.__counter
@staticmethod
def set_counter(new):
if type(new) == int:
Atm.counter = new
else:
print('Not Allowed')
In [64]: Atm.get_counter()
Out[64]: 1
In [69]: Atm.set_counter(5)
In [70]: Atm.get_counter()
Out[70]: 5
Relationships
1. Aggregation (Has-A)
2. Inheritance (Is-A)
# Inheritance Ex:
+--------------------+ 1 +-------------------+
| Department |<------------------->| Employee |
+--------------------+ +-------------------+
| - name: str | | - name: str |
| - location: str | | - employeeID: int |
+--------------------+ +-------------------+
| + addEmployee() | | + getDetails() |
| + removeEmployee() | | + updateDetails() |
+--------------------+ +-------------------+
1..* 1..*
class Address:
def __init__(self, city, pincode, state):
self.city = city
self.pincode = pincode
self.state = state
print(cust.address)
<__main__.Address object at 0x0000016B60C8A950>
In [84]: print(cust.address.city)
Kolkata
In [85]: print(cust.address.pincode)
700156
When creating an object, if another object is passed, the new object behaves like
the passed one.
class Address:
def __init__(self, city, pincode, state):
self.city = city
self.pincode = pincode
self.state = state
122011
INHERITANCE
Real-world concept; means to inherit.
In [98]: In Inheritence,
def login(self):
print('login')
def register(self):
print('Register')
def enroll(self):
print('Enroll')
def review(self):
print('Review')
stu1 = Student()
stu1.enroll()
stu1.review()
stu1.login()
stu1.register()
Enroll
Review
login
Register
def login(self):
print('login')
def register(self):
print('Register')
def enroll(self):
print('Enroll')
def review(self):
print('Review')
u = User()
u.enroll()
u.review()
u.login()
u.register()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[103], line 22
18 print('Review')
20 u = User()
---> 22 u.enroll()
23 u.review()
24 u.login()
+-------------+
| Animal |
+-------------+
| - name: str |
| - age: int |
+-------------+
| + eat() |
| + sleep() |
+-------------+
/ | \
/ | \
/ | \
+----------+ +----------+ +---------+
| Dog | | Cat | | Hamster |
+----------+ +----------+ +---------+
| - breed | | - color | | - size |
+----------+ +----------+ +---------+
| + bark() | | + meow() | | + run() |
+----------+ +----------+ +---------+
Ex 1 - Inheriting Constructer
In [108… # Constructor Ex:
class Phone:
def __init__(self, price, brand, camera):
print ("Inside phone constructor")
self.price = price
self.brand = brand
self.camera = camera
class SmartPhone(Phone):
pass
In [109… print(s.brand)
Apple
class SmartPhone(Phone):
pass
POLYMORPHISM
Ex 3 - Polymorphism
In [117… # Method Overriding:
class Phone:
def __init__(self, price, brand, camera):
print ("Inside phone constructor")
self.__price = price
self.brand = brand
self.camera = camera
def buy(self):
print("Buying a phone")
class SmartPhone(Phone):
def buy(self):
print("Buying a smartphone")
s.buy()
Method Overriding
Child Class method overrides Parent Class method if they have the same name.
Polymorphism
• Method Overriding
• Method Overloading:
• Operator Overloading
Ex - Class Parent
In [124… class Parent:
def __init__(self, num):
self.__num = num
def get_num(self):
return self.__num
class Child(Parent):
def show(self):
print("This is in child class")
son = Child(100,)
print(son.get_num())
son.show()
100
This is in child class
Ex - 2
In [126… class Parent:
def __init__(self, num):
self.__num = num
def get_num(self):
return self.__num
class Child(Parent):
def __init__(self, val, num):
self.__val = val
def get_val(self):
return self.__val
Ex - 3
In [129… class A:
def __init__(self):
self.var1 = 100
def display1(self, var1):
print("class A :", self.var1)
class B(A):
def display2(self, var1):
print("class B :", self.var1)
obj = B()
obj.display1(200)
class A : 100
User of super()
Ex - Super
In [131… class Phone:
def __init__(self, price, brand, camera):
print ("Inside phone constructor")
self.__price = price
self.brand = brand
self.camera = camera
def buy(self):
print ("Buying a phone")
class SmartPhone(Phone):
def buy(self):
print ("Buying a smartphone")
super().buy() # Call parent buy()
s.super().buy()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[132], line 3
1 # super keyword doesn't work outside the class
----> 3 s.super().buy()
super keyword
1. Accesses parent class methods
2. Accesses parent class constructor
class SmartPhone(Phone):
def __init__(self, price, brand, camera, os, ram):
print('Inside smartphone constructor')
super().__init__(price, brand, camera)
self.os = os
self.ram = ram
print ("Inside smartphone constructor")
Ex - Super
In [143… class Parent:
def __init__(self, num):
self.__num = num
def get_num(self):
return self.__num
class Child(Parent):
def __init__(self, num, val):
super().__init__(num)
self.__val = val
def get_val(self):
return self.__val
100
200
class Child(Parent):
def __init__(self):
super().__init__()
self.var = 200
def show(self):
print(self.num)
print(self.var)
son = Child()
son.show()
100
200
Access parent attribute in child class via self .
class Child(Parent):
def __init__(self):
super().__init__()
self.__var = 10
def show(self):
print("Child:", self.__var)
dad = Parent()
dad.show()
son = Child()
son.show()
Parent: 100
Child: 10
In [ ]: # Inheritance Summary:
Class Inheritance allows one class to inherit from another; enhances code reuse
Types of inheritance
1. Single Inheritance
2. Multilevel Inheritance
3. Hierarchical Inheritance
4. Multiple Inheritance (Diamond Problem)
5. Hybrid Inheritance
+------------------+
| Animal |
+------------------+
| - name: str |
| - age: int |
+------------------+
| + eat() |
| + sleep() |
+------------------+
/ \
/ \
/ \
+----------+ +----------+
| Dog | | Cat |
+----------+ +----------+
| - breed | | - color |
+----------+ +----------+
| + bark() | | + meow() |
+----------+ +----------+
\ /
\ /
\ /
+------------------+
| PetOwner |
+------------------+
| - ownerName: str |
+------------------+
| + feed() |
| + play() |
+------------------+
Hybrid Inheritance
+------------------+
| Animal |
+------------------+
| - name: str |
| - age: int |
+------------------+
| + eat() |
| + sleep() |
+------------------+
/ \
/ \
/ \
+-------------+ +----------------+
| Dog | | Cat |
+-------------+ +----------------+
| - breed | | - color |
+-------------+ +----------------+
| + bark() | | + meow() |
+-------------+ +----------------+
/ \ / \
/ \ / \
/ \ / \
+-------------+ +-------------+ +----------------+ +----------------+
| Labrador | | Bulldog | | Siamese | | Persian
+-------------+ +-------------+ +----------------+ +----------------+
| - size: str | | - size: str | | - pattern: str | | - furType: str
+-------------+ +-------------+ +----------------+ +----------------+
| + fetch() | | + guard() | | + purr() | | + groom()
+-------------+ +-------------+ +----------------+ +----------------+
\ /
\ /
\ /
+--------------------+
| PetOwner |
+--------------------+
| - ownerName: str |
+--------------------+
| + feed() |
| + play() |
+--------------------+
In [ ]: # Multiple Inheritance:
class SmartPhone(Phone):
pass
class Phone(Product):
def __init__(self, price, brand, camera):
print ("Inside phone constructor")
self.__price = price
self.brand = brand
self.camera = camera
def buy(self):
print ("Buying a phone")
class SmartPhone(Phone):
pass
s.buy()
s.review()
p.review()
Inside phone constructor
Inside phone constructor
Buying a phone
Product customer review
Product customer review
Ex - Hierarchical Inheritence
In [8]: class Phone:
def __init__(self, price, brand, camera):
print ("Inside phone constructor")
self.__price = price
self.brand = brand
self.camera = camera
def buy(self):
print ("Buying a phone")
def return_phone(self):
print("Returning a phone")
class SmartPhone(Phone):
pass
class FeaturePhone(Phone):
pass
Ex - Multiple Inheritence
In [10]: class Phone:
def __init__(self, price, brand, camera):
print ("Inside phone constructor")
self.__price = price
self.brand = brand
self.camera = camera
def buy(self):
print ("Buying a phone")
class Product:
def review(self):
print ("Customer review")
s.buy()
s.review()
Inside phone constructor
Buying a phone
Customer review
class Product:
def buy(self):
print ("Product buy method")
Ex - Multilevel Inheritence
In [16]: class A:
def m1(self):
return 20
class B(A):
def m1(self):
return 30
def m2(self):
return 40
class C(B):
def m2(self):
return 20
obj1 = A()
obj2 = B()
obj3 = C()
print(obj1.m1() + obj3.m1() + obj3.m2())
70
In [2]: class A:
def m1(self):
return 20
class B(A):
def m1(self):
val = super().m1() + 30
return val
class C(B):
def m1(self):
val = self.m1() + 20
return val
obj = C()
print(obj.m1())
Polymorphism
1. Method Overriding
2. Method Overloading
3. Operator Overloading
obj = Geometry()
print(obj.area(4))
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[7], line 10
7 return l * b
9 obj = Geometry()
---> 10 print(obj.area(4))
In [8]: Method Overloading in Java ---> Multiple methods, same name, different inputs
in Python ---> No true traditional method overloading.
Same method name = last definition overrides
Workaround:
class Geometry:
def area(self, a, b = 0):
if b == 0:
print('Circle', 3.14 * a * a)
else:
print('Rectangle', a * b)
obj = Geometry()
obj.area(4)
obj.area(4, 5)
Circle 50.24
Rectangle 20
Out[14]: 'helloworld'
In [15]: x = Fraction(3, 4)
y = Fraction(5, 6)
print(x + y)
# Operator Overloading:
38/24
In [21]: 4 + 5
Out[21]: 9
Out[22]: [1, 2, 3, 4, 5]
ABSTRACTION
Hides implementation details; focuses on functionality.
+----------+
| Bank App | (Top-level)
+----------+
/ \
/ \
+---------+ +------------+
| Web App | | Mobile App | (Children)
+---------+ +------------+
Children (Web & Mobile) must inherit Bank App and include security functions.
Ensures secure access to database-related functions.
Senior dev imposes constraints (abstraction) on subclasses (Web & Mobile) for consiste
Abstract Class
Abstract Class Contains at least 1 Abstract Method.
2 Method Types:
1. Abstract: No code.
2. Concrete: Contains code.
class BankApp(ABC):
def database(self):
print('connected to database')
@abstractmethod # decorator
def security(self):
pass
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[8], line 1
----> 1 mob = MobileApp()
TypeError: Can't instantiate abstract class MobileApp with abstract method secu
rity
Bank app inheritance requires implementing abstract methods from the bank app.
In [13]: mob.database()
connected to database
In [14]: mob.mobile_login()
In [15]: mob.security()
mobile security
Senior devs/top-level designers enforce abstract methods (clauses) for child classes
class BankApp(ABC):
def database(self):
print('connected to database')
@abstractmethod
def security(self):
pass
@abstractmethod
def display(self):
pass
TypeError: Can't instantiate abstract class MobileApp with abstract method disp
lay
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[28], line 1
----> 1 obj = BankApp()
TypeError: Can't instantiate abstract class BankApp with abstract methods displ
ay, security
What is a thread?
# Main Thread
while(True):
displayScreen()
Shows fleeting images; appears like the app is running but isn't.
# Main Thread
while(True):
# Heavy Operation
displayScreen()
Results in flicker; heavy op delays screen display.
# Main Thread
while(True):
Image = request(ImageUrl)
displayScreen()
Causes screen to pop up based on network delay.
Solution: Multithreading
# Main Thread
Image = None
def startAnotherThread():
while(True):
Image = request(ImageUrl)
while(True):
displayScreen(Image)
startAnotherThread() fetches images concurrently; displayScreen() shows
available images.
In [3]: # another example ---> multithreading in servers is used to handle multiple requests.
# mechanism involves diff threads for each request
3 threads.
CPU switches between them.
Effect: Concurrency.
Implementation
In [6]: from time import sleep, time
import threading
start = time()
def task(id):
print(f"Sleeping...{id}")
sleep(1)
print(f"Woke up...{id}")
for t in threads:
t.join()
end = time()
print(f"Main Thread Duration: {end - start} sec")
Sleeping...0
Sleeping...1
Sleeping...2
Sleeping...3
Sleeping...4
Sleeping...5
Sleeping...6
Sleeping...7
Sleeping...8
Sleeping...9
Woke up...8Woke up...7
Woke up...5
Woke up...4
Woke up...2
Woke up...1
Woke up...9
Woke up...3
Woke up...6
Woke up...0
balance = 200
lock = threading.Lock()
deposit_thread.start()
withdraw_thread.start()
deposit_thread.join()
withdraw_thread.join()
print(balance)
200
What is an Iteration?
Sequentially processing each item in a collection.
for i in num: # `for` loops iterate over data sequences, executing operations.
print(i)
1
2
3
What is Iterator?
Object for sequential data traversal without storing entire dataset in memory.
for i in L:
print(i*2, end = ',')
2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,5
6,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104,10
6,108,110,112,114,116,118,120,122,124,126,128,130,132,134,136,138,140,142,144,1
46,148,150,152,154,156,158,160,162,164,166,168,170,172,174,176,178,180,182,18
4,186,188,190,192,194,196,198,
import sys
0.8984375
for i in x:
print(i*2, end = ',')
2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,5
6,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104,10
6,108,110,112,114,116,118,120,122,124,126,128,130,132,134,136,138,140,142,144,1
46,148,150,152,154,156,158,160,162,164,166,168,170,172,174,176,178,180,182,18
4,186,188,190,192,194,196,198,
0.046875
Iterator traverses data sequence without full memory load. Processes one item at a
time, discards, and loads next. Efficient looping/traversing.
What is Iterable?
Object that can be iterated over (looped through).
Examples:
In [4]: L = [1, 2, 3]
type(L)
# L is an iterable
Out[4]: list
In [5]: iter(L)
In [6]: type(iter(L))
Out[6]: list_iterator
In [ ]: # Trick
In [1]: a = 2
a
Out[1]: 2
In [2]: for i in a:
print(i)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[2], line 1
----> 1 for i in a:
2 print(i)
In [3]: dir(a)
Out[3]: ['__abs__',
'__add__',
'__and__',
'__bool__',
'__ceil__',
'__class__',
'__delattr__',
'__dir__',
'__divmod__',
'__doc__',
'__eq__',
'__float__',
'__floor__',
'__floordiv__',
'__format__',
'__ge__',
'__getattribute__',
'__getnewargs__',
'__gt__',
'__hash__',
'__index__',
'__init__',
'__init_subclass__',
'__int__',
'__invert__',
'__le__',
'__lshift__',
'__lt__',
'__mod__',
'__mul__',
'__ne__',
'__neg__',
'__new__',
'__or__',
'__pos__',
'__pow__',
'__radd__',
'__rand__',
'__rdivmod__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__rfloordiv__',
'__rlshift__',
'__rmod__',
'__rmul__',
'__ror__',
'__round__',
'__rpow__',
'__rrshift__',
'__rshift__',
'__rsub__',
'__rtruediv__',
'__rxor__',
'__setattr__',
'__sizeof__',
'__str__',
'__sub__',
'__subclasshook__',
'__truediv__',
'__trunc__',
'__xor__',
'as_integer_ratio',
'bit_count',
'bit_length',
'conjugate',
'denominator',
'from_bytes',
'imag',
'numerator',
'real',
'to_bytes']
In [4]: T = (1, 2, 3)
dir(T)
Out[4]: ['__add__',
'__class__',
'__class_getitem__',
'__contains__',
'__delattr__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__getitem__',
'__getnewargs__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__iter__',
'__le__',
'__len__',
'__lt__',
'__mul__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__rmul__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'count',
'index']
In [5]: T = {1, 2, 3}
dir(T)
Out[5]: ['__and__',
'__class__',
'__class_getitem__',
'__contains__',
'__delattr__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__iand__',
'__init__',
'__init_subclass__',
'__ior__',
'__isub__',
'__iter__',
'__ixor__',
'__le__',
'__len__',
'__lt__',
'__ne__',
'__new__',
'__or__',
'__rand__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__ror__',
'__rsub__',
'__rxor__',
'__setattr__',
'__sizeof__',
'__str__',
'__sub__',
'__subclasshook__',
'__xor__',
'add',
'clear',
'copy',
'difference',
'difference_update',
'discard',
'intersection',
'intersection_update',
'isdisjoint',
'issubset',
'issuperset',
'pop',
'remove',
'symmetric_difference',
'symmetric_difference_update',
'union',
'update']
dir(T)
Out[6]: ['__class__',
'__class_getitem__',
'__contains__',
'__delattr__',
'__delitem__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__getitem__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__ior__',
'__iter__',
'__le__',
'__len__',
'__lt__',
'__ne__',
'__new__',
'__or__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__reversed__',
'__ror__',
'__setattr__',
'__setitem__',
'__sizeof__',
'__str__',
'__subclasshook__',
'clear',
'copy',
'fromkeys',
'get',
'items',
'keys',
'pop',
'popitem',
'setdefault',
'update',
'values']
Check Iterability/Iterator in Python:
1. Iterable:
2. Iterator:
In [17]: L = [1, 2, 3]
# L is not an iterator
dir(L)
Out[17]: ['__add__',
'__class__',
'__class_getitem__',
'__contains__',
'__delattr__',
'__delitem__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__getitem__',
'__gt__',
'__hash__',
'__iadd__',
'__imul__',
'__init__',
'__init_subclass__',
'__iter__',
'__le__',
'__len__',
'__lt__',
'__mul__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__reversed__',
'__rmul__',
'__setattr__',
'__setitem__',
'__sizeof__',
'__str__',
'__subclasshook__',
'append',
'clear',
'copy',
'count',
'extend',
'index',
'insert',
'pop',
'remove',
'reverse',
'sort']
In [18]: iter(L)
In [20]: dir(iter(L))
Out[20]: ['__class__',
'__delattr__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__iter__',
'__le__',
'__length_hint__',
'__lt__',
'__ne__',
'__new__',
'__next__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__setstate__',
'__sizeof__',
'__str__',
'__subclasshook__']
# iter_L is an iterator
for i in num:
print(i)
1
2
3
# Get iterator
iter_num = iter(num)
# Access elements
next(iter_num)
next(iter_num)
next(iter_num)
next(iter_num)
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
Cell In[23], line 10
8 next(iter_num)
9 next(iter_num)
---> 10 next(iter_num)
StopIteration:
In [25]: a = [1, 2, 3]
b = range(1, 11)
c = (1, 2, 3)
d = {1, 2, 3}
e = {0:1, 1:1}
mera_khudka_for_loop(e)
0
1
A Confusing Point
In [26]: num = [1, 2, 3]
iter_obj = iter(num)
print(id(iter_obj), 'Address of iterator 1')
iter_obj2 = iter(iter_obj)
print(id(iter_obj2), 'Address of iterator 2')
def __iter__(self):
return mera_range_iterator(self)
def __iter__(self):
return self
def __next__(self):
current = self.iterable.start
self.iterable.start += 1
return current
1
2
3
4
5
6
7
8
9
10
In [31]: type(x)
Out[31]: __main__.mera_range
In [32]: iter(x)
Benefit: Only one item in memory at a time, managing large datasets efficiently.
Keras uses Image data generators function like iterators, embodying the same
principle.
What is a Generator?
Simple way to create iterators.
In [1]: # Iterable
class mera_range:
def __init__(self, start, end):
self.start = start
self.end = end
def __iter__(self):
return mera_iterator(self)
# Iterator
class mera_iterator:
def __init__(self, iterable_obj):
self.iterable = iterable_obj
def __iter__(self):
return self
def __next__(self):
if self.iterable.start >= self.iterable.end:
raise StopIteration
current = self.iterable.start
self.iterable.start += 1
return current
Solution: Generators.
The Why
# for i in L:
# print(i**2)
import sys
sys.getsizeof(L) # Memory size of list `L`
Out[5]: 800984
Out[6]: 48
this is why iterators are vital; generators are key for easily creating them.
A Simple Example
In [10]: def gen_demo():
yield "first statement"
yield "second statement"
yield "third statement"
In [12]: print(next(gen))
first statement
In [13]: print(next(gen))
second statement
In [14]: print(next(gen))
third statement
In [15]: print(next(gen))
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
Cell In[15], line 1
----> 1 print(next(gen))
StopIteration:
first statement
second statement
third statement
Usage:
yield vs return
Normal Function executes, completes, and is removed from memory; Generator
pauses, retains state (variables), and resumes from pause point.
Key Difference: Generators maintain state and resume execution, while normal
functions are discarded post-execution.
Example 2
In [25]: def square(num):
for i in range(1, num+1):
yield i**2
In [27]: print(next(gen))
In [28]: print(next(gen))
In [29]: print(next(gen))
In [30]: print(next(gen))
16
25
36
49
64
81
100
Range Function (Generator)
In [35]: def mera_range(start, end):
for i in range(start, end):
yield i
15
16
17
18
19
20
21
22
23
24
25
15
16
17
18
19
20
21
22
23
24
25
Generator Expression
Generator expressions simplifies iterator creation with (expr for item in
iterable) .
In [41]: L
Out[41]: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
1
4
9
16
25
36
49
64
81
100
Practical Example
In [49]: import os
import cv2
def image_data_reader(folder_path):
for file in os.listdir(folder_path):
f_array = cv2.imread(os.path.join(folder_path,file))
yield f_array
next(gen)
next(gen)
next(gen)
next(gen)
Out[50]: array([[[ 38, 38, 38],
[ 26, 26, 26],
[ 23, 23, 23],
...,
[198, 198, 198],
[196, 196, 196],
[167, 167, 167]],
...,
1. Ease of Implementation
In [ ]: class mera_range:
def __init__(self, start, end):
self.start = start
self.end = end
def __iter__(self):
return mera_iterator(self)
In [ ]: # iterator
class mera_iterator:
def __init__(self, iterable_obj):
self.iterable = iterable_obj
def __iter__(self):
return self
def __next__(self):
if self.iterable.start >= self.iterable.end:
raise StopIteration
current = self.iterable.start
self.iterable.start += 1
return current
In [ ]: # generator
2. Memory Efficiency
In [55]: L = [x for x in range(100000)]
gen = (x for x in range(100000))
import sys
print('Size of L in memory', sys.getsizeof(L))
print('Size of gen in memory', sys.getsizeof(gen))
Generators save significant memory vs. lists, even when lists are expanded 10x;
use generators for sequential tasks.
3. Infinite Streams
In [56]: def all_even():
n = 0
while True:
yield n
n += 2
Out[57]: 2
4. Chaining Generators
In [58]: def fibonacci_numbers(nums):
x, y = 0, 1
for _ in range(nums):
x, y = y, x + y
yield x
def square(nums):
for num in nums:
yield num**2
print(sum(square(fibonacci_numbers(10))))
4895
File Types:
Process:
f = open('sample.txt', 'w')
f.write('Hello world')
f.close()
f.write('hello')
f = open('sample1.txt', 'w')
f.write('hello world')
f.write('\n how are you?')
f.close()
f = open('sample.txt', 'w')
f.write('salman khan')
f.close()
File Access & RAM Interaction: File loaded from disk (ROM) to RAM buffer.
File Operations & Modes: Modes (e.g., 'w' for write) determine file interactions
( f.write('salman khan') writes to RAM).
f = open('/content/sample1.txt', 'a')
f.write('\nI am fine')
f.close()
f = open('/content/temp/sample.txt', 'w')
f.writelines(L) # Efficiently writes multiple lines
f.close()
When you use f.close() to close a file, it serves two main purposes:
1. Memory Management:
2. Security:
2. readline() : Reads one line at a time. Good for large files and
sequential processing.
In [ ]: # `read()` Usage
f = open('/content/sample.txt', 'r')
s = f.read()
print(s)
f.close()
hello
hi
how are you
I am fine
In [ ]: # Read up to n chars
f = open('/content/sample.txt', 'r')
s = f.read(10)
print(s)
f.close()
hello
hi
h
In [ ]: # Using `readline()`
f = open('/content/sample.txt', 'r')
print(f.readline(), end='') # Avoid auto newline
print(f.readline(), end='')
f.close()
hello
hi
In [ ]: `read()` Method:
`readline()` Method:
In [ ]: # Count Lines in File Efficiently ---> Avoid readline() per line; use custom code for
f = open('/content/sample.txt', 'r')
while True:
data = f.readline()
if data == '':
break
else:
print(data, end='')
f.close()
hello
hi
how are you
I am fine
Avoids
Benefits:
In [ ]: # `with` Statement
In [ ]: f.write('hello')
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-4-00cba062fa3d> in <module>
----> 1 f.write('hello')
In [ ]: # `f.readline()`
hello
hello
hi
h
ow are you
I am fine
Chunk-Based Processing
• Process in chunks, not all at once. e.g., 10 GB file, 8 GB RAM ---> 2000
chars/chunk.
Advantages
e you
I am
25
fine
30
Binary Files:
Non-Textual Data:
Structured Data:
---------------------------------------------------------------------------
UnicodeDecodeError Traceback (most recent call last)
<ipython-input-23-b662b4ad1a91> in <module>
1 # working with binary file
2 with open('screenshot1.png','r') as f:
----> 3 f.read()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-26-a8e7a73b1431> in <module>
1 # working with other data types
2 with open('sample.txt','w') as f:
----> 3 f.write(5)
10
d = {
'name':'nitish',
'age':33,
'gender':'male'
}
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-34-949b64f1fbe0> in <module>
1 with open('sample.txt','r') as f:
----> 2 print(dict(f.read()))
1. Storage ---> Plain Text Files ideal for simple textual data.
Complex Data (e.g., Python Dicts) contains structured data with
# NOTE: for Simple Data use text files; for Complex Data use serialization libraries o
json.dumps()
Deserialization:
json.loads()
What is JSON?
JavaScript Object Notation
{
"d": {
"results": [
{
"_metadata": {
"type": "Employee Details. Employee"
},
"UserID": "E12012",
"RoleCode": "35"
}
]
}
}
# List to JSON
import json
L = [1, 2, 3, 4]
In [ ]: # Dict to JSON
d = {
'name':'nitish',
'age':33,
'gender':'male'
}
In [ ]: # Deserialization
import json
In [ ]: # Serialize/Deserialize Tuple
import json
t = (1, 2, 3, 4, 5)
In [ ]: # Note: Serialization/Deserialization
Serialize tuple ---> List (using `dump`)
d = {
'student':'nitish',
'marks':[23, 14, 34, 45, 56]
}
# Print format:
# Name: {fname} {lname}
# Age: {age}
# Gender: {gender}
In [ ]: # String Representation
import json
def show_object(person):
if isinstance(person, Person):
return "{} {} age -> {} gender -> {}".format(person.fname, person.lname, person
In [ ]: # Dictionary Representation
import json
def show_object(person):
if isinstance(person, Person):
return {'name':person.fname + ' ' + person.lname, 'age':person.age, 'gender'
In [ ]: # indent attribute
# As a dict
In [ ]: # Deserializing JSON
import json
Until now, we've printed Python Custom Objects (dicts, strings) in specific
formats.
Cross-file Object Usage i.e. Direct use of class/obj from another file not possible.
In [ ]: class Person:
def display_info(self):
print('Hi my name is', self.name, 'and I am ', self.age, 'years old')
In [ ]: p = Person('nitish', 33)
In [ ]: # Pickle Dump
import pickle
with open('person.pkl', 'wb') as f:
pickle.dump(p, f)
In [ ]: # Pickle Load
import pickle
with open('person.pkl', 'rb') as f:
p = pickle.load(f)
p.display_info()
Pickle vs JSON
In [ ]: +-----------------------------------------------+-------------------------------------
| Pickle | JSON
+-----------------------------------------------+-------------------------------------
| |
| Binary format; Python-specific. | Text-based; cross-platform.
| |
| Non-human-readable, Python-only. | Human-readable, interoperable
| |
| Potential security risks with untrusted data. | Safer for untrusted data.
| |
| Efficient for complex Python structures. | Ideal for web APIs, configs,
| |
+-----------------------------------------------+-------------------------------------
Programming Errors
1. Compilation (High-level ---> Machine code)
Syntax Error
In [ ]: a = 5
if a == 3
print('hello')
File "<ipython-input-68-efc58c10458d>", line 2
if a==3
^
SyntaxError: invalid syntax
In [ ]: a = 5
iff a == 3:
print('hello')
In [ ]: a = 5
if a == 3:
print('hello')
L = [1, 2, 3]
L[100]
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-71-c90668d2b194> in <module>
2 # The IndexError is thrown when trying to access an item at an invalid
index.
3 L = [1,2,3]
----> 4 L[100]
import mathi
math.floor(5.3)
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
<ipython-input-73-cbdaf00191df> in <module>
1 # ModuleNotFoundError
2 # The ModuleNotFoundError is thrown when a module could not be found.
----> 3 import mathi
4 math.floor(5.3)
---------------------------------------------------------------------------
NOTE: If your import is failing due to a missing package, you can
manually install dependencies using either !pip or !apt.
d = {'name':'nitish'}
d['age']
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-74-453afa1c9765> in <module>
3
4 d = {'name':'nitish'}
----> 5 d['age']
KeyError: 'age'
1 + 'a'
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-78-2a3eb3f5bb0a> in <module>
1 # TypeError
2 # The TypeError is thrown when an operation or function is applied to a
n object of an inappropriate type.
----> 3 1 + 'a'
int('a')
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-76-e419d2a084b4> in <module>
1 # ValueError
2 # The ValueError is thrown when a function's argument is of an inapprop
riate type.
----> 3 int('a')
print(k)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-79-e3e8aaa4ec45> in <module>
1 # NameError
2 # The NameError is thrown when an object could not be found.
----> 3 print(k)
L = [1, 2, 3]
L.upper()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-80-dd5a29625ddc> in <module>
1 # AttributeError
2 L = [1,2,3]
----> 3 L.upper()
Stacktrace Overview:
• Detailed error info during execution. Includes error type, message, code
location (line/file).
• Purpose is to help identify and fix issues. Crucial for debugging in
development/testing.
Production Considerations:
Best Practices:
• Gracefully handle errors. Show user-friendly messages.
• Use stacktraces internally for debugging only.
Exceptions in Programming:
Runtime issues disrupting execution. Require immediate handling for stability.
Common Issues:
Python Handling
In [ ]: # Create file and write text
In [ ]: # Try-Catch Demo
try:
with open('sample1.txt', 'r') as f:
print(f.read())
except:
print('sorry file not found')
try-except Blocks
Purpose:
Structure:
Best Practices:
In [ ]: # Catching Specific Exceptions ---> informing users about errors, improving user exper
try:
m = 5
f = open('sample1.txt', 'r')
print(f.read())
print(m)
print(5 / 2)
L = [1, 2, 3]
L[100]
except FileNotFoundError:
print('file not found')
except NameError:
print('variable not defined')
except ZeroDivisionError:
print("can't divide by 0")
except Exception as e:
print(e)
In [ ]: # `else` in Try-Except
try:
f = open('sample1.txt', 'r')
except FileNotFoundError:
print('file nai mili')
except Exception:
print('kuch to lafda hai')
else:
print(f.read())
In [ ]: # `finally`
try:
f = open('sample1.txt', 'r')
except FileNotFoundError:
print('file nai mili')
except Exception:
print('kuch to lafda hai')
else:
print(f.read())
finally:
print('ye to print hoga hi')
raise Keyword
Trigger exceptions manually.
# Java Equivalents:
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
<ipython-input-106-5a07d7d89433> in <module>
----> 1 raise ZeroDivisionError('aise hi try kar raha hu')
In [ ]: class Bank:
obj = Bank(10000)
try:
obj.withdraw(15000)
except Exception as e:
print(e)
else:
print(obj.balance)
Python allows creating custom exceptions, which means you can define your own
types of errors.
In [ ]: class MyException(Exception):
def __init__(self, message):
print(message)
class Bank:
def __init__(self, balance):
self.balance = balance
obj = Bank(10000)
try:
obj.withdraw(5000)
except MyException as e:
pass
else:
print(obj.balance)
5000
Benefits:
Implementation:
simple example
In [ ]: class SecurityError(Exception):
def logout(self):
print('logout')
class Google:
try:
obj.login('[email protected]', '1234', 'windows')
except SecurityError as e:
e.logout()
else:
print(obj.name)
finally:
print('database connection closed')
In [ ]: Types of Namespaces:
+---------------------------------------------------------------+---------------------
| Namespace |
+---------------------------------------------------------------+---------------------
| |
| 1. Global Namespace | x = 10 # Global
| | def func():
| Scope ---> Top-level. | print(x)
| |
| Accessible everywhere unless shadowed. |
| |
+---------------------------------------------------------------+---------------------
| |
| 2. Local Namespace | def func():
| | y = 5 # Local
| Scope ---> Function-specific. | print(y)
| |
| Created during function call & Destroyed after function call. |
| |
+---------------------------------------------------------------+---------------------
| |
| 3. Enclosing Namespace | def outer():
| | z = 20
| Scope ---> Outer function for nested functions. | def inner
| | print
| Outer function's scope accessible to inner functions. |
| |
+---------------------------------------------------------------+---------------------
| |
| 4. Builtin Namespace | print(len([1,
| |
| Scope ---> Predefined names (e.g., functions, modules). |
| |
| Always accessible. |
| |
+---------------------------------------------------------------+---------------------
In [ ]: Details:
+---------------------------------+---------------------------------------------+
| Scope | Example
+---------------------------------+---------------------------------------------+
| |
| 1. Local | def outer():
| | x = "enclosing"
| Scope ---> Innermost function. | def inner():
| | x = "local"
| Checked first. | print(x) # local |
| | inner()
| |
+---------------------------------+---------------------------------------------+
| |
| 2. Enclosing | def outer():
| | x = "enclosing"
| Scope ---> Outer function. | def inner():
| | print(x) # enclosing |
| Checked after local. | inner()
| |
+---------------------------------+---------------------------------------------+
| |
| 3. Global | x = "global"
| | def func():
| Scope ---> Module/script-level. | print(x) # global |
| |
| Checked after enclosing. |
| |
+---------------------------------+---------------------------------------------+
| |
| 4. Built-in | def func():
| | print(len([1, 2, 3])) # built-in len() |
| Scope ---> Python's built-ins. | |
| |
| Checked last. |
| |
+---------------------------------+---------------------------------------------+
| |
| Unresolved Names | def func():
| | print(x) # NameError |
| `NameError` if name not found. |
| |
+---------------------------------+---------------------------------------------+
def temp():
b = 3 # Local
print(b)
temp()
print(a)
3
2
a = 2 # Global
def temp():
a = 3 # Local
print(b)
temp()
print(a)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[2], line 9
6 a = 3
7 print(b)
----> 9 temp()
10 print(a)
a = 2 # Global
def temp():
# Accesses global `a`
print(a)
temp()
print(a)
2
2
def temp():
a += 1 # Modifying 'a'
print(a)
temp()
print(a)
---------------------------------------------------------------------------
UnboundLocalError Traceback (most recent call last)
Cell In[4], line 9
6 a += 1
7 print(a)
----> 9 temp()
10 print(a)
In [5]: a = 2
def temp():
global a
a += 1
print(a)
temp()
print(a)
3
3
def temp():
global a # Declare 'a' as global
a = 1 # Modify global 'a'
print(a)
temp()
print(a)
1
1
a = 5 # a is global
temp(5)
print(a)
print(z)
5
5
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[7], line 9
7 temp(5)
8 print(a)
----> 9 print(z)
Built-in Scope
• Python offers built-in functions (e.g., print ) without imports.
• Part of the built-in scope, automatically included in Python.
import builtins
print(dir(builtins))
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'Block
ingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessErr
or', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'Co
nnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'Environment
Error', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'Floating
PointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'Impor
tWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryE
rror', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNo
tFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'Not
ImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'Pe
rmissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'Reso
urceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIter
ation', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabErro
r', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeErr
or', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWar
ning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '__IPYTHO
N__', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__',
'__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bo
ol', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'com
pile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'display',
'divmod', 'enumerate', 'eval', 'exec', 'execfile', 'filter', 'float', 'format',
'frozenset', 'get_ipython', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'h
ex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'licens
e', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oc
t', 'open', 'ord', 'pow', 'print', 'property', 'range', 'repr', 'reversed', 'ro
und', 'runfile', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 's
um', 'super', 'tuple', 'type', 'vars', 'zip']
In [ ]: # Renaming Built-ins
L = [1, 2, 3]
print(max(L)) # Uses built-in max()
print(max(L))
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-68-c19f3451a38f> in <module>
1 # renaming built-ins
2 L = [1,2,3]
----> 3 print(max(L))
4 def max():
5 print('hello')
In [ ]: # Enclosing Scope
def outer():
def inner():
print(a) # a in outer's scope
inner()
print('outer function') # Outer
outer() # Calls outer ---> inner ---> prints a ---> 'outer function'
print('main program') # Main
1
outer function
main program
def outer():
a = 1
def inner():
nonlocal a # Access outer 'a'
a += 1
print('inner', a)
inner()
print('outer', a)
outer()
print('main program')
inner 2
outer 2
main program
Decorators
Function that modifies/extends another function’s behavior.
Usage: Adds functionality (e.g., logging, access control) without altering original
code.
Types:
1. Built-in:
2. User-Defined:
Creation:
def my_decorator(func):
def wrapper():
# Add functionality
return func()
return wrapper
def square(num):
return num ** 2
modify(square, 2)
Out[ ]: 4
In [ ]: def my_decorator(func):
def wrapper():
print('***********************')
func()
print('***********************')
return wrapper
def hello():
print('hello')
def display():
print('hello nitish')
# Manual Decoration
a = my_decorator(hello)
a()
b = my_decorator(display)
b()
***********************
hello
***********************
***********************
hello nitish
***********************
more functions
In [10]: # Closure Example
def outer():
a = 5 # Outer scope var
def inner():
print(a) # Access outer scope var
return inner
b = outer() # b now holds the inner function
b()
Closures
Inner function retains access to outer function's variables post-execution.
Example Code
def outer(outer_var):
def inner():
print(outer_var)
return inner
# Usage
closure = outer('Hello, world!')
closure() # Output: Hello, world!
In [ ]: def my_decorator(func):
def wrapper():
print('***********************')
func()
print('***********************')
return wrapper
@my_decorator
def hello():
print('hello')
hello()
***********************
hello
***********************
import time
def timer(func):
def wrapper(*args):
start = time.time()
func(*args)
print('time taken by', func.__name__, time.time()-start, 'secs')
return wrapper
@timer
def hello():
print('hello world')
time.sleep(2)
@timer
def square(num):
time.sleep(1)
print(num**2)
@timer
def power(a, b):
print(a**b)
hello()
square(2)
power(2, 3)
hello world
time taken by hello 2.0078108310699463 secs
4
time taken by square 1.0098490715026855 secs
8
time taken by power 0.0 secs
# A Big Problem
def square(num):
print(num ** 2)
# Erroneous call
square('hehe')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[11], line 9
6 print(num ** 2)
8 # Function call with incorrect argument type
----> 9 square('hehe')
In [ ]: @checkdt(int)
def square(num):
print(num**2)
In [ ]: def sanity_check(data_type):
def outer_wrapper(func):
def inner_wrapper(*args):
if type(*args) == data_type:
func(*args)
else:
raise TypeError('Ye datatype nahi chalega')
return inner_wrapper
return outer_wrapper
@sanity_check(int)
def square(num):
print(num**2)
@sanity_check(str)
def greet(name):
print('hello', name)
square(2)