5

Let's say I have a simple method which requires a list of strings.

 def make_fruits_lower_case(list_of_fruits):
    """make fruits pretty bla bla bla"""
    return [fruit.lower() for fruit in list_of_fruits]

Use case 1: a developer provides a list of fruits and it works fine. Expected behaviour.

make_fruits_lower_case(['APPLE', 'ORANGE']) -- > ['apple', 'orange']

Use case 2: Let's say some other developer, intentionally or unintentionally, provides it a string.

make_fruits_lower_case('APPLE') --> ['a', 'p', 'p', 'l', 'e']

What is the pythonic way to handle such situation?

1: Introduce argument validation

def make_fruits_lower_case(list_of_fruits):
    if isinstance(list_of_fruits, list):
            return [fruit.lower() for fruit in list_of_fruits]raise 
        TypeError('list_of_fruits must be a of type list')
    

2: Expect developer in use case 2 to provide a list.

Beside this specific situation, It would be great to know what are pythonic recommendations to handle such situations in general such that we expect the developers to make sure that they provide right arguments or should we add some basic validations?.

utengr
  • 3,225
  • 3
  • 29
  • 68
  • 4
    Just define your function as `make_fruits_lower_case(*list_of_fruits)` and problem will disappear. `list of fruits` will be a tuple which contains all arguments you passed to function. – Olvin Roght Jun 22 '21 at 12:53
  • 3
    In general, you assume that the user is a consenting adult – if they are misusing your function, it's on them. You cannot predict all the ways someone might do wrong. – MisterMiyagi Jun 22 '21 at 13:00
  • Just a minor observation: in your first example of "introducing argument validation", a better way would be to exit early by negating the condition of the `if` statement: `if not isinstance(..., list):`, otherwise continue with the normal behaviour. – Alexandru Dinu Jun 22 '21 at 13:00
  • 1
    The real question is: Why do you expect a *list* of fruits? This would just as well work with a generator, or iterable, or similar. – MisterMiyagi Jun 22 '21 at 13:11

2 Answers2

1
  1. Your validation error is considered pythonic:
def make_fruits_lower_case(list_of_fruits):
    if isinstance(list_of_fruits, list):
        return [fruit.lower() for fruit in list_of_fruits]
    raise TypeError('list_of_fruits must be a of type list')

But in order to make it clear that your function takes in a list, you can specify the type of input parameters required (and output type to expect):

def make_fruits_lower_case(list_of_fruits : list) -> list:
    if isinstance(list_of_fruits, list):
        return [fruit.lower() for fruit in list_of_fruits]
    raise TypeError('list_of_fruits must be a of type list')
Red
  • 26,798
  • 7
  • 36
  • 58
  • 2
    The type hints can be improved by using: `List[str]` instead of just `list`, at the "expense" of `from typing import List`. – Alexandru Dinu Jun 22 '21 at 13:02
  • 3
    @AlexandruDinu, or even better `Iterable[str]` – Olvin Roght Jun 22 '21 at 13:03
  • @AlexandruDinu That's right. But it can also introduce errors: https://stackoverflow.com/questions/67278017/pip-command-line-importerror-no-module-named-typing – Red Jun 22 '21 at 13:04
  • 1
    @AnnZen, but you're using [feature](https://www.python.org/dev/peps/pep-0585/) which added in python 3.9 . – Olvin Roght Jun 22 '21 at 13:05
  • 2
    @OlvinRoght [They were introduced into Python in version 3.5](https://www.python.org/dev/peps/pep-0484/) – Red Jun 22 '21 at 13:13
  • 1
    @AnnZen, but you're using `list`, not `List` . Possibility to use standard collections in type hinting have been added to python 3.9, I've added link to corresponding PEP in previous comment. – Olvin Roght Jun 22 '21 at 13:31
  • @OlvinRoght I know, but when users see your comment *"but **you're** using feature which added in python 3.9"* they would assume that it's a flaw in my answer. – Red Jun 22 '21 at 15:17
  • @AnnZen, you **are** using feature that have been introduced in python 3.9 **in your answer** and at same time [blaming](https://stackoverflow.com/posts/comments/120335124?noredirect=1) Alexandr that he [advised](https://stackoverflow.com/posts/comments/120335080?noredirect=1) to use more precised typing attaching question where error occurred because of python 3.4 doesn't support typing. I found this strange. – Olvin Roght Jun 22 '21 at 15:25
  • @OlvinRoght Gee, sorry if it seemed like it, but I'm not blaming anyone here. I'm just saying that type hinting existed before python 3.9. Anyway, I'm confused... if I'm using a feature that have been introduced in python 3.9, then how come it works in my python version that's lower than 3.9? – Red Jun 22 '21 at 16:00
  • 1
    @AnnZen, it doesn't throw an error and doesn't work for type validation. For python lower than 3.9 you should import [`typing.List`](https://docs.python.org/3/library/typing.html#typing.List), starting from 3.9 you can use standard collections in typing. – Olvin Roght Jun 22 '21 at 16:09
0

@Olvin Roght gave the most pytonic way but you can also perform function overloading using singledispatch from functools to handle as many cases as types of input you want.

from functools import singledispatch


@singledispatch
def make_fruits_lower_case(s):
    return s


@make_fruits_lower_case.register(list)
def _1(s):
    return [fruit.lower() for fruit in s]


@make_fruits_lower_case.register(str)
def _2(s):
    return s.lower()


if __name__ == "__main__":
    print(make_fruits_lower_case("APPLE")) # apple
    print(make_fruits_lower_case(["APPLE", "ORANGE"])) # ['apple', 'orange']
yudhiesh
  • 6,383
  • 3
  • 16
  • 49