1

Background

I am new to python and I am writing a simple function but I am also interested in learning to do things the correct / pythonic way as I progress in my journey.

Lets consider the function below

def test_func(nested_lists,val):


    return

I am expecting two arguments. One argument would be a list containing more lists. Something like this [[1,2,3,],[4,5,6,]...]. The second argument could be a value like 1.

If someone say for instance passes in a single value as the first argument and an array as the second argument. My code as it is currently returning the correct output which is 0 , However is there another way that i should be handle this?

For example should I be doing something like this

if(type(value) == list):
   return 0

Or do i not need to do anything because my function is returning 0 anyway.

I know this maybe a very basic question so please forgive me but coming from a java background I am new to python so i am not sure how to handle such scenarios in python.

7
  • @Sayse I mean one is going to be a list of list another is going to be a single value and the purpose of function is to search all instances of value and do something with it. The core logic i understand but i am kind of curious how to handle things I mentioned above. Commented Oct 16, 2019 at 19:40
  • Typically, you don't check the types. You just document the expected interface, and it is the user's responsibility to provide a value of a conforming type. Commented Oct 16, 2019 at 19:41
  • @Dinero, you say you have java background. How a java programer would solve this problem ? My basic answer is to use exception. So does Python. Now, how exceptionworks in Python. You should check that. It will help you to achieve your goal... Commented Oct 16, 2019 at 19:42
  • @Sayse Define "incompatible". dict and str are completely compatible types to a function that simply wants to iterate over its argument. Commented Oct 16, 2019 at 20:01
  • 1
    No, that would be very bad :) Commented Oct 16, 2019 at 20:16

2 Answers 2

2

The other answer illustrates the proper way to check in advance for problems you can foresee. I'll provide a different approach.

The idiomatic solution in python is to "ask forgiveness, not permission". There are a lot of things that can go wrong, and where other languages might ask you to foresee all those problems and address them manually, python encourages just handling them as they happen. I would recommend doing:

def test_func(nested_lists, val):
    try:
        ...
    except TypeError:
        # do whatever error-handling behavior you need to
        # either throw a custom exception or return a specific value or whatever
        return 0

and then designing your code in such a way that, if nested_lists and values are not compatible types, then they throw a TypeError (e.g. trying to iterate through nested_lists should fail if nested_lists is not a list. You can experiment with this behavior in a python console, but in general trying to do something to a variable that doesn't work because it's not the right type will produce a TypeError).

Sign up to request clarification or add additional context in comments.

3 Comments

This is the preferred approach because a value of type list isn't necessarily the only thing that you function could use. Given a very simply definition like return nested_lists[0][0], the only requirement is that type(nested_lists).__getitem__ be defined, and that what it returns also support __getitem__.
@chepner That's true, but who are we to prevent someone from using their own custom subclass of list, or something sharing the same interface, with our function? At a certain point you move beyond the possibility of 'stupidity' and misuse becomes obvious 'malice'. By python's principles, the 'malice' should be encouraged - if you want to break the method by using it wrong, go ahead, but it should be clear that you're doing it on purpose.
I think you are misreading my comment (or I wrote something I did not intend to); I fully agree that simply trying to use nested_lists without worrying about its type is the Right Thing To Do, and that you can optionally try to catch any problems that might arise.
1

If your current code is working correctly, there is no pressing need to change anything. However, there are some reasons you might want to code more defensively;

If the code will seem to work correctly when you pass in bogus values, it would be better if it raised an exception instead of return a bogus value. The responsibility to call it correctly lies squarely with the caller, but enforcing it can help make sure the code is correct.

if not isinstance(nested_lists,list):
    raise ValueError('Need a list, got {0!r}'.format(nested_lists))

This has the drawback that it hardcodes list as the type for the first argument; properly reusable code should work with any type, as long as it has the required methods and behaviors to remain compatible with your implementation. Perhaps instead check for a behavior:

try:
    something involving nested_lists[0][0]
except (IndexError,  AttributeError):
    raise ValueError('Expected nested list but got {0!r}'.format(nested_lists))

(The try is not strictly necessary here; but see below.)

If you get a traceback when you call the code incorrectly, but it is opaque or misleading, it is more helpful to catch and explicitly point out the error earlier. #or example, the snippet above (without the try wrapper) would produce

Traceback (most recent call last):
  module __main__ line 141
    traceback.print_exc()
  module <module> line 1
    test_func(1,1)
  module <module> line 2

AttributeError: 'int' object has no attribute '__getitem__'

which is somewhat unobvious to debug.

If the code will be used by third parties, both of the above considerations will be more important from a support point of view, too.

Notice how the code raises an exception when called incorrectly. This is generally better than silently returning some garbage value, and the caller can similarly trap the error with a try/except if this is well-defined (i.e. documented!) behavior.

Finally, since Python 3.5, you have the option to use type annotations:

def test_func(nested_lists: list, val: int) -> int:
    ...

As noted in the documentation, the core language does not (yet?) enforce these type checks, but they can help static code analysis tools point out possible errors.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.