Een Date klasse#

Data

  • dag

  • maand

  • jaar

class Date:
    """Date is a user-defined data structure --
       a class that stores and manipulates dates
    """
    def __init__(self, day, month, year):
        """The constructor for objects of type Date
        """
        self.day = day
        self.month = month
        self.year = year

    def __repr__(self):
        """This method returns a string representation for the
           object of type Dat that calls it (named self)
        """
        return f"{self.day:02d}/{self.month:02d}/{self.year:04d}"

    def is_leap_year(self):
        """Returns True if self, the calling object, is
           in a leap year; False otherwise
        """
        if self.year % 400 == 0: return True
        if self.year % 100 == 0: return False
        if self.year % 4 == 0: return True
        return False

Een object d#

Stap voor stap#

class Date:
    """Date is a user-defined data structure --
       a class that stores and manipulates dates
    """

Dit is het begin van een nieuw type Date aangeven door het class keyword

Let op de dubbele punt, dit betekent dat alles wat volgt ingesprongen moet worden omdat het bij de klasse hoort.

    def __init__(self, day, month, year):
        """The constructor for objects of type Date
        """
        self.day = day
        self.month = month
        self.year = year

Dit is de constructor voor Date objecten. Dit is de plek voor waar input data wordt toegekend aan de (data) velden.

d = Date(11, 12, 2013)

De velden zijn de informatie die in elk Date object aanwezig zijn.

    def __repr__(self):
        """This method returns a string representation for the
           object of type Dat that calls it (named self)
        """
        return f"{self.day:02d}/{self.month:02d}/{self.year:04d}"

Dit is de string representatie van Date objecten, het vertelt Python hoe een Date object moet worden geprint.

f"strings"#

Strings formatteren, handig!

klassen = 12
studenten = 25
f"{klassen} klassen met {studenten} studenten is in totaal {klassen * studenten}"
'12 klassen met 25 studenten is in totaal 300'
x = 1
f"{x:02d}"
'01'

02d formatteert de integer x tot een string met een minimale breedte van 2 posities, en “zero-padding” (voorloopmullen) links, indien nodig.

    def is_leap_year(self):
        """Returns True if self, the calling object, is
           in a leap year; False otherwise
        """
        if self.year % 400 == 0:
            return True
        if self.year % 100 == 0:
            return False
        if self.year % 4 == 0:
            return True
        return False

Waarom wordt self gebruikt en niet d?

self#

Is de variabele die de methode aanroept

Misschien heb je al kennisgemaakt met andere programmeertalen en zal je herkennen dat self overeenkomt met wat je kent als this in deze talen (bijvoorbeeld in Java of C++). Guido van Rossum geeft hier antwoord op de vraag waarom niet voor this is gekozen, en legt het uit als een daad van speels verzet.

Het is hier misschien ook van belang om stil te staan bij het feit dat programmeertalen niet zomaar zijn ontstaan, het is mensenwerk en wij bouwen weer voort op het werk van anderen!

wd = Date(11, 12, 2013)
print(wd)
11/12/2013
wd.is_leap_year()
False
d = Date(11, 12, 2020)
print(d)
11/12/2020
d.is_leap_year()
True

Quiz#

De anatomie van een klasse

class Date:                                                        # (1)
    def __init__(self, day, month, year):                          # (2)
        self.day = day                                             # (3)
        self.month = month                                         # (4)
        self.year = year                                           # (5)

    def __repr__(self):                                            # (6)
        return f"{self.day:02d}/{self.month:02d}/{self.year:04d}"  # (7)

    def is_leap_year(self):                                        # (8)
        if self.year % 400 == 0: return True
        if self.year % 100 == 0: return False
        if self.year % 4 == 0: return True
        return False
                                                                   # (9)
wd = Date(11, 12, 2013)                                            # (10)
ny = Date(1, 1, 2021)                                              # (11)

Probeer de volgende onderdelen aan te wijzen:

  • class keyword

  • waar eindigt de klassedefinitie?

  • objectdefinities, 2 totaal

  • methoden, 3 totaal

  • constructor

  • datavelden (attributen), 3 totaal

  • wat print Date’s?

Oplossing#

  • class keyword: (1)

  • waar eindigt de klassedefinitie?: (9)

  • objectdefinities, 2 totaal: (11, 12)

  • methoden, 3 totaal: (2, 6, 8)

  • constructor: (2)

  • datavelden (attributen), 3 totaal: (3, 4, 5)

  • wat print Date’s?: (6)

Methoden en operatoren#

Als alles een object is …

21 < 42
True
"🥚" > "🐔"
True
history = Date(1, 1, 1970)
epoch = Date(1, 1, 1970)
epoch == history
False

Dit werkt niet en dat is jammer! Dit gaan we straks oplossen, je zal Python moeten vertellen dat de operator == (en anderen) betekenis heeft als we objecten van type Date willen gaan vergelijken.

Operatoren#

methode

operator

yesterday(self)

-= 1

tomorrow(self)

+= 1

add_n_days(self, N)

+= N

sub_n_days(self, N)

-= N

is_before(self, other)

<

is_after(self, other)

>

diff(self, other)

-

dow(self)

What’s the diff?#

