Python functions

In these lessons, we’ll cover the fundamentals of Python functions. A function is a series of statements that executes a specific task, helping to reduce code duplication. Throughout the course, we’ll explore various types of functions, as illustrated in the figure below:

space-1.jpg
Type of Functions in Python : Source

Before starting the function we need to recall the function name, arguments, and parameters.

def function_name(parameters):
  statement
  statement
  ...
  return value

return_value = function_name(arguments)

Defining Function

We can define a function with the def keyword.

def print_hello():
  print(f"Hello")

print_hello()

>>> Hello

More on defining function

We can define a function with a variable number of arguments.

Default Argument Values

# Defining function with default arguments
def sum(x , y=2, z=3):
  print(f"Sum of x, y, and z is:{x+y+z}")

Calling a function in different ways:

# giving only the mendatory argument
sum(1)
>>> Sum of x, y, and z is:6
# Giving one of the option arguments
sum(3,4)
>>> Sum of x, y, and z is:10
# Giving all arguments
sum(3,4,5)
>>> Sum of x, y, and z is:12

Note1 : The default values are evaluated at the point of function defination in the defining scope, so that

a = 10
# The default value for variable arg is set at the time of function definition
def function(arg=a):
  print(arg)

a = 15
function()
>>> 10

Note2: The default value is evaluated only once. This makes a difference when the default is a mutable object such as a list, dictionary, or instances of most classes. For example, the following function accumulates the arguments passed to it on subsequent calls:

# The default value is evaluated only once but in the case of mutable(list, dictionary...) we can evaluate many times.
def append(a,L={}):
  L[a]=a
  return L

print(append(1))
print(append(2))
print(append(3))

>>> {1: 1}
>>> {1: 1, 2: 2}
>>> {1: 1, 2: 2, 3: 3}
  • Note: Order matter while calling in a default argument values function.

Keyword Arguments

Functions can also be called using keyword arguments of the form kwarg=value. For instance, the following function:

def add(x,y=2,z=3):
  print(f"Sum of x, y, and z is:{x+y+z}")

Calling a function in different ways:

add(1)
>>> Sum of x, y, and z is:6
add(x=2)
>>> Sum of x, y, and z is:7
add(x=2,y=4)
>>> Sum of x, y, and z is:9
add(1,2,3)
>>> Sum of x, y, and z is:6
add(1,2,z=8)
>>> Sum of x, y, and z is:11

But some following calls are invalid:

add()
>>> -
TypeError                                 Traceback (most recent call last)
<ipython-input-33-d5d29de3ed94> in <module>()
----> 1 add()

TypeError: add() missing 1 required positional argument: 'x'
add(x=1,2)
>>> File "<ipython-input-34-ea0ca97f0446>", line 1
    add(x=1,2)
           ^
SyntaxError: positional argument follows keyword argument
add(5,x=2)
>>> -
TypeError                                 Traceback (most recent call last)
<ipython-input-37-cf0a26e4ab05> in <module>()
----> 1 add(5,x=2)

TypeError: add() got multiple values for argument 'x'
add(k=2)
>>> -
TypeError                                 Traceback (most recent call last)
<ipython-input-38-4063630c747d> in <module>()
----> 1 add(k=2)

TypeError: add() got an unexpected keyword argument 'k'

Special parameters

Developer needs only look at the function definition to determine if items are passed by position, by position or keyword, or by keyword.

A function definition may look like:

def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
      -----------    ----------     ----------
        |             |                  |
        |        Positional or keyword   |
        |                                - Keyword only
         -- Positional only

where / and * are optional. If used, these symbols indicate the kind of parameter by how the arguments may be passed to the function: positional-only, positional-or-keyword, and keyword-only. Keyword parameters are also referred to as named parameters.

Positional-or-Keyword Arguments

If / and * are not present in the function definition, arguments may be passed to a function by position or by keyword.

Positional-Only Parameters
def f(pos1, pos2, /):
      -----------    ----------     ----------
        |
         -- Positional only
def pos_only_arg(arg,/):
  print(arg)

Note: poition only support 3.8

pos_only_arg(1)
>>> 1

pos_only_arg(arg=1)
>>> Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: pos_only_arg() got an unexpected keyword argument 'arg'

Keyword-Only Parameters
def kwd_only_arg(*, arg):
    print(arg)
kwd_only_arg(3)
>>> -
TypeError                                 Traceback (most recent call last)
<ipython-input-8-896c53ef896c> in <module>()
----> 1 kwd_only_arg(3)

TypeError: kwd_only_arg() takes 0 positional arguments but 1 was given
kwd_only_arg(arg=3)
>>> 3

Arbitrary Argument

Function can be call with an arbitrary number of arguments.

Arbitrary Argument tuples
def arbitrary_argument(*args):
  return args
print(arbitrary_argument("hi","iam","madan"))

>>> ('hi', 'iam', 'madan')

Note: From above we can see that arguments are wrapped up in a tuple (see Tuples and Sequences)

