top of page
Writer's pictureDev Deck

Python Function

What is a Python Function?

A function in Python is a logical unit of code containing a sequence of statements indented under a name given using the “def” keyword.

Functions allow you to create a logical division of a big project into smaller modules. They make your code more manageable and extensible.

While programming, it prevents you from adding duplicate code and promotes reusability.

How to Create a Function – Syntax

The syntax of a Python function is as follows.

Single line function:

def single_line(): statement

Python function with docstring:

def fn(arg1, arg2,...):
    """docstring"""
    statement1
    statement2

Nested Python function:

def fn(arg1, arg2,...):
    """docstring"""
    statement1
    statement2
    def fn_new(arg1, arg2,...):
        statement1
        statement2
        ...
    ...

Def Statement

Please read the below notes before creating your first Python function.

  • The “def” keyword is a statement for defining a function in Python.

  • You start a function with the def keyword, specify a name followed by a colon (:) sign.

  • The “def” call creates the function object and assigns it to the name given.

  • You can further re-assign the same function object to other names.

  • Give a unique name to your function and follow the same rules as naming the identifiers.

  • Add a meaningful docstring to explain what the function does. However, it is an optional step.

  • Now, start the function body by adding valid Python statements each indented with four spaces.

  • You can also add a statement to return a value at the end of a function. However, this step is optional.

  • Just press enter and remove the indentation to end a function.

  • Since def is a statement, so you can use it anywhere a statement can appear – such as nested in an if clause or within another function.

Example :

if test:
        def test(): # First definition
            ...
    else:
        def test(): # Alternate definition
            ...
    ...

How to Call a Function in Python?

By using the def keyword, you learned to create the blueprint of a function which has a name, parameters to pass and a body with valid Python statements.

The next step is to execute it. You can do so by calling it from the Python script or inside a function or directly from the Python shell.

To call a function, you need to specify the function name with relevant parameters, and that’s it.

Follow the below example to learn how to call a function in Python.



Example of a Function Call

It’s a simple example where a function “typeOfNum()” has nested functions to decide on a number is either odd or even.

def typeOfNum(num): # Function header
    # Function body
    if num % 2 == 0:
        def message():
            print("You entered an even number.")
    else:
        def message():
            print("You entered an odd number.")
    message()
# End of function

typeOfNum(2)  # call the function
typeOfNum(3)  # call the function again

Polymorphism in Python

In Python, functions polymorphism is possible as we don’t specify the argument types while creating functions.

  • The behavior of a function may vary depending upon the arguments passed to it.

  • The same function can accept arguments of different object types.

  • If the objects find a matching interface, the function can process them.

Example :

def product(x, y) : return x * y
print(product(4, 9)) # function returns 36
print(product('Python!', 2))  # function returns
                              # Python!Python!
print(product('Python 2 or 3?', '3')) # TypeError occurs

The above example clarifies that we can pass any two objects to the product() function which supports the ‘*’ operator.

The concept above we’ve explained is known as polymorphism. Some points which you should remember are as follows.

  • Python is a dynamically typed language which means the types correlate with values, not with variables. Hence, the polymorphism runs unrestricted.

  • That’s one of the primary differences between Python and other statically typed languages such as C++ or Java.

  • In Python, you don’t have to mention the specific data types while coding.

  • However, if you do, then the code limits to the types anticipated at the time of coding.

  • Such code won’t allow other compatible types that may require in the future.

  • Python doesn’t support any form of function overloading.


Parameters in a Function

We often use the terms parameters and arguments interchangeably. However, there is a slight difference between them.

Parameters are the variables used in the function definition whereas arguments are the values we pass to the function parameters.

Python supports different variations of passing parameters to a function. Before we discuss each of them, you should read the following notes.

  • The argument gets assigned to the local variable name once passed to the function.

  • Changing the value of an argument inside a function doesn’t affect the caller.

  • If the argument holds a mutable object, then changing it in a function impacts the caller.

  • We call the passing of immutable arguments as Pass by Value because Python doesn’t allow them to change in place.

  • The passing of mutable arguments happens to be Pass by Pointer in Python because they are likely to be affected by the changes inside a function.

Example: Immutable vs. Mutable

def test1(a, b) :
    a = 'Garbage' # 'a' receives an immutable object
    b[0] = 'Python' # 'b' receives a list object
                    # list is mutable
                    # it can undergo an in place change
def test2(a, b) :
    a = 'Garbage 2'
    b = 'Python 3' # 'b' now is made to refer to new
                   # object and therefore argument 'y'
                   # is not changed

arg1 = 10
arg2 = [1, 2, 3, 4]
test1(arg1, arg2)
print("After executing test 1 =>", arg1, arg2)
test2(arg1, arg2)
print("After executing test 2 =>", arg1, arg2)

After execution, the above code prints the following.

After executing test 1 => 10 ['Python', 2, 3, 4]
After executing test 2 => 10 ['Python', 2, 3, 4]

Example: How to avoid changing the mutable argument

def test1(a, b) :
    a = 'Garbage'
    b[0] = 'Python'

arg1 = 10
arg2 = [1, 2, 3, 4]

