3

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?

S12312
  • 45
  • 5
  • 4
    You could throw an exception. You could return -1, you could return None. – Thomas Weller Mar 08 '22 at 13:59
  • 3
    Your function name is misleading, suggesting it should just be returning the boolean in the first place – Sayse Mar 08 '22 at 14:04
  • If you expect your function to only return an index (an integer), it can even return a random string and then you can check the type... – Tomerikoo Mar 08 '22 at 14:35
  • 2
    I can't help but wonder why you'd want to return the index. Chances are you're going to use the index to reference the *students* list. So why not return a reference to a *student* object or None if it's not found? – DarkKnight Mar 08 '22 at 15:01
  • 1
    @ArthurKing, ive been avoiding using the object since it looks like this: `<__main__.Student at 0x1e2fc667e20>`, and is more confusing for me when debugging. Is there a reason for me to reference the actual object rather than its position in the list? – S12312 Mar 08 '22 at 15:33
  • @S12312 It depends on how you're going to use it – DarkKnight Mar 08 '22 at 15:36
  • @ArthurKing I have a list, and ive used this function to find the student in the list (if it exists) before doing things like modifying/deleting/adding – S12312 Mar 08 '22 at 15:50
  • @S12312 If *that* is your problem with returning the instance itself, then see [How to print instances of a class using print()?](https://stackoverflow.com/q/1535327/6045800) – Tomerikoo Mar 08 '22 at 15:52

8 Answers8

2

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)

Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
Tomer Geva
  • 1,764
  • 4
  • 16
2

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
Ruli
  • 2,592
  • 12
  • 30
  • 40
  • I should probably stay away from -1 in case later in the code I make a mistake and use it as an index. None seems to have worked. Thanks! – S12312 Mar 08 '22 at 14:06
2

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
Red
  • 26,798
  • 7
  • 36
  • 58
  • That's just one example. [`str.index`](https://docs.python.org/3/library/stdtypes.html#str.index) on the other hand raises an exception – Tomerikoo Mar 08 '22 at 14:37
  • @Tomerikoo Sure, that's why I used `str.find()` as an example rather than `str.index()`. The OP asked *what* their function should return, not *whether* or not is should return anything at all :) – Red Mar 08 '22 at 14:43
  • Just saying... If we inspect how Python handles things, raising an exception is another possible approach... – Tomerikoo Mar 08 '22 at 15:49
2

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
Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
  • @AnnZen */faceslap/* You're totally right! Don't know what I was thinking. You are correct that it is `O(N*2)` (==`O(N)`) and not `O(N^2)`. Thanks for the correction. Will edit :) – Tomerikoo Mar 08 '22 at 16:28
1

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.

DarkKnight
  • 19,739
  • 3
  • 6
  • 22
0

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.
codester_09
  • 5,622
  • 2
  • 5
  • 27
0

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.

Stuart
  • 9,597
  • 1
  • 21
  • 30
-1

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:
    ...
Waket Zheng
  • 5,065
  • 2
  • 17
  • 30