Lambda inside function
def power(n):
return lambda a : a ** n
base = power(2)
print("8 powerof 2 = ", base(8)) # 8 powerof 2 = 64
base = power(5)
print("8 powerof 5 = ", base(8)) # 8 powerof 5 = 32768
Filter and Map
a = [100, 2, 8, 60, 5, 4, 3, 31, 10, 11]
filtered = filter (lambda x: x % 2 == 0, a) # [100, 2, 8, 60, 4, 10]
mapped = map (lambda x: x % 2 == 0, a) # [True, True, True, True, False, True, False, False, True, False]
Decorators
def hello_decorator(func):
def inner1(*args, **kwargs):
print("before Execution")
returned_value = func(*args, **kwargs)
print("after Execution")
return returned_value
return inner1
@hello_decorator
def sum_two_numbers(a, b):
print("Inside the function")
return a + b
a, b = 1, 2
print("Sum =", sum_two_numbers(a, b)) # Sum = 3
Chaining Decorators
def decor1(func):
def inner():
x = func()
return x * x
return inner
def decor(func):
def inner():
x = func()
return 2 * x
return inner
@decor1
@decor
def num():
return 10
print(num()) # 400
Decorators with parameters
''' example1 '''
def decorator(*args, **kwargs):
print("Inside decorator")
def inner(func):
print("Hi", kwargs["name"])
func()
return inner
@decorator(name = "abc")
def my_func():
print("Inside actual function")
''' example2 '''
def decodecorator(dataType, message1, message2):
def decorator(fun):
def wrapper(*args, **kwargs):
if all([type(arg) == dataType for arg in args]):
return fun(*args, **kwargs)
return "Invalid Input"
return wrapper
return decorator
@decodecorator(str, "Decorator for 'stringJoin'", "stringJoin started ...")
def stringJoin(*args):
st = ''
for i in args:
st += i
return st
@decodecorator(int, "Decorator for 'summation'\n", "summation started ...")
def summation(*args):
summ = 0
for arg in args:
summ += arg
return summ
print(stringJoin("Hi ", "there. ", "How ", "are ", "you?")) # Hi there. How are you?
print()
print(summation(19, 2, 8, 533, 67, 981, 119)) # 1729
Reduce in Python
from functools import reduce
seq = [1,2,3,4,5,6,7,8]
sum = reduce(lambda x,y: x+y, seq)
print(sum) # 36
Defaults in a function
def append(ele, to=[], a=0):
to.append(ele)
a = ele
return to, a
print(append.__defaults__) # ([], 0)
l1, x = append(10)
print(l1, x) # [10] 10
print(append.__defaults__) # ([10], 0)
l2, y = append(20)
print(l2, y) # [10, 20] 20
# Python's default work like static, as default variables are stored in __defaults__ variable. So, with call by reference (not exactly), it changes the default ones permanently.
Class Instance into a Function
class Foo:
def __init__(self, x):
self.x = x
def outer():
f = Foo(10)
inner1(f)
print(f.x) # 20
inner2(f)
print(f.x) # 20
g = None
inner2(g)
print(g is None) # True
def inner1(f):
f.x = 20
def inner2(f):
f = Foo(30)
outer()
'''
Since outer and inner1 are referring to the same memory, changes made to fields in f in inner1 are reflected in the variable in outer.
When inner2 reassigns f to a new class instance, this creates a separate instance and does not affect the variable in outer.
'''
Sorted with Lambda
def sqr(num):
return num*num
L = [4, -2, 9, -3, 6]
print(sorted(L, key=sqr)) # [-2, -3, 4, 6, 9]
@staticmethod vs @classmethod
- @staticmethod does not receive any reference to the class or instance it is called on. It's like a regular function but belongs to the class's namespace.
- @classmethod receives the class itself as the first argument (usually named cls) and can modify class state that applies across all instances of the class.
class Demo:
count = 0
@classmethod
def increment_count(cls):
cls.count += 1
@staticmethod
def static_method():
print("Called static_method")
# Static method call
Demo.static_method() # "Called static_method"
# Class method modifying class state
print("Initial Count:", Demo.count) # Initial Count: 0
Demo.increment_count()
print("Modified Count:", Demo.count) # Modified Count: 1
__call__ in Python
- __call__ allows an instance of a class to be called as a function.
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
def __call__(self, x):
print(f"{self.name} costs {self.price * x}")
p = Product("coffee", 2)
p(3) # Output: coffee costs 6
Abstract Method vs Virtual Method
Abstract Method
- An abstract method is a method that is declared in a class, but it does not have an implementation in that class.
- The class containing an abstract method is typically known as an abstract class, and it cannot be instantiated on its own.
- Abstract methods must be implemented by any subclass that derives from the abstract class.
- Must be declared in an abstract class using the abc module and must be implemented by any subclass.
from abc import ABC, abstractmethod
class MyAbstractClass(ABC):
@abstractmethod
def my_abstract_method(self):
"""This is a description of what the method does."""
pass
class ConcreteClass(MyAbstractClass):
def my_abstract_method(self):
print("Implementation of the abstract method.")
# Attempting to instantiate MyAbstractClass would raise an error
# obj = MyAbstractClass() # TypeError: Can't instantiate abstract class
# Correct usage
obj = ConcreteClass()
obj.my_abstract_method() # Output: Implementation of the abstract method.
Virtual Method
- A virtual method is a method that has an implementation in a base class but can be overridden in a derived class.
- Python handles method overriding differently and does not have the "virtual" keyword.
- In Python, all methods in classes are effectively virtual, which means any method can be overridden in a subclass.
class BaseClass:
def my_method(self):
print("This is a method in the base class.")
class DerivedClass(BaseClass):
def my_method(self):
print("This method overrides the base class method.")
base_obj = BaseClass()
base_obj.my_method() # Output: This is a method in the base class.
derived_obj = DerivedClass()
derived_obj.my_method() # Output: This method overrides the base class method.