print("Before test 1 =>", arg1, arg2)
test1(arg1, arg2[:]) # Create an explicit copy of mutable object
                     # 'y' in the function.
                     # Now 'b' in test1() refers to a
                     # different object which was initially a
                     # copy of 'arg2'
                            
print("After test 1  =>", arg1, arg2)

After execution, the above code prints the following.

Before test 1 => 10 [1, 2, 3, 4]
After test 1  => 10 [1, 2, 3, 4]

Standard arguments

The standard arguments are those which you pass as specified in a Python function definition. It means without changing their order and without skipping any of them.

def fn(value):
    print(value)
    return

fn()

Executing the above code throws the below error as we’ve not passed the single argument required.

TypeError: fn() missing 1 required positional argument: 'value'

Keyword-based arguments

When you assign a value to the parameter (such as param=value) and pass to the function (like fn(param=value)), then it turns into a keyword argument.

If you pass the keyword arguments to a function, then Python determines it through the parameter name used in the assignment.

See the below example.

def fn(value):
    print(value)
    return

fn(value=123) # output => 123
fn(value="Python!") # output => Python!

While using keyword arguments, you should make sure that the name in the assignment should match with the one in the function definition. Otherwise, Python throws the TypeError as shown below.

fn(value1="Python!") # wrong name used in the keyword argument

The above function call causes the following error.

TypeError: fn() got an unexpected keyword argument 'value1'

Arguments with Default Values

Python functions allow setting the default values for parameters in the function definition. We refer them as the default arguments.

The callee uses these default values when the caller doesn’t pass them in the function call.

The below example will help you clearly understand the concept of default arguments.

def daysInYear(is_leap_year=False):
    if not is_leap_year:
        print("365 days")
    else:
        print("366 days")
    return

daysInYear()
daysInYear(True)

Here, the parameter “is_leap_year” is working as a default argument. If you don’t pass any value, then it assumes the default which is False.

The output of the above code is:

365 days
366 days

Variable Arguments

You may encounter situations when you have to pass additional arguments to a Python function. We refer them as variable-length arguments.

The Python’s print() is itself an example of such a function which supports variable arguments.

To define a function with variable arguments, you need to prefix the parameter with an asterisk (*) sign. Follow the below syntax.

def fn([std_args,] *var_args_tuple ):
   """docstring"""
   function_body
   return_statement

Check out the below example for better clarity.

def inventory(category, *items):
    print("%s [items=%d]:" % (category, len(items)), items)
    for item in items:
        print("-", item)
    return

inventory('Electronics', 'tv', 'lcd', 'ac', 'refrigerator', 'heater')
inventory('Books', 'python', 'java', 'c', 'c++')

The output of the above code goes like this.

Electronics [items=5]: ('tv', 'lcd', 'ac', 'refrigerator', 'heater')
- tv
- lcd
- ac
- refrigerator
- heater
Books [items=4]: ('python', 'java', 'c', 'c++')
- python
- java
- c
- c++

Please note that you can choose to have a formal argument or not in the function definition along with the variable arguments.

You may choose to skip the variable arguments while calling the function. In such a case, the tuple would remain empty.


Local Variables inside a Function

A local variable has visibility only inside a code block such as the function def.

They are available only while the function is executing.

Check out the below example of using local variables.

def fn(a, b) :     
    temp = 1
    for iter in range(b) :
        temp = temp*a
    return temp

print(fn(2, 4))

print(temp) # error : can not access 'temp' out of scope of function 'fn'
print(iter) # error : can not access 'iter' out of scope of function 'fn'

In this example, we try to access local variables outside the function body which results in the NameError.

Function’s local variables don’t retain values between calls. The names used inside a def do not conflict with variables outside the def, even if you’ve used the same names elsewhere.

In Python, the variables assignment can occur at three different places.

  • Inside a def – it is local to that function

  • In an enclosing def – it is nonlocal to the nested functions

  • Outside all def(s) – it is global to the entire file


Global Variables in a Function

The global keyword is a statement in Python. It enables variable (names) to retain changes that live outside a def, at the top level of a module file.

In a single global statement, you can specify one or more names separated by commas.

All the listed names attach to the enclosing module’s scope when assigned or referenced within the function body.

Check the below example.

x = 5
y = 55
def fn() :
    global x
    x = [3, 7]
    y = [1, 33, 55]
    # a local 'y' is assigned and created here
    # whereas, 'x' refers to the global name
fn()
print(x, y)

In the above code, ‘x’ is a global variable which will retain any change in its value made in the function. Another variable ‘y’ has local scope and won’t carry forward the change.

Let’s now see how a globally declared name behaves in two different Python functions.

foo = 99

def fn1() :
    foo = 'new' # new local foo created

def fn2() :
    global foo
    foo = 'update' # value of global foo changes

In the next example, let’s see how global behaves with the import statement.

Here, we have got the following three scripts:

  • mod_global.py: It contains the global definition and a function changing and displaying values.

  • test1.py: It imports the first file and accesses the global variable.

  • test2.py: It is using the “from” clause to import the first file and accessing the global variable.

