If my function returns list index, what should it return if position does not exist

Viewed 550

I wrote a function that returns the index of an item in a list if that item exists, otherwise return False

def student_exists(ID):
    for student in students:
        if student.id == ID:
            return students.index(student)
    return False

But then I realised that it can be an issue later since later in my code I did something like this:

if student_exists(ID) == False

which will be true if the function returned index 0.

What can I use instead of false to represent the item not existing in the list?

8 Answers

You can return None if the item does not exist.

When you return None, you will avoid the location 0 problem. Note that when trying to ask if something is None you should use: if x is None.

Note that the is operator should be used for checking None, otherwise you can experience unexpected behavior (see "is" operator behaves unexpectedly with integers)

You can do this:

def student_exists(ID):
    for student in students:
        if student.id == ID:
            return students.index(student)
    return None

Or you can retrun index that is not normally returned, such as -1:

def student_exists(ID):
    for student in students:
        if student.id == ID:
            return students.index(student)
    return -1

Observe how Python handles situations like this:

>>> string = "hello world"
>>> print(string.find("w"))
6
>>> print(string.find("z"))
-1
>>> 

As you can see, the str.find() method returns -1 when the substring is not found in the string. So you can do

def student_exists(ID):
    for student in students:
        if student.id == ID:
            return students.index(student)
    return -1

Another good option would be to use None. But when checking if the output is None, use the is keyword rather than the == operator:

if student_exists(ID) is None

If you want, you can leave the function as it is, and simply use the return value differently. Instead of if student_exists(ID) == False do:

if student_exists(ID) is False

See When to use == and when to use is?

>>> False == 0
True
>>> False is 0
False

Just because no one else even mentioned it, the way your function is implemented is not so efficient/optimal and is doing one extra unnecessary pass of the list. You are looping over the list to find a matching element, but then index will loop the list again behind the scenes. Instead, you should Access the index in 'for' loop:

def student_exists(ID):
    for i, student in enumerate(students):
        if student.id == ID:
            return i
    return False

As mentioned in an earlier comment, returning an index into a list is not a good idea. Better, IMO, to return a reference to the object itself (or None if it can't be found).

Allow me to elaborate...

Let's assume that our student class looks like this:

class Student:
    def __init__(self, name, id_):
        self.name = name
        self.id_ = id_
    def __repr__(self):
        return f'Student: name={self.name} id={self.id_}'

...and that we have a global list of students...

students = [Student('Arthur', 100),
            Student('Lancelot', 101)]

Now we need a function to search the list for a student with a specific id:

def find_student(student_list, id_):
    for student in student_list:
        if id_ == student.id_:
            return student

Then this is how we can bring everything together:

for i in [100, 101, 102]:
    if (student := find_student(students, i)):
        print(student)
    else:
        print(f'Student {i} not found')

Which will give this output:

Student: name=Arthur id=100
Student: name=Lancelot id=101
Student 102 not found

Notes:

Note the trailing underscore for the student id variable (id_). This is to avoid any possible conflict with the builtin id function.

Added a repr dunder function to the class to facilitate simple printing of a class instance.

Even though the student list is global, it's better to pass a reference to the find_student function. After all, somewhere else in the code you might have another list of students. This makes the function reusable.

And finally, this is the wrong way to maintain a list of students. Assuming their IDs are unique then a dictionary is more appropriate and has the added benefit of making a lookup much easier and faster. If the IDs are not unique then the find_student function could give unpredictable results.

There is more than one way to do this one of the ways used by @Ruli.

There is some information for you : 0 and an empty string is always False

if 0: #==> False
    print('Yes') #This line of code is not executed because the above expression is False.
a = ''
if a:# False
    print('Yes') #This line of code not execute because the above expression is False.

The way I do is:

def student_exists(ID):
    for student in students:
        if student.id == ID:
            return students.index(student)
    return False

output = student_exists(ID)
if output == False and output != 0:
    # write your code here.

Behavior when searching for an item in a collection of some kind, and the item is not found, is quite varied across Python.

In the standard library:

External modules commonly return None, e.g. BeautifulSoup find, Google App Engine Model.get_by_id, MongoDB find_one. Raising an error might be more logical in some cases, but returning None is probably more convenient, avoiding the need for a lot of try...except blocks.

In your case, I would rename your function something like get_student_by_id and return None if it is not found.

Use None and enumerate

def student_exists(ID):
    return {s.id: index for index, s in enumerate(students)}.get(ID)

if student_exists(ID) is None:
    ...
Related