Advanced Stuff with Python Functions

Advanced Stuff with Python Functions


Published at - Nov 04, 2021

I’m hoping we know how to define a function in Python already. Arguments can be sent to functions, which will result in local variables with the same name. As an example:

def print_box(message, border='*'):
    print(border * (len(message) + 4))
    print(border, message, border)
    print(border * (len(message) + 4))

print_box("hello")

We’ll learn more about defining functions and how they might be beneficial in this post.

Multiple Return Values

Although a function can accept numerous parameters, it can only return one value. However, there are occasions where returning several values makes sense:

def login():
    username = input("Username: ")
    password = input("Password: ")
    # how the heck are we going to return these?

Returning a tuple of values and unpacking it everywhere the method is invoked is the ideal solution:

def login():
    ...
    return (username, password)

username, password = login()
...

If there are more than three values to return, things become a little hairy, but I’ve never had to return more than three values. You should generally use a class instead if you need to return four or more data.

For example, instead of this:

def get_new_info(username):
    print("Changing user information of %s." % username)
    username = input("New username: ")
    password = input("New password: ")
    fullname = input("Full name: ")
    phonenumber = input("Phone number: ")
    return (username, password, fullname, phonenumber)

You could do this:

class User:
    # you probably want to make many other user related things too, add
    # them here

    def change_info(self):
        print("Changing user information of %s." % self.username)
        self.username = input("New username: ")
        self.password = input("New password: ")
        self.fullname = input("Full name: ")
        self.phonenumber = input("Phone number: ")

*args

Sometimes you might see code like this:

def thing(*args, **kwargs):
    ...

These kinds of functions are pretty simple to comprehend.

Let’s create a function that outputs *args.

>>> def thing(*args):
...     print("now args is", args)
...
>>> thing()
now args is ()
>>> thing(1, 2, 3)
now args is (1, 2, 3)
>>>

So far, we’ve learned that if we wish to call a function with parameters like thing(1, 2, 3), we must declare the arguments when declaring the function with the def thing (a, b, c). However, *args just take any positional arguments the function is given and convert them to a tuple, without raising any issues. Of course, instead of args, we could use whatever variable name we wished.

No keyword arguments are required for our *args-only function:

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

We may also keep our arguments as a list in a variable and then use a * to give them to a function. It doesn’t have to be a list or a tuple; any iterable will suffice.

>>> stuff = ['hello', 'world', 'test']
>>> print(*stuff)
hello world test
>>>
**kwargs

*kwargs is the same as args, but instead of positional arguments, it uses keyword arguments.

