Introduction to Advanced Python

Functions

Jul 2024
@1995parham
python-logo

Functions


    def function_name(param1, param2):
        '''
        I am your function documentation but you can use me
        in other and maybe bad ways.
        '''
        print('You saw me in the past')
    

Functions in Action


    function_name(1, 2)
    function_name(1, param2=2)
    function_name(param1=1, param2=2)
    

__doc__ is useful to provide some documentation

exec function supports dynamic execution of Python code

Function documentation, bad way


    def function_bad():
        '''print('Hello from the doc')'''
        pass
    print(exec(function_bad.__doc__))
    

I want a function with many many arguments

question_answer


    import functools


    def many_arg_func(list):
        s = functools.reduce(lambda x, y: x + y, list)
        return s

    many_arg_func([1, 2, 3, 4])
    

But I want to call my function normally :\

*args, **kwargs

The syntax is the * and **. The names *args and **kwargs are only by convention but there's no hard requirement to use them.

You would use *args when you're not sure how many arguments might be passed to your function

Similarly, **kwargs allows you to handle named arguments that you have not defined in advance


    def hello(param1, *args, **kwargs):
        print(param1, args, kwargs)

    hello(1, 2, 3, 4, 5, h=6)
    

    import functools

    def many_arg_func(*args):
        s = functools.reduce(lambda x, y: x + y, list)
        return s

    many_arg_func([1, 2, 3, 4])
    

Fibonacci

F(n) = F(n - 1) + F(n - 2)

<Code Time.py>

Write recuresive function for calculating Nth term in the Fibonacci sqeuence

0: 1, 1: 1, 2: 2, 3: 3, 4: 5, ...


    def fib(n):
        if n == 0 or n == 1:
            return 1
        return fib(n - 1) + fib(n - 2)
    print(fib(10)
    
code fibonacci.py

Function call tree

Memoization

In computing, memoization or memoisation is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.

<Code Time.py>

Write recuresive function with memoization for calculating Nth term in the Fibonacci sqeuence

0: 1, 1: 1, 2: 2, 3: 3, 4: 5, ...


    memory = {}

    def m_fibonacci(n):
        if n == 1 or n == 0:
            return 1
        if n in memory:
            return memory[n]
        memory[n] = m_fibonacci(n - 1) + m_fibonacci(n - 2)
        return memory[n]

    print(m_fibonacci(10))
    
code m-fibonacci.py

Functions are first-level citizens in python

To say that functions are first-class in a certain programming language means that they can be passed around and manipulated similarly to how you would pass around and manipulate other kinds of objects.


    def a_hello():
        print("Hi I am function A")


    a_hello()
    print(a_hello)
    b_hello = a_hello
    b_hello()
    1()
    
code function-citizen.py

no need to repeat all arguments every time


    def partial(f, *args):
        param = args

        def _f(*args, **kwargs):
            return f(*param, *args, **kwargs)

        return _f


    def say(name, message):
        print(f'{message} to {name}')


    say('parham', 'hello')

    say_to_parham = partial(say, 'parham')
    say_to_parham('bye bye')
    
code partial.py

Event Handling

You can pass your callback into library for specific event and then customize its behaviour


    import paho.mqtt.client as mqtt


    # The callback for when the client receives a CONNACK response from the server.
    def on_connect(client, userdata, flags, rc):
        print("Connected with result code "+str(rc))

        # Subscribing in on_connect() means that if we lose the connection and
        # reconnect then subscriptions will be renewed.
        client.subscribe("$SYS/#")


      # The callback for when a PUBLISH message is received from the server.
      def on_message(client, userdata, msg):
          print(msg.topic+" "+str(msg.payload))

      client = mqtt.Client()
      client.on_connect = on_connect
      client.on_message = on_message

      client.connect("iot.ceit.aut.ac.ir", 58904, 60)

      # Blocking call that processes network traffic, dispatches callbacks and
      # handles reconnecting.
      # Other loop*() functions are available that give a threaded interface and a
      # manual interface.
      client.loop_forever()
    

Decorators

New in Python 2.4


    def my_decorator(some_function):
        def wrapper():

            print("Something is happening before some_function() is called.")

            some_function()

            print("Something is happening after some_function() is called.")

        return wrapper


    def just_some_function():
        print("Wheee!")


    just_some_function = my_decorator(just_some_function)
    just_some_function()
    

Decorators (cont'd)


    def my_decorator(some_function):
        def wrapper():

            print("Something is happening before some_function() is called.")

            some_function()

            print("Something is happening after some_function() is called.")

        return wrapper


    @my_decorator
    def just_some_function():
        print("Wheee!")


    just_some_function()
    

Decorators (cont'd)

A Python decorator is a specific change to the Python syntax that allows us to more conveniently alter functions and methods.

A decorator is just another function which takes a function and returns one.

Function Wrapping


    import functools

    def my_decorator(some_function):
        @functools.wraps(some_function)
        def wrapper():

            print("Something is happening before some_function() is called.")

            some_function()

            print("Something is happening after some_function() is called.")

        return wrapper


    @my_decorator
    def just_some_function():
        print("Wheee!")


    just_some_function()
    

Function Wrapping (cont'd)

Without the use of this decorator factory, the name of the just_some_function function would have been 'wrapper', and the docstring of the original example() would have been lost.

<Code Time.py>

Using flask create simple web server for handling /hello get request.


    import flask


    app = flask.Flask('Hello')


    @app.route('/hello')
    def hello():
        return '

Hello world

' app.run()
code hello-flask.py

Parameter in decorators


    @decorator
    def function_name():
        pass
    

decorator is a name for decorator function and will not actually call the function

Parameter in decorators (cont'd)

How to pass parameters into decorator when we are not caller

Parameter in decorators (cont'd)

Get parameters and create decorator in place.


    import functools


    def decorator_args(arg1, arg2):
        def decorator(f):
            @functools.wraps(f)
            def wrapper():
                print(arg1)
                f()
                print(arg2)
            return wrapper
        return decorator
    
code decorator-args.py

<Code Time.py>

Giving input from user and running appropriate functions for his/her entered commands.

Fork me on GitHub