Arbitrary Argument lists
def arbitrary_argument(*args):
  L = list(args)
  return L
print(arbitrary_argument("hi","iam","madan"))
>>> ['hi', 'iam', 'madan']
Arbitrary Argument dictionaries

In the same fashion, dictionaries can deliver keyword arguments with the **-operator:

def arbitrary_argument(**args):
  return args
print(arbitrary_argument(hi="hi",iam="iam",Madan="Madan"))
>>> {'hi': 'hi', 'iam': 'iam', 'Madan': 'Madan'}

Note: for dictionary we need to pass “key”:”value” into key=”value” this formate.

Lambda Expressions

Small anonymous functions can be created with the lambda keyword.

  • lambda arguments : expression

Arguments:

-Positional arguments

-Named arguments (sometimes called keyword arguments)

-Variable list of arguments (often referred to as varargs)

-Variable list of keyword arguments

-Keyword-only arguments

lambda a,b:a+b
>>> <function __main__.<lambda>>

calling lambda function

_(1,2) # Note _  call recent run things
>>> 3
_ #last output value
>>> 3
(lambda a,b:a*b)(2,3)# call lambda function
>>> 6
divide = lambda a,b: a/b

divide(4,2) # calling lambda function
>>> 2.0

Recursion

We can make a function call itself. This is known as recursion.

def facto(n):
  if n==1:
    return 1
  return n*facto(n-1) 
facto(5)
>>> 120

How does it work?

We already know the return keyword return value.

first step: value = 5*facto(4)

second step: value = 5* 4 * facto(3)

third step: value = 5* 4* 3 * facto(2)

fourth step: value = 5 * 4 * 3 * 2 *facto(1)

fifth step: value = 5 * 4 * 3 * 2 * 1

Decorators

Decorators are very powerful and useful tool in Python since it allows programmers to modify the behavior of function or class.

def uppercase_decorator(function): # Pass function as an argument
    def wrapper():
        func = function()
        make_uppercase = func.upper()
        return make_uppercase # return function as value

    return wrapper # return function as value

Our decorator function takes a function as an argument, and we shall, therefore, define a function and pass it to our decorator.

def say_hi():
    return 'hello there'

decorate = uppercase_decorator(say_hi)
decorate()
>>> HELLO THERE

However, Python provides a much easier way for us to apply decorators. We simply use the @ symbol before the function we’d like to decorate.

@uppercase_decorator
def say_hi():
    return 'hello there'

say_hi()
>>> HELLO THERE

Generator functions and yield

Consider this simple function which returns a range of numbers as a list:

def my_list(n):
    i = 0
    l = []

    while i < n:
        l.append(i)
        i += 1

    return l

This function builds the full list of numbers and returns it. We can change this function into a generator function while preserving a very similar syntax, like this:

def my_gen(n):
    i = 0

    while i < n:
        yield i  #  Yield le value return gardain , state retun garxa
        i += 1
my_gen(3) 
>>> <generator object my_gen at 0x7f06035b3c50>

From above we can say that yield statement suspends function’s execution and sends a value back to the caller, but retains enough state to enable function to resume where it is left off.

g = my_gen(3)

print(type(g))

for x in g:
    print(x)
    
>>>  <class 'generator'>
0
1
2

How yield keyword work?

  • After the yield statement is executed, execution of the function does not end – when the next value is requested from the generator, it will go back to the beginning of the function and execute it again.

  • The yield keyword in python works like a return with the only difference is that instead of returning a value, it gives back a generator object to the caller.

Documentation Strings

def my_function():
    """Do nothing, but document it.          # First latter capital and end with period
                                             # Second line is blank if there is multiple line for documentation string.
     No, really, it doesn't do anything.     # Indentation determine by first line open quote
     """
    pass
print(my_function.__doc__)

>>> Do nothing, but document it.         # first latter capital and end with period
                                            # second line is blank if there is multiple line for documentation string.
     No, really, it doesn't do anything.     # indentation determine by first line open quote
     

Callback Function

What is a Callback Function?

A callback is a function that is passed as an argument to other function. This other function is expected to call this callback function in its definition. The point at which other function calls our callback function depends on the requirement and nature of other function.

Callback Functions are generally used with asynchronous functions.

Example for Callback Function: Callback Function can be passed to a function to print out the size of file after the function reads given text file.

def callbackFunc(s):
    print('Length of the text file is : ', s)

def printFileLength(path, callback):
    f = open(path, "r")
    length = len(f.read())
    f.close()
    callback(length)

printFileLength("test.txt", callbackFunc)

>>> Output : Length of the text file is : 40

Function Annotations

def bio(name: str, address: str='address')->str: # colon (:) paxiko annotation ho and -> paxiko return type janauna
  print("ANnotation",bio.__annotations__)   
  return 'and'+address
bio(10)
>>> ANnotation {'name': <class 'str'>, 'address': <class 'str'>, 'return': <class 'str'>}
'andaddress'

References




Enjoy Reading This Article?

Here are some more articles you might like to read next:

  • Covid-19 Detection