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.