# mod_global.py
def fn1() :
   global x	
   x = [1,2] ; y = [20, 200]
   # a local 'y' is created – availableonly within 'f1'
   # 'x' can be accessed anywhere after a call to 'f1'
fn1()
try :
    print(x, y) # name 'y' is not defined – error
except Exception as ex:
    print('y ->', ex)
    print('x ->', x)
# test1.py
import mod_global
print('test1 ->', mod_global.x)
# test2.py
from mod_global import *
print('test2 ->', x)

Name Resolution in a Python Function

It is essential to understand how name resolution works in case of a def statement.

Here are a few points you should keep in mind.

  • The name assignments create or change local names.

  • The LEGB rule comes in the picture for searching the name reference.

    • local – L

    • then enclosing functions (if any) – E

    • next comes the global – G

    • and the last one is the built-in – B

To gain more understanding, run through the below example.

#var = 5
def fn1() :
   #var = [3, 5, 7, 9]
   def fn2() :
      #var = (21, 31, 41)
      print(var)
   fn2()
fn1()	# uncomment var assignments one-by-one and check the output
print(var)

After uncommenting the first “var” assignment, the output is:

5
5

Next, after uncommenting the second “var” assignment as well, the output is:

[3, 5, 7, 9]
5

Finally, if we uncomment the last “var” assignment, then the result is as follows.

(21, 31, 41)
5

Scope Lookup in Functions

Python functions can access names in all available enclosing def statements.

Check the below example.

X = 101 # global scope name - unused
def fn1():
   X = 102 # Enclosing def local
   def fn2():
      print(X) # Reference made in nested def
   fn2() # Prints 102: enclosing def local
fn1()	

The scope lookup remains in action even if the enclosing function has already returned.

def fn1():
   print('In fn1')
   X = 100
   def fn2(): 
      print('In fn2')
      print(X) # Remembers X in enclosing def scope
   return fn2 # Return fn2 but don't call it
action = fn1() # Make, return function
action() # Call fn2() now: prints 100

The output is as follows.

In fn1
In fn2
100

Return Values from a Python Function

In Python functions, you can add the “return” statement to return a value.

Usually, the functions return a single value. But if required, Python allows returning multiple values by using the collection types such as using a tuple or list.

This feature works like the call-by-reference by returning tuples and assigning the results back to the original argument names in the caller.

def returnDemo(val1, val2) :
   val1 = 'Windows'
   val2 = 'OS X'
   return val1, val2 # return multiple values in a tuple

var1 = 4
var2 = [2, 4, 6, 8]

print("before return =>", var1, var2)
var1, var2 = returnDemo(var1, var2)
print("after return  =>", var1, var2)

The above code gives the following output.

before return => 4 [2, 4, 6, 8]
after return => Windows OS X

Function Examples

General Function

Check out a general function call example.

def getMin(*varArgs) :
    min = varArgs[0]
    for i in varArgs[1:] :
        if i < min :
            min = i
    return min

min = getMin(21, -11, 17, -23, 6, 5, -89, 4, 9)
print(min)

The output is as follows.

-89

Recursive Function

Next is an example of the recursive function.

def calcFact(num) :
    if(num != 1) :
        return num * calcFact(num-1)
    else :
        return 1

print(calcFact(4))

The output is as follows.

24

Python Functions as Objects

Yes, Python treats everything as an object and functions are no different.

You can assign a function object to any other names.

def testFunc(a, b) : print('testFunc called')
fn = testFunc
fn(22, 'bb')

The output is:

testFunc called

You can even pass the function object to other functions.

def fn1(a, b) : print('fn1 called')
def fn2(fun, x, y) : fun(x, y)
fn2(fn1, 22, 'bb')

The output is:

fn1 called

You can also embed a function object in data structures.

def fn1(a) : print('fn1', a)
def fn2(a) : print('fn2', a)

listOfFuncs = [(fn1, "First function"), (fn2, "Second function")]
for (f, arg) in listOfFuncs : f(arg)

The output is:

fn1 First function
fn2 Second function

You can return a function object from another function.

def FuncLair(produce) :  
    def fn1() : print('fn1 called')
    def fn2() : print('fn2 called')
    def fn3() : print('fn3 called')
    if produce == 1 : return fn1
    elif produce == 2 : return fn2
    else : return fn3

f = FuncLair(2) ; f()

The output is:

fn2 called

Function Attributes

Python functions also have attributes.

  • You can list them via the dir() built-in function.

  • The attributes can be system-defined.

  • Some of them can be user-defined as well.

  • The dir() function also lists the user-defined attributes.

def testFunc():
    print("I'm just a test function.")

testFunc.attr1 = "Hello"
testFunc.attr2 = 5
testFunc()
print(dir(testFunc))

The output is:

I'm just a test function.
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'attr1', 'attr2']

You can utilize the function attributes to archive state information instead of using any of the globals or nonlocals names.

Unlike the nonlocals, attributes are accessible anywhere the function itself is, even from outside its code.


Summary – Python Function

We’ve covered every essential concept of a Python function in this tutorial. You should now try to use these concepts in your routine programming tasks.

0 views0 comments

Comments


bottom of page