If we don't need to know the indexes of the occurrences we can use the method count from the String class. We can also indicate search boundaries (start, end).

sub_sting = "apple"
print(f"Count of occurrences for '{sub_sting}': {my_string.count(sub_sting)}")

start = 5
end = 25
print(f"Count of occurrences for '{sub_sting}' from the interval [{start}, {end}]: {my_string.count(sub_sting, start, end)}")

Output:

Count of occurrences for 'apple': 3
Count of occurrences for 'apple' from the interval [5, 25]: 1

Otherwise, if we also need start and end indexes of the occurrence we can use re (regular expression) package and its method finditer. Code example:

import re

sub_sting = "apple"
occurrences = list(re.finditer(sub_sting, my_string))
for o in occurrences:
    print(f"Find occurrence for '{sub_sting}': start={o.start()}, end={o.end()}")
print(f"Total occurrences count: {len(occurrences)}")

Output:

Find occurrence for 'apple': start=0, end=5
Find occurrence for 'apple': start=15, end=20
Find occurrence for 'apple': start=26, end=31
Total occurrences count: 3

This method doesn't support interval params but we can perform this method for a substring. Something like this:

import re

sub_sting = "apple"
start = 5
end = 25

occurrences = list(re.finditer(sub_sting, my_string[start:end]))
for o in occurrences:
    print(f"Find occurrence for '{sub_sting}': start={o.start()}, end={o.end()}")
print(f"Total occurrences count: {len(occurrences)}")

Output:

Find occurrence for 'apple': start=10, end=15
Total occurrences count: 1