epoch = Date(1, 1, 1970)
today = Date(6, 12, 2020)

Via een methode

today.diff(epoch)
18602
epoch.diff(today)
-18602

Where’s the dow?#

sm1 = Date(28, 10, 1929)
sm2 = Date(19, 10, 1987)

Gebruikt een named object

sm1.dow()
'Monday'
sm2.dow()
'Monday'

Zonder naam, unnamed!

Date(1,1,1).dow()
'Monday'
Date(1,1,2021).dow()
'Friday'
str("Astronaut wordt snel oud tijdens een reis naar Mars").split()
['Astronaut', 'wordt', 'snel', 'oud', 'tijdens', 'een', 'reis', 'naar', 'Mars']
"Astronaut wordt snel oud tijdens een reis naar Mars".split()
['Astronaut', 'wordt', 'snel', 'oud', 'tijdens', 'een', 'reis', 'naar', 'Mars']

Het == probleem#

wd = Date(11, 12, 2013)
wd
11-12-2013
wd2 = Date(11, 12, 2013)
wd2
11-12-2013
wd == wd2
True

Waarde versus identiteit#

id(wd)
139902871396944
id(wd2)
139902871403152

== zal standaard controleren op identiteit, de geheugenlokatie!

Vergelijken op waarde#

Laten we een eigen test voor gelijkwaardigheid schrijven!

class Date:
    def __init__(self, day, month, year):
        """The constructor for objects of type Date
        """
        self.day = day
        self.month = month
        self.year = year

    def __repr__(self):
        return f"{self.day:02d}/{self.month:02d}/{self.year:04d}"

    def is_leap_year(self):
        if self.year % 400 == 0: return True
        if self.year % 100 == 0: return False
        if self.year % 4 == 0: return True
        return False

    def equals(self, other):
        """Returns True if they represent
           the same date; False otherwise
        """
        if (
            self.year == other.year
            and self.month == other.month
            and self.day == other.day
        ):
            return True
        else:
            return False
wd = Date(11, 12, 2013)
wd2 = Date(11, 12, 2013)
wd.equals(wd2)
True
wd2.equals(wd)
True

Maar …

wd == wd2
False

__eq__#

Vertel Python wat wij met gelijkwaardigeid bedoelen!

class Date:
    def __init__(self, day, month, year):
        """The constructor for objects of type Date
        """
        self.day = day
        self.month = month
        self.year = year

    def __repr__(self):
        return f"{self.day:02d}/{self.month:02d}/{self.year:04d}"

    def is_leap_year(self):
        if self.year % 400 == 0: return True
        if self.year % 100 == 0: return False
        if self.year % 4 == 0: return True
        return False

    def __eq__(self, other):
        """Returns True if they represent
           the same date; False otherwise
        """
        if (
            self.year == other.year
            and self.month == other.month
            and self.day == other.day
        ):
            return True
        else:
            return False
wd = Date(11, 12, 2013)
wd2 = Date(11, 12, 2013)
wd == wd2
True

Hergebruik#

De methode equals blijven gebruiken maar ook Python vertellen over gelijkwaardigheid (__eq__)?

class Date:
    def __init__(self, day, month, year):
        """The constructor for objects of type Date
        """
        self.day = day
        self.month = month
        self.year = year

    def __repr__(self):
        return f"{self.day:02d}/{self.month:02d}/{self.year:04d}"

    def is_leap_year(self):
        if self.year % 400 == 0: return True
        if self.year % 100 == 0: return False
        if self.year % 4 == 0: return True
        return False

    def equals(self, other):
        """Returns True if they represent
           the same date; False otherwise
        """
        if (
            self.year == other.year
            and self.month == other.month
            and self.day == other.day
        ):
            return True
        else:
            return False

    def __eq__(self, other):
        return self.equals(other)

Operator overloading#

Python duidelijk maken wat een type bedoelt!

  • __eq__(self, other) definieert de gelijkheid operatator ==

  • __ne__(self, other definieert de ongelijkheid operatator !=

  • __lt__(self, other) definieert de kleiner dan operatator <

  • __gt__(self, other) definieert de groter dan operatator >

  • __le__(self, other) definieert de kleiner of gelijk aan operatator <=

  • __ge__(self, other) definieert de groter of gelijk aan operatator >=

  • __add__(self, other) definieert de optelling operatator +

  • __sub__(self, other) definieert de aftrekking operatator -

Morgen en gisteren#

class Date:
    ...
    
    def tomorrow(self):
        """Moves the self date ahead 1 day
        """
        DIM = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
        
        self.day += 1  # first, add 1 to self.day
        
        if ...:        # test if we have gone "out of bounds"!
            self.month ...
            self.day ...
            
            if ...:  # then adjust the month and year, but only as needed!
                self.year ...
                self. month ...

Februari is variabel#

class Date:
    ...
    
    def tomorrow(self):
        """Moves the self date ahead 1 day
        """
        if self.is_leap_year():
            fdays = 29
        else:
            fdays = 28
        
        DIM = [0, 31, fdays, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
        
        self.day += 1  # add 1 to the day
        
        if self.day > DIM[self.month]:  # check day
            self.month += 1
            self.day = 1
            
            if self.month > 12:  # check month
                self.year += 1
                self.month = 1