>>> def thing(**kwargs):
...     print('now kwargs is', kwargs)
...
>>> thing(a=1, b=2)
now kwargs is {'b': 2, 'a': 1}
>>> thing(1, 2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: thing() takes 0 positional arguments but 2 were given
>>> def print_box(message, border):
...     print(border * len(message))
...     print(message)
...     print(border * len(message))
...
>>> kwargs = {'message': "Hello World!", 'border': '*'}
>>> print_box(**kwargs)
************
Hello World!
************
>>>

It’s sometimes useful to keep track of all the parameters our function accepts. We can simply mix *args and **kwargs:

>>> def thing(*args, **kwargs):
...     print("now args is", args, "and kwargs is", kwargs)
...
>>> thing(1, 2, a=3, b=4)
now args is (1, 2) and kwargs is {'b': 4, 'a': 3}
>>>

This is often used for calling a function from another “fake function” that represents it. We’ll find uses for this later.

>>> def fake_print(*args, **kwargs):
...     print(*args, **kwargs)
...
>>> print('this', 'is', 'a', 'test', sep='-')
this-is-a-test
>>> fake_print('this', 'is', 'a', 'test', sep='-')
this-is-a-test
>>>

Keyword-only Arguments

Let’s pretend we have a file-moving function. It most likely accepts source and destination inputs, but it may also accept additional arguments that tailor how the file is moved. It may, for example, accept an overwrite argument to erase the destination before transferring if it already exists, or a backup argument to make a backup of the file in case the moving fails. As a result, our function would be as follows:

def move(source, destination, overwrite=False, backup=False):
    if overwrite:
        print("deleting", destination)
    if backup:
        print("backing up")
    print("moving", source, "to", destination)

Then we can move files like this:

>>> move('file1.txt', 'file2.txt')
moving file1.txt to file2.txt
>>> move('file1.txt', 'file2.txt', overwrite=True)
deleting file2.txt
moving file1.txt to file2.txt
>>>
This works just fine, but if we accidentally give the function three filenames, bad things will happen:
>>> move('file1.txt', 'file2.txt', 'file3.txt')
deleting file2.txt
moving file1.txt to file2.txt
>>>

Oh no, that’s not at all what we intended.

The original file2.txt has been gone!

The issue was that overwrite was now ‘file3.txt,’ and the if overwrite section of the code interpreted the text as True and erased the file.

That’s not a pleasant thing to say.

The answer is to make our move function keyword-only for overwrite and backup:

def move(source, destination, *, overwrite=False, backup=False):
    ...

The asterisk (*) between destination and overwrite indicates that overwrite and backup must be specified as keyword arguments.

The core concept is straightforward: it is now impossible to overwrite by using move(‘file1.txt’, ‘file2.txt’, True), and the overwrite must always be specified as overwrite=True.

>>> move('file1.txt', 'file2.txt')
moving file1.txt to file2.txt
>>> move('file1.txt', 'file2.txt', True)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: move() takes 2 positional arguments but 3 were given
>>> move('file1.txt', 'file2.txt', 'file3.txt')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: move() takes 2 positional arguments but 3 were given
>>> move('file1.txt', 'file2.txt', overwrite='file3.txt')
deleting file2.txt
moving file1.txt to file2.txt
>>>

Doing overwrite='file3.txt' doesn't make much sense and it's easy to notice that something's wrong.

When Should We Make Use of These Items?

Returning a tuple from a function is perfectly acceptable, and you are free to do it anytime you need it.

Most of the functions we develop don’t require *args and *kwargs. We require args and **kwargs when we need to construct anything that takes whatever arguments it’s provided or calls a function with arguments from a list, and there’s no reason to avoid them. With functions like print box, I don’t suggest utilizing keyword-only parameters.

It’s not difficult to figure out or remember what print box(‘hello’, ‘-’) does, so there’s no need to argue. Because remembering what move(‘file1.txt’,’file2.txt’, True, False) performs is substantially more difficult, utilizing keyword-only parameters makes sense.

Summary

  • If you want to return multiple values from a function you can return a tuple.

  • Defining a function that takes *args as an argument makes args a tuple of positional arguments. **kwargs is the same thing with dictionaries and keyword arguments.

  • Adding a * in a function, the definition makes all arguments after it keyword-only. This is useful when using positional arguments that would look implicit, like the True and False in move('file1.txt', 'file2.txt', True, False).

And there you have it. Thank you for reading.

Don’t forget to clap and follow me to beat Medium’s algorithm and read more articles like this in the future.

More content at plainenglish.io





About author

Harendra
Harendra Kanojiya

Hello, I am Harendra Kumar Kanojiya - Owner of this website and a Fullstack web developer. I have expertise in full-stack web development using Angular, PHP, Node JS, Python, Laravel, Codeigniter and, Other web technologies. I also love to write blogs on the latest web technology to keep me and others updated. Thank you for reading the articles.



Related Posts -

Getting started with Python
Getting started with Python ...

&gt;&gt;&gt; It means Python is ready and can install the command. The basi...



How to Use the ZIP function in Python
How to Use the ZIP function ...

The zip function in Python is a useful tool for combining two or more itera...



20 Python Snippets You Should Learn in 2021
20 Python Snippets You Shou ...

Python is one of the most popular languages used by many in Data Science, m...



Python 3 speech recognition (speech to text)
Python 3 speech recognition ...

Speech Recognition is an important feature in several applications used suc...





Follow Us

Follow us on facebook Click Here

Facebook QR
Scan from mobile
Join our telegram channel Click Here
Telegram QR
Scan from mobile