Returning Multiple Values In Python Is a Bad Practice

Returning Multiple Values In Python Is a Bad Practice

Reading Time: 4 minutes

Python is great. It has a lot of cool features and perks within the language itself and the standard library. But sometimes we overuse some of those features or maybe the right term is misuse. I’m here to take responsibility for some functions that are written in our products. Even though I wasn’t necessarily the developer who written them, I am the one to blame to make it hang in the code base for months or even years. I won’t provide the real code here because it is private, but let’s take a look at the following function (this is a pseudo code function that is pretty much a copy of some functions in our code base):

def get_user_and_token(http_request, ignore_logged_in=False, check_email_valid=False):
	if not ignore_logged_in:
		email = get_user_email()
		if not_blank(email.lower()):
			domain_string = get_domain_string(email)
			domain = Domain.get(domain_string)
			user = User.get(email)
			if domain and user:
				return (False, None, None)

		has_token, user = get_user(http_request)
		if has_token:
			return (False, None, None)

	if token_exists(http_request):
		token = get_token(http_request)
		token_to_check = get_token(user)
		if token = token_to_check:
			domain = Domain.get(user)
			if domain and check_email_valid:
				if user._email_valid:
					return (True, Domain, user)
				else:
					return (False, None, None)
			else:
				return (True, domain, user)

	return (False, None, None)

Please take a look at the returning values, can you tell by the implementation of the function what each parameter means? Don’t you see how can we use this function with all those (False, None, None)‘s? Me neither. The only place that gives me a hint is in the invocation of this function¬†user_has_token, domain, user = get_user_and_token(http_request=http.request). Let’s leave the issues with the naming of the function which not exactly tells us what the function is going to do (function names should always tell exactly what they do – this is a MUST for clean code) and the horrible logic in the body of the function.

Either if the values returned are related to each other or not, it will be better to know what are we returning from the implementation of the function, and have better access to those values. That sounds like a return of some data structure is what we are looking for. Python provide the namedtuple factory function for those kind of situations. So the first step to get more readable code is to use it as here:

from collections import namedtuple

def get_user_and_token(http_request, ignore_logged_in=False, check_email_valid=False):
	UserMeta = namedtuple("UserMeta", ["user_has_token", "domain", "user"])
	if not ignore_logged_in:
		email = get_user_email()
		if not_blank(email.lower()):
			domain_string = get_domain_string(email)
			domain = Domain.get(domain_string)
			user = User.get(email)
			if domain and user:
				return UserMeta(False, None, None)

		has_token, user = get_user(http_request)
		if has_token:
			return UserMeta(False, None, None)

	if token_exists(http_request):
		token = get_token(http_request)
		token_to_check = get_token(user)
		if token = token_to_check:
			domain = Domain.get(user)
			if domain and check_email_valid:
				if user._email_valid:
					return UserMeta(True, Domain, user)
				else:
					return UserMeta(False, None, None)
			else:
				return UserMeta(True, domain, user)

	return UserMeta(False, None, None)

Now you know what type of values you are returning which makes it much more readable, and the access to the values is more verbose:

user_meta = get_user_and_token(http_request=http.request)
print(user_meta.user_has_token)
print(user_meta.domain)
print(user_meta.user)

Conclusion

Of course the function will be re-factored and partitioned into much more smaller functions to follow SRP (single responsibility principle). This is a small step toward cleaner code.

Cheers.