Basics

Properties

  • Python allows docstring for functions using triple-quotes(""" or ''').
  • Python allows default arguments, but they should not be followed by non-default arguments.
  • Python allows functions to return multiple values.
  • Python allows functions to have variable length of arguments (using *args for non-keywords arguments and **kwargs for keyword arguments).
  • Python allows anonymous functions.
  • Python allows functions within functions.

Syntax and example


    ''' syntax '''
    def function_name(parameters):
        """docstring"""
        statement(s)
        return expression

    ''' defining and calling a function '''
    def fun():
        print("Welcome to GFG")
    fun()
        

Arguments


    def print_double(x):
        print(x*2)
    print_double(10)

    ''' default arguments '''
    def multiply(x, y=2):
        print(x*y)
    multiply(5, 10) # 50
    multiply(5) # 10

    ''' keyword arguments '''
    def multiply(multiplier1, multiplier2):
        print(multiplier1 * multiplier2)
    multiply(multiplier2=10, multiplier1=5)

    ''' variable length non-keywords argument using argv '''
    def myFun(*argv):
        for arg in argv:
            print(arg)
    myFun('Hello', 'Welcome', 'to', 'GeeksforGeeks')

    ''' variable length keywords argument using argv '''
    def myFun(**kwargs):
        for key, value in kwargs.items():
            print("%s == %s" % (key, value))
    myFun(first='Geeks', mid='for', last='Geeks')

    ''' args and kwargs '''
    def myFun(*args,**kwargs):
        print("args: ", args) # args: ('a', 'b', 'c')
        print("kwargs: ", kwargs) # kwargs {'first': 'abc', 'mid': 'def', 'last': 'xyz'}
    myFun('a','b','c',first="abc",mid="def",last="xyz")
        

Docstring


    def evenOdd(x):
        """Function to check if the number is even or odd"""
        if (x % 2 == 0):
            print("even")
        else:
            print("odd")
    print(evenOdd.__doc__) # Function to check if the number is even or odd
        

Anonymous functions


    def cube(x): return x*x*x
    cube_v2 = lambda x : x*x*x
    print(cube(7)) # 343
    print(cube_v2(7)) # 343
        

Functions within functions


    def f1(name):
        s = 'Hi ' + name
        def f2():
            print(s) # Hi abc
        f2()
    f1("abc")
        

Generators

Generator Functions

A generator function is defined like a normal function, but whenever it needs to generate a value, it does so with the yield keyword rather than return. If the body of a def contains yield, the function automatically becomes a generator function.

    def simpleGeneratorFun():
        yield 1
        yield 2
        yield 3
    for value in simpleGeneratorFun():
        print(value)
        

Generator Object

Generator functions return a generator object. Generator objects are used either by calling the next method on the generator object or using the generator object in a “for in” loop.

    def fib(limit):
        a, b = 0, 1 # Initialize first two Fibonacci Numbers
        while a < limit:
            yield a # One by one yield next Fibonacci Number
            a, b = b, a + b

    x = fib(5) # Create a generator object

    ''' either use next() '''
    print(x.next()) # In Python 3, __next__() # 0
    print(x.next()) # 1
    print(x.next()) # 1
    print(x.next()) # 2
    print(x.next()) # 3

    ''' or iterable '''
    for i in x:
        print(i) # 0 1 1 2 3
        

Lambda

Anonymous function means that a function is without a name. def keyword is used to define the normal functions and the lambda keyword is used to create anonymous functions.
  • can have any number of arguments but only one expression, which is evaluated and returned.
  • One is free to use lambda functions wherever function objects are required.
  • are syntactically restricted to a single expression.
  • lambda returns a function object

    ''' syntax '''
    lambda arguments : expression

    ''' example '''
    x ="Hi there!"
    (lambda x : print(x))(x)

    ''' lambda vs normal function '''
    # normal function
    def cube(y):
        return y*y*y;
    print(cube(5))

    # lambda function
    g = lambda x: x*x*x
    print(g(7))
        

Local and Global Variables

local variables are accessible only inside the function in which it was initialized whereas the global variables are accessible throughout the program and inside every function. to access global variables inside a function we need to re-declare them using the keyword global

    def f():
        global s # accesses the global variable 's'
        s += ' there'
        print(s) # Hi there
        s = "Hello"
        print(s) # Hello

    # Global Scope
    s = "Python is great!"
    f()
    print(s) # Hello
        

First Class Function

First class objects in a language are handled uniformly throughout. They may be stored in data structures, passed as arguments, or used in control structures. A programming language is said to support first-class functions if it treats functions as first-class objects.
  • A function is an instance of the Object type.
  • You can store the function in a variable.
  • You can pass the function as a parameter to another function.
  • You can return the function from a function.
  • You can store them in data structures such as hash tables, lists, …

    ''' functions are objects '''
    def shout(text):
        return text.upper()
    print (shout('Hello')) # HELLO
    yell = shout
    print (yell('Hello')) # HELLO

    ''' passing functions as argument '''
    def shout(text):
        return text.upper()
    def whisper(text):
        return text.lower()
    def greet(func):
        greeting = func("Hi there.")
        print (greeting)
    greet(shout) # HI THERE.
    greet(whisper) # hi there.

    ''' function returning another function '''
    def create_adder(x):
        def adder(y):
            return x+y
        return adder
    add_15 = create_adder(15)
    print (add_15(10)) # 25
        

Closures

A Closure is a function object that remembers values in enclosing scopes even if they are not present in memory.
  • It is a record that stores a function together with an environment: a mapping associating each free variable of the function (variables that are used locally but defined in an enclosing scope) with the value or reference to which the name was bound when the closure was created.
  • A closure—unlike a plain function—allows the function to access those captured variables through the closure's copies of their values or references, even when the function is invoked outside their scope.

    def outerFunction(text):
        text = text
        def innerFunction():
            print(text)
        return innerFunction
    if __name__ == '__main__':
        myFunction = outerFunction('Hey!')
        myFunction() # Hey!
    # The function innerFunction has its scope only inside the outerFunction. But with the use of closures, we can easily extend its scope to invoke a function outside its scope.
        

Why and when to use Closures?
  • As closures are used as callback functions, they provide some sort of data hiding. This helps us to reduce the use of global variables.
  • When we have few functions in our code, closures prove to be an efficient way. But if we need to have many functions, then go for class (OOP).

Decorators

Decorators allow us to wrap another function in order to extend the behaviour of the wrapped function, without permanently modifying it.

Introduction


    def hello_decorator(func):
        def inner1():
            print("Hello, this is before function execution")
            func()
            print("This is after function execution")
        return inner1

    ''' either use @hello_decorator '''
    if __name__ == '__main__':
        @hello_decorator
        def function_to_be_used(): # defining a function, to be called inside wrapper
            print("This is inside the function !!")
        function_to_be_used()

    ''' or without using @hello_decorator '''
    if __name__ == '__main__':
        def function_to_be_used(): # defining a function, to be called inside wrapper
            print("This is inside the function !!")
        f = hello_decorator(function_to_be_used) # passing 'function_to_be_used' inside the decorator to control its behaviour
        f()
        

Chained decorators


    @decor2
    @decor1
    def function_to_be_used():
        pass
    # first function_to_be_used() is passed to decor1, then decor1(function_to_be_used()) is passed to decor2
    # final result: decor2(decor1(function_to_be_used))
        

Decorators with parameters


    def decorator(*args, **kwargs):
        pass
    @decorator(name = "abc")
    def function_to_be_used():
        pass
        

Memoization using decorators

Memoization is a technique of recording the intermediate results so that it can be used to avoid repeated calculations and speed up the programs. It can be used to optimize the programs that use recursion.

    memory = {}
    def memoize_factorial(f):
        def inner(num):
            if num not in memory:
                memory[num] = f(num)
                print('result saved in memory')
            else:
                print('returning result from saved memory')
            return memory[num]
        return inner

    @memoize_factorial
    def facto(num):
        if num == 1:
            return 1
        else:
            return num * facto(num-1)

    print(facto(5))
    print(facto(5)) # directly coming from saved memory
        

  • In python, everything is passed by reference, except immutable objects which are passed by value.