__getitem__

Documentatation

  1. Special method names in the Python Language Reference
  2. Emulating container types in the Python Language Reference
    1. __len__
    2. __getitem__

If I hadn’t given an __iter__ method to class Month, the for loop would have called the __getitem__ method of class Month and passed the illegal argument 0 to __getitem__.

"""
This module is month.py
"""
import datetime
import calendar

class Month(object):
    "A Month object can be subscripted to return a datetime.date object of the Month."

    def __init__(self, month, year):
        if not isinstance(year, int):
            raise TypeError(f"year must be int, not {type(year)}")
        if not isinstance(month, int):
            raise TypeError(f"month must be int, not {type(month)}")
        if month < 1 or month > 12:
            raise ValueError(f"bad month {month}")
        self.year = year
        self.month = month
        self.last = calendar.monthrange(self.year, self.month)[1] #last day of month

    def __len__(self):
        return self.last;

    def __getitem__(self, day):
        if not isinstance(day, int):
            raise TypeError(f"day must be int, not {type(day)}")
        if day < 1 or day > self.last:
            raise ValueError(f"bad day {day} must be in range 1 to {self.last}")
        return datetime.date(self.year, self.month, day)

    def __iter__(self):
        return Month_iter(self.month, self.year, self.last)


class Month_iter(object):
    def __init__(self, month, year, last):
        self.year = year
        self.month = month
        self.current = 1
        self.last = last

    def __iter__(self):
        return self

    def __next__(self):
        if self.current > self.last:
            raise StopIteration
        d = datetime.date(self.year, self.month, self.current)
        self.current += 1
        return d

if __name__ == "__main__":
    import sys
    today = datetime.date.today()
    month = Month(today.month, today.year)
    d = month[today.day]
    print(d.strftime("%A, %B %-d, %Y"))
    sys.exit(0)
import sys
import month

november = month.Month(11, 2019)
print(f"len(november) = {len(november)}")   #calls november.__len__()

d = november[19]   #does the same thing as d = november.__getitem__(19)
print(d.strftime("%A, %B %-d, %Y"))
print()

#Print the entire month.
for d in november:
    print(d.strftime("%A, %B %-d, %Y"))

sys.exit(0)
len(november) = 30
Tuesday, November 19, 2019

Friday, November 1, 2019
Saturday, November 2, 2019
Sunday, November 3, 2019
Monday, November 4, 2019
Tuesday, November 5, 2019
Wednesday, November 6, 2019
Thursday, November 7, 2019
Friday, November 8, 2019
Saturday, November 9, 2019
Sunday, November 10, 2019
Monday, November 11, 2019
Tuesday, November 12, 2019
Wednesday, November 13, 2019
Thursday, November 14, 2019
Friday, November 15, 2019
Saturday, November 16, 2019
Sunday, November 17, 2019
Monday, November 18, 2019
Tuesday, November 19, 2019
Wednesday, November 20, 2019
Thursday, November 21, 2019
Friday, November 22, 2019
Saturday, November 23, 2019
Sunday, November 24, 2019
Monday, November 25, 2019
Tuesday, November 26, 2019
Wednesday, November 27, 2019
Thursday, November 28, 2019
Friday, November 29, 2019
Saturday, November 30, 2019

Class MyStr

An object of class mystr.MyStr contains an object of class str. Therefore an object of class mystr.MyStr_iterator contains an object of class str_iterator.

"""
This module is mystr.py
"""

class MyStr(object):
    def __init__(self, s = ""):
        if isinstance(s, str):
            self.s = s
        elif isinstance(s, MyStr):
            self.s = str(s)
        else:
            raise TypeError(f"s must be str or MyStr, not {type(s)}")

    def __str__(self):
        return self.s

    def __len__(self):
        return len(self.s)

    def __getitem__(self, key):
        le = len(self)
        if isinstance(key, int):
            if key < -le or key >= le:
                raise IndexError(f"index {key} must be in range {-le} to {le-1} inclusive")
            return MyStr(self.s[key])
        elif isinstance(key, slice):
            if key.start != None and not isinstance(key.start, int):
                raise TypeError(f"start must be int, not {type(key.start)}")
            if key.stop != None and not isinstance(key.stop, int):
                raise TypeError(f"stop must be int, not {type(key.stop)}")
            if key.step != None and not isinstance(key.step, int):
                raise TypeError(f"step must be int, not {type(key.step)}")
            if key.step == 0:
                raise ValueError("step can't be 0")
            return MyStr(self.s[key.start:key.stop:key.step]) #or return MyStr(self.s[key])
        else:
            raise TypeError(f"key must be int or slice, not {type(key)}")

    def __iter__(self):
        return MyStr_iterator(self.s)


class MyStr_iterator(object):
    def __init__(self, s):
        self.s = s
        self.iter = iter(self.s)      #self.iter is a str_iterator

    def __iter__(self):
        return self

    def __next__(self):
        return MyStr(next(self.iter)) #next(self.iter) is a str


if __name__ == "__main__":
    import sys
    ms = MyStr("hello")
    print(ms)
    sys.exit(0)

Thanks to the __getitem__ method, the following for loop would have worked even if class MyStr had no __iter__ method.

import mystr

ms = mystr.MyStr("hello")
print(f'ms = "{ms}"')
print(f"len(ms) = {len(ms)}")

empty = mystr.MyStr()
print(f'empty = "{empty}"')
print()

print(ms[0])
print(ms[0:3])
print(ms[:3])
print(ms[-1::-1])
print()

for c in ms:   #c is a mystring.MyString
    print(c)
ms = "hello"
len(ms) = 5
empty = ""

h
hel
hel
olleh

h
e
l
l
o