Functions


1. Why functions matter

A function is a reusable block of code that performs a task.

Instead of copy-pasting the same logic over and over, you define it once and call it wherever you need it.

In DevOps you use functions constantly:


2. Defining and calling a basic function

def function_name():
    print('hello')


function_name()
function_name()

Adding a docstring

def greet():
    '''
    this function prints hello to the user
    :return:
    '''
    print('hello')

A docstring is a string literal right below the def line. It documents what the function does and shows up in help().


3. Parameters — giving the function input

3.1 Single parameter

def greet_with_name(name):        # input ✓  output ✗
    print(f'hello {name}')
greet_with_name('hodi')

3.2 Multiple parameters

def greet_with_name_and_age(name, age):
    print(f'hello {name} , {age} years old ')
greet_with_name_and_age('guy', 50)
def greet_with_name_and_age(name: str, age: int):
    print(f'hello {name} , {age} years old ')

Type hints are not enforced at runtime, but they make code more readable and help your editor warn you about mistakes.

3.4 Passing a list as a parameter

def greet_all(names: list):
    for name in names:
        greet_with_name(name)


greet_all(['or', 'elia', 'rotem'])
# greet_all(150)  # TypeError — int is not iterable

4. Return values — giving the function output

4.1 No return (returns None)

def your_sum():
    x = 12 + 3    # x -> 15
    # nothing returned


print(your_sum())   # None

A function with no return statement always returns None (same idea as null / void in other languages).

4.2 Returning a value

def my_sum(a, b):
    return a + b


def get_number():
    return 66


print(my_sum(1, 2))      # 3
ans = get_number()
print(ans)               # 66
print(get_number())      # 66

5. Default parameter values

You can give a parameter a default value. If the caller skips it, the default is used.

def login(username='spooky33'):
    print(f'welcome back {username}')


login()               # uses default → welcome back spooky33
login('iusechatgpt')  # overrides default

5.1 Multiple defaults — positional vs keyword arguments

def hard_login(username, password, otp=11, token=1):
    pass
hard_login(1, 2, 3, 4)              # all positional
hard_login(14, 55, token=88)        # skip otp, set token by name
# hard_login(username=123, otp=1, 44, password=1)  # SyntaxError

Rule: positional arguments must come before keyword arguments in a call.


6. Real-world DevOps example — git_save()

import time


def git_save(userid, ticket_number, version=0.1, approved=False, env='dev'):
    if approved:
        print(f'{userid} saved the code .....')
        time.sleep(2)
        print(f'git add ..........{1 + version}')
        time.sleep(1)
        print(f'git commit -m {ticket_number} fix ..')
    else:
        inline_approval = True if input('approved ? Y/N only ') == 'Y' else False
        if inline_approval:
            print(f'{userid} saved the code .....')
            time.sleep(2)
            print(f'git add ........{version}')
            time.sleep(1)
            print(f'git commit -m {ticket_number} fix ..')
        else:
            print('declined ')
            exit()
# Various ways to call git_save:
git_save(147852, 'infra123')
git_save(147852, ticket_number='infra123', env='prd')
git_save(147852, 'infra123', 1.1, True, 'dev')
git_save(userid=147852, version=0.5, approved=True, ticket_number='infra123', env='prd')
# git_save(userid=147852, env='prd', 'infra123')  # SyntaxError

Key takeaways:


7. *args — variable number of positional arguments

What if you don’t know in advance how many numbers you’ll receive?

7.1 The problem

def total(a, b):      return a + b       # works for 2
def total(a, b, c):   return a + b + c   # works for 3
# ... not scalable

7.2 Solution: *args

def total(*args):       # args is a tuple
    print(type(args))   # <class 'tuple'>
    s = 0
    for arg in args:
        s += arg
    return s


print(total(1, 2, 3, 4, 5))   # 15
print(total(1))                # 1
print(total(55, 77))           # 132
# print(total('a','b','c',1,2,3))  # TypeError — can't add str + int

*args collects all extra positional arguments into a tuple named args.

7.3 Mixing fixed params, *args, and keyword-only params

def foo(a, *args, z=123):
    print(a)
    print(args)
    print(z)
foo(5)                           # a=5  args=()    z=123
foo(1, 2, 3, 4, 5, 6, 7, 8, 9)  # a=1  args=(2..9) z=123
foo(88, 84, 4, 4, 8, 48, 64, 6, z=88)  # z=88 (keyword only)

After *args, any parameter can only be passed by keyword — that’s called a keyword-only argument.

7.4 Practical example: printing names

def print_the_names(count, *names, lines=1):
    for name in names:
        for i in range(lines):
            print()
        print(name.upper())


print_the_names(4, 'avi', 'moshe', 'ziv', 'mor', 'eden', 'yaki')

8. **kwargs — variable number of keyword arguments

**kwargs collects any extra keyword arguments into a dict.

8.1 Basic usage

def objs(**kwargs):        # kwargs is a dict
    print(type(kwargs))    # <class 'dict'>
    for k, v in kwargs.items():
        print(k, v)


objs(len=11, size='3XL', color=144)
objs(cal=1990, toppings='cheese')

8.2 Real-world DevOps example — car_def()

Problem: two end users submit car data, but they use different key names for the same attribute:

Attribute User 1 writes User 2 writes
color color clr, cr, c
make make m, mk, manufacturer
def car_def(model, year, **kwargs):
    car = dict()
    car['model'] = model
    car['year'] = year
    for k, v in kwargs.items():
        if k in ['clr', 'cr', 'c', 'color']:
            car['color'] = v
        elif k in ['make', 'm', 'mk', 'manufacturer']:
            car['make'] = v
    return car


car1 = car_def('m4',  2026, make='bmw',  c='black')
car2 = car_def('Q7',  2020, manufacturer='audi', clr='white')

print(car1)   # {'model': 'm4', 'year': 2026, 'color': 'black', 'make': 'bmw'}
print(car2)   # {'model': 'Q7', 'year': 2020, 'color': 'white', 'make': 'audi'}

**kwargs makes your functions flexible to accept varying key names without breaking.


9. Combining *args and **kwargs

You can use both in the same function. The order must always be:

def func(fixed_args, *args, **kwargs):
def debug_info(label, *values, **meta):
    print(f'[{label}]', values, meta)


debug_info('deploy', 'step1', 'step2', env='prod', version=1.2)
# [deploy] ('step1', 'step2') {'env': 'prod', 'version': 1.2}

10. Summary

Concept Syntax What it does
Define a function def name(): Creates a reusable block
Docstring '''...''' Documents the function
Parameter def f(x): Accepts input
Type hint def f(x: int): Documents expected type
Return return value Sends output back to caller
Default value def f(x=10): Optional parameter
Keyword argument f(x=5) Pass by name
*args def f(*args): Variable positional → tuple
**kwargs def f(**kwargs): Variable keyword → dict

11. Quick recap