Mostenirea reprezinta un concept fundamental in POO, care permite programatorului sa construiasca clase pe baza altor clase, deja existente. Prin mostenire, o clasa capata acces la membrii unei alte clase.
Clasa de la care se mostenesc membrii se numeste superclasa sau clasa parinte sau ancestor. Clasa care mosteneste membrii se numeste subclasa, sau clasa copil.

Asadar, prin mostenire:

- membrii superclasei sunt accesibili (vizibili) in corpul subclasei
- membrii subclasei nu sunt accesibili (sunt invizibili) in corpul superclasei

In POO, se spune ca o subclasa este derivata dintr-o superclasa.

In limbajul Python, oricare clasa definita de catre programator, sau care este deja definita in limbajul Python, este implicit derivata din clasa parinte `object`. Acest lucru implica faptul ca oricare clasa definita in limbajul Python mosteneste membrii clasei `object`.

In [1]:

    # membrii clasei object
    dir(object)

Out[1]:

    ['__class__',
     '__delattr__',
     '__dir__',
     '__doc__',
     '__eq__',
     '__format__',
     '__ge__',
     '__getattribute__',
     '__gt__',
     '__hash__',
     '__init__',
     '__init_subclass__',
     '__le__',
     '__lt__',
     '__ne__',
     '__new__',
     '__reduce__',
     '__reduce_ex__',
     '__repr__',
     '__setattr__',
     '__sizeof__',
     '__str__',
     '__subclasshook__']

# 1. Definitia unei subclase¶

Sintaxa care trebuie utilizata la definitia unei subclase este:

    class NumeSubclasa(NumeSuperclasa):
        corp_subclasa

In [2]:

    # Exemplul 1 - definitia unei superclase si a unei subclase a acesteia

    # superclasa

    class GeometricObjects:
        pass

    # subclasa

    class Circle(GeometricObjects):
        pass

Limbajul Python pune la dispozitia programatorului 2 functii implicite, care returneaza sub forma unei valori booleene `True/False` relatia in care se afla 2 clase, repectiv clasa unui obiect:

- issubclass()
- isinstance()

Sintaxa acestor functii este urmatoarea:

    issubclass(clasaB, clasaA)  # returneaza True daca clasaB
                                # este o subclasa a clasei clasaA
                                # si False in caz contrar

    isinstance(obiect, clasa)   # returneaza True daca obiect
                                # este de tipul clasei "clasa"
                                # si False in caz contrar

Observatie: Un obiect de tipul subclasei este implicit si de tipul superclasei. Reciproca insa nu este valabila: un obiect de tipul superclasei nu este si de tipul subclasei.

In [3]:

    # Exemplul 2 - testarea tipului obiectelor si a relatiei dintre clase

    # superclasa

    class GeometricForms:
        pass

    # subclasa

    class Circle(GeometricForms):
        pass

    # creare obiecte

    form = GeometricForms()
    circ = Circle()

    # test relatii intre clase

    Circle_vs_GeometricForms = issubclass(Circle, GeometricForms)
    GeometricForms_vs_object = issubclass(GeometricForms, object)
    Circle_vs_object = issubclass(Circle, object)

    print(f'Circle is a subclass of GeometricForms:        {Circle_vs_GeometricForms}')
    print(f'GeometricForms is a subclass of "object":      {GeometricForms_vs_object}')
    print(f'Circle is a subclass of "object":              {Circle_vs_object}')

    # test tip obiect

    circ_is_GeometricForms = isinstance(circ, GeometricForms)
    form_is_Circle = isinstance(form, Circle)
    form_is_object = isinstance(form, object)
    circ_is_object = isinstance(circ, object)

    print()

    print(f'circ has type of GeometricForms:   {circ_is_GeometricForms}')
    print(f'form has type of Circle:           {form_is_Circle}')
    print(f'form has type of "object":         {form_is_object}')
    print(f'circ has type of "object":         {circ_is_object}')

    Circle is a subclass of GeometricForms:        True
    GeometricForms is a subclass of "object":      True
    Circle is a subclass of "object":              True

    circ has type of GeometricForms:   True
    form has type of Circle:           False
    form has type of "object":         True
    circ has type of "object":         True

# 2. Initializarea atributelor superclasei¶

Daca o superclasa are definite atribute de obiect, atunci, la crearea unui obiect de tipul subclasei, pentru initializarea atributelor de obiect mostenite de la superclasa, este necesar ca in corpul subclasei sa se implementeze un mecanism care asigura initializarea acestora. In caz contrar, poate fi creat un obiect de tipul subclasei, a carui stare este "incomplet" definita, la momentul crearii sale (obiect incomplet).

Solutia de implementare a mecanismului de initializare a tuturor atributelor de obiect mostenite, consta in initializarea valorii acestora in corpul metodei de initializare `__init__()` a subclasei. Initializarea tuturor atributelor de obiect mostenite se poate realiza prin apelarea in corpul metodei de initializare `__init__()` a subclasei, a metodei de initializare `__init__()` a superclasei si furnizarea ca argumente pentru aceasta din urma a valorilor de initializare ale atributelor de obiect mostenite.

Apelarea metodei de initializare a superclasei in corpul metodei de initializare a subclasei se realizeaza prin utlizarea functiei Python `super()`. La executie, functia `super()` returneaza un obiect care reprezinta superclasa subclasei in care este apelata functia `super()`. Functia `super()` permite accesarea oricarei metode mostenite, inclusiv a metodei de initializare a superclasei.

Sintaxa utilizata pentru accesarea unei metode mostenite:

    super().metoda_mostenita(lista_argumente)

Sintaxa utilizata pentru apelarea metodei de initializare a superclasei, in corpul metodei de initializare a subclasei este:

    def __init__(self, lista_argumente_atribute_superclasa, lista_argumente_atribute_subclasa):
        super().__init__(lista_argumente_atribute_superclasa)
        # initializare argumente atribute subclasa

In exemplul de sintaxa utilizat mai sus, argumentele utilizate pentru initializarea atributelor mostenite (ale superclasei), sunt furnizate metodei de initializare a superclasei, disponibila prin executia functiei `super()`, iar argumentele utilizate pentru initializarea atributelor subclasei sunt utilizate in continuare, in corpul metodei de initializare a acesteia, pentru initializarea atributelor subclasei.

Observatie: Functia `super()` poate fi apelata doar in corpul unei functii.

In [4]:

    # Exemplul 2 - initializarea atributelor

    # implementare superclasa

    class GeometricForms:

        no_of_forms = 0                       # atribut public de clasa

        def __init__(self, a_color = ''):
            self.color = a_color              # atribut public de obiect
            GeometricForms.no_of_forms += 1

    # implementare subclasa

    class Circle(GeometricForms):

        def __init__(self, a_color = 'white', a_radius = 1.0):
            super().__init__(a_color)         # initializare atribute de obiect mostenite
            self.radius = a_radius            # initializare atribute de obiect proprii subclasei

    # creare obiect subclasa

    form = GeometricForms()
    circ = Circle()

    # afisare membri obiecte

    dir(circ)

    # for member in dir(form):
    #     if member[0] == '_':
    #         continue
    #     else:
    #         print(member)

Out[4]:

    ['__class__',
     '__delattr__',
     '__dict__',
     '__dir__',
     '__doc__',
     '__eq__',
     '__format__',
     '__ge__',
     '__getattribute__',
     '__gt__',
     '__hash__',
     '__init__',
     '__init_subclass__',
     '__le__',
     '__lt__',
     '__module__',
     '__ne__',
     '__new__',
     '__reduce__',
     '__reduce_ex__',
     '__repr__',
     '__setattr__',
     '__sizeof__',
     '__str__',
     '__subclasshook__',
     '__weakref__',
     'color',
     'no_of_forms',
     'radius']

# 3. Accesarea membrilor mosteniti¶

Metoda de accesare a membrilor mosteniti de catre o subclasa depinde de tipul acestora: membri publici vs. membri privati. Deasemenea, depinde de locatia in care sunt accesati: in corpul subclasei, respectiv din exteriorul acesteia.

## 3.1. Accesarea membrilor mosteniti in corpul subclasei¶

### Accesarea atributelor de clasa¶

Indiferent daca sunt publice sau private, accesarea atributelor de clasa mostenite de la superclasa, se realizeaza pe baza operatorului punct `.`, aplicat numelui superclasei:

    NumeSuperclasa.atribut_mostenit    # atribut_mostenit = atribut de clasa

In cazul atributelor de clasa private mostenite de la superclasa, la referirea acestora in corpul subclasei, este necesara prefixarea acestora cu `_NumeSuperclasa` (1 caracter undescore urmat de numele superclasei):

    NumeSuperclasa._NumeSuperclasa__atribut    # atribut = atribut de clasa privat, mostenit

Exemplu:

    class A:
        __a = 100        # atribut privat de clasa

    class B(A):
        __b = A._A__a    # b ia valoarea atributului a, mostenit de la clasa A

In cazul in care, la accesarea atributului privat mostenit, numele sau nu este prefixat de catre programator cu `_NumeSuperclasa`, acesta este prefixat automat cu numele `_NumeSubclasa`, devenind la acces

    _NumeSubclasa__atribut

In cazul in care se doreste accesarea atributului mostenit, fara a fi prefixat cu numele superclasei, din cauza prefixarii automate cu numele subclasei, se acceseaza de fapt un atribut diferit de cel declarat in superclasa.

Exemplu:

    class A:
        __a = 100

    class B(A):
        __b = A.__a    # A._a nu reprezinta atributul de clasa mostenit din clasa A
                       # in realitate, prin prefixarea automata cu numele subclasei (_B__a)
                       # este accesat un atribut denumit tot "a", local subclasei

Observatie: daca accesarea atributului de clasa, mostenit de la superclasa, se realizeaza in interiorul corpului unei metode de clasa a subclasei, atunci, pentru accesarea atributului respectiv, se poate utiliza si parametrul `cls`:

    cls.atribut_mostenit   # atribut_mostenit = atribut de clasa;
                           # se aplica numai in interiorul metodelor de clasa ale subclasei

Observatie: daca accesarea atributului de clasa, mostenit de la superclasa, se realizeaza in interiorul corpului unei metode comune a subclasei, atunci, pentru accesarea atributului respectiv, se poate utiliza si referinta `self` la obiectul creat, deoarece aceasta este disponibila in corpul metodei (in exteriorul corpului metodei, referinta `self` nu exista, deci nu poate fi utilizata).

    self.atribut_mostenit   # atribut_mostenit = atribut de clasa;
                            # se aplica numai in interiorul metodelor comune ale subclasei

Se recomanda insa, pentru evitarea confuziilor, utilizarea referintei catre clasa `cls` si in aceste cazuri.

In [5]:

    # Exemplul 4 - accesarea atributelor publice de clasa mostenite, in corpul subclasei

    # implementare superclasa

    class GeometricForms:

        no_of_forms = 0     # atribut public de clasa

        def __init__(self):
            GeometricForms.no_of_forms += 1

    # implementare subclasa

    class Circle(GeometricForms):

    # atribut public de clasa local subclasei,
    # initializat prin accesarea atributului public clasa mostenit

        no_of_circles = GeometricForms.no_of_forms  + 10

        def __init__(self):
            super().__init__()
            Circle.no_of_circles += 1

        @classmethod
        def get_info(cls):
            forms   = cls.no_of_forms      # acces atribut public de clasa mostenit
            circles = cls.no_of_circles
            return (forms, circles)

    #  creare  obiect

    circ_1 = Circle()
    circ_2 = Circle()

    print(Circle.get_info())

    dir(circ_1)

    (2, 12)

Out[5]:

    ['__class__',
     '__delattr__',
     '__dict__',
     '__dir__',
     '__doc__',
     '__eq__',
     '__format__',
     '__ge__',
     '__getattribute__',
     '__gt__',
     '__hash__',
     '__init__',
     '__init_subclass__',
     '__le__',
     '__lt__',
     '__module__',
     '__ne__',
     '__new__',
     '__reduce__',
     '__reduce_ex__',
     '__repr__',
     '__setattr__',
     '__sizeof__',
     '__str__',
     '__subclasshook__',
     '__weakref__',
     'get_info',
     'no_of_circles',
     'no_of_forms']

In [6]:

    # Exemplul 5 - accesarea atributelor private de clasa mostenite, in corpul subclasei

    # implementare superclasa

    class GeometricForms:
        __no_of_forms = 0     # atribut privat de clasa

        def __init__(self):
            GeometricForms.__no_of_forms += 1

    # implementare subclasa

    class Circle(GeometricForms):
        no_of_circles = GeometricForms._GeometricForms__no_of_forms

        def __init__(self):
            super().__init__()
            Circle.no_of_circles += 1

        @classmethod
        def get_info(cls):
            forms   = cls._GeometricForms__no_of_forms
            circles = cls.no_of_circles
            return (forms, circles)

    #  creare  obiect

    circ_1 = Circle()
    circ_2 = Circle()

    print(Circle.get_info())
    dir(circ_1)

    (2, 2)

Out[6]:

    ['_GeometricForms__no_of_forms',
     '__class__',
     '__delattr__',
     '__dict__',
     '__dir__',
     '__doc__',
     '__eq__',
     '__format__',
     '__ge__',
     '__getattribute__',
     '__gt__',
     '__hash__',
     '__init__',
     '__init_subclass__',
     '__le__',
     '__lt__',
     '__module__',
     '__ne__',
     '__new__',
     '__reduce__',
     '__reduce_ex__',
     '__repr__',
     '__setattr__',
     '__sizeof__',
     '__str__',
     '__subclasshook__',
     '__weakref__',
     'get_info',
     'no_of_circles']

### Accesarea atributelor de obiect¶

Indiferent daca sunt publice sau private, accesarea atributelor de obiect, mostenite de la superclasa, se realizeaza pe baza operatorului punct `.`, aplicat referintei `self` catre obiectul creat. Pentru a putea accesa un astfel de atribut, este necesar ca mai intai, referinta `self` catre obiectul creat sa existe. Din acest motiv, accesarea atributelor de obiect mostenite de la superclasa, se poate realiza numai in corpul metodelor comune ale subclasei, unde referinta `self` la obiectul creat este disponibila.

Sintaxa:

    self.atribut_mostenit  # atribut_mostenit = atribut de obiect;
                           # se aplica numai in corpul metodelor comune ale sublcasei

In cazul atributelor de obiect private mostenite de la superclasa, la referirea acestora in corpul subclasei, este necesara prefixarea acestora cu `_NumeSuperclasa`, asa cum s-a explicat si in cazul atributelor de clasa:

    self._NumeSuperclasa_atribut    # atribut = atribut de obiect privat, mostenit

In [7]:

    # Exemplul 6 - accesarea atributelor publice de obiect mostenite, in corpul subclasei

    # implementare clasa

    class GeometricForms:

        def __init__(self, a_color):
            self.color = a_color

    # implementare subclasa

    class Circle(GeometricForms):

        def __init__(self, a_color = 'WHITE', a_radius = '1.0'):
            super().__init__(a_color)
            self.radius = a_radius       # atribut public de obiect

        def set_params(self, new_color, new_radius):
            self.color = new_color      # acces atribut public de obiect mostenit
            self.radius = new_radius

        def get_params(self):
            return (self.color, self.radius)

    #  creare  obiect

    circ = Circle()

    print(circ.get_params())
    circ.set_params('RED', 2.3)
    print(circ.get_params())

    dir(circ)

    ('WHITE', '1.0')
    ('RED', 2.3)

Out[7]:

    ['__class__',
     '__delattr__',
     '__dict__',
     '__dir__',
     '__doc__',
     '__eq__',
     '__format__',
     '__ge__',
     '__getattribute__',
     '__gt__',
     '__hash__',
     '__init__',
     '__init_subclass__',
     '__le__',
     '__lt__',
     '__module__',
     '__ne__',
     '__new__',
     '__reduce__',
     '__reduce_ex__',
     '__repr__',
     '__setattr__',
     '__sizeof__',
     '__str__',
     '__subclasshook__',
     '__weakref__',
     'color',
     'get_params',
     'radius',
     'set_params']

In [8]:

    # Exemplul 7 - accesarea atributelor private de obiect mostenite, in corpul subclasei

    # implementare clasa

    class GeometricForms:

        def __init__(self, a_color):
            self.__color = a_color       # atribut privat de obiect

    # implementare subclasa

    class Circle(GeometricForms):

        def __init__(self, a_color = 'WHITE', a_radius = '1.0'):
            super().__init__(a_color)
            self.radius = a_radius       # atribut public de obiect

        def set_params(self, new_color, new_radius):
            self._GeometricForms__color = new_color      # acces atribut privat de obiect mostenit
            self.radius = new_radius

        def get_params(self):
            return (self._GeometricForms__color, self.radius)

    #  creare  obiect

    circ = Circle()

    print(circ.get_params())
    circ.set_params('RED', 2.3)
    print(circ.get_params())

    dir(circ)

    ('WHITE', '1.0')
    ('RED', 2.3)

Out[8]:

    ['_GeometricForms__color',
     '__class__',
     '__delattr__',
     '__dict__',
     '__dir__',
     '__doc__',
     '__eq__',
     '__format__',
     '__ge__',
     '__getattribute__',
     '__gt__',
     '__hash__',
     '__init__',
     '__init_subclass__',
     '__le__',
     '__lt__',
     '__module__',
     '__ne__',
     '__new__',
     '__reduce__',
     '__reduce_ex__',
     '__repr__',
     '__setattr__',
     '__sizeof__',
     '__str__',
     '__subclasshook__',
     '__weakref__',
     'get_params',
     'radius',
     'set_params']

### Accesarea metodelor¶

Accesarea tuturor metodelor mostenite de la superclasa, se realizeaza pe baza operatorului punct `.`, aplicat referintei catre superclasa, care este reprezentata de catre numele superclasei

Sintaxa:

    NumeSuperclasa.metoda_mostenita   # metoda_mostenita

In corpul metodelor comune si de clasa, accesarea metodelor publice (comune, de clasa si statice), mostenite de la superclasa, se poate realiza deasemenea, pe baza operatorului punct `.` aplicat referintei returnate de catre functia `super()`.

Sintaxa:

    super().metoda_mostenita   # metoda_mostenita
                               # se aplica numai in corpul metodelor comune si de clasa ale subclasei

Observatie: aparent, deoarece metoda mostenita apartine oricarui obiect creat de tipul subclasei, aceasta poate fi accesata si cu sintaxa

    self.metoda

Atat timp cat, in corpul subclasei nu se defineste o metoda cu acelasi nume ca cel al metodei mostenite, utilizarea sintaxei de mai sus este corecta. Daca insa, in corpul subclasei se defineste o metoda diferita dar cu acelasi nume ca cel al metodei mostenite, sintaxa de mai sus apeleaza metoda subclasei, nu metoda mostenita. Acest aspect se va detalia in paragraful 4, care prezinta metoda "suprascrierii" metodelor mostenite.

In cazul metodelor private mostenite de la superclasa, la referirea acestora in corpul subclasei, este necesara prefixarea acestora cu `_NumeSuperclasa`, asa cum s-a explicat si in cazul atributelor private mostenite:

    self._NumeSuperclasa_metoda    # metoda = metoda privata, mostenita

In [9]:

    # Exemplul 6 - accesarea metodelor mostenite, in corpul subclasei

    # implementare superclasa

    class GeometricForms:

        __no_of_forms = 0

        def __init__(self, a_color = ''):
            self.__color = a_color
            GeometricForms.__no_of_forms += 1

        @classmethod
        def get_no_of_forms(cls):
            return cls.__no_of_forms

        def get_color(self):
            return self.__color

        def set_color(self, new_color):
            self.__color = new_color

        @staticmethod
        def drawing():
            print("Drawing forms", end = ' - ')

    # implementare subclasa

    class Circle(GeometricForms):

        def __init__(self, a_color = 'WHITE', a_radius = 1.0):
            super().__init__(a_color)
            self.__radius = a_radius

        @classmethod
        def get_no_of_circles(cls):
            return super().get_no_of_forms()

        def get_params(self):
            return(super().get_color(), self.__radius)

        def set_params(self, new_color, new_radius):
            super().set_color(new_color)
            self.__radius = new_radius

        @staticmethod
        def drawing_circle():
            GeometricForms.drawing()
            print('Circles.')

    circ = Circle()
    print(Circle.get_no_of_circles())
    print(circ.get_params())

    circ.set_params('RED', 1.5)
    print(circ.get_params())

    Circle.drawing_circle()

    1
    ('WHITE', 1.0)
    ('RED', 1.5)
    Drawing forms - Circles.

## 3.2. Accesarea membrilor mosteniti, din exteriorul subclasei¶

### Accesarea membrilor publici mosteniti, din exteriorul subclasei¶

Sintaxa utilizata pentru accesarea membrilor publici, mosteniti de catre clasa unui obiect in codul in care obiectul respectiv este creat, depinde de tipul membrului respectiv. In cazul in care membrul este de clasa se aplica operatorul punct `.` denumirii subclasei:

    NumeSubclasa.membru_mostenit

In cazul in care membrul este de obiect se aplica operatorul punct `.` referintei la obiectul respectiv:

    referinta_obiect.membru_mostenit

In [10]:

    # Exemplul 8 - accesarea membrilor publici mosteniti, din exteriorul corpului subclasei

    # implementare superclasa

    class GeometricForms:
        no_of_forms = 0
        def __init__(self, a_color = ''):
            self.color = a_color
            GeometricForms.no_of_forms += 1

    # implementare subclasa

    class Circle(GeometricForms):

        def __init__(self, a_color = 'WHITE', a_radius = 1.0):
            super().__init__(a_color)
            self.radius = a_radius

    # creare obiecte

    circ1 = Circle()
    circ2 = Circle()

    # acces atribut public de clasa mostenit

    n = Circle.no_of_forms

    # acces atribut public de obiect mostenit

    circ1_col = circ1.color
    circ2_col = circ2.color

    # acces atribut public de obiect

    circ1_rad = circ1.radius
    circ2_rad = circ2.radius

    # afisare rezultate

    print(f'There are {n} circles.')
    print(f'circ1 has {circ1_col} color and {circ1_rad} radius.')
    print(f'circ2 has {circ2_col} color and {circ2_rad} radius.')

    # acces atribut public de obiect mostenit

    circ1.color = 'RED '
    circ2.color = 'BLUE'

    # acces atribut public de obiect

    circ1.radius = 0.5
    circ2.radius = 1.5

    # actualizare atribute

    circ1_col = circ1.color
    circ2_col = circ2.color
    circ1_rad = circ1.radius
    circ2_rad = circ2.radius

    # afisare rezultate

    print()
    print(f'circ1 has {circ1_col} color and {circ1_rad} radius.')
    print(f'circ2 has {circ2_col} color and {circ2_rad} radius.')

    There are 2 circles.
    circ1 has WHITE color and 1.0 radius.
    circ2 has WHITE color and 1.0 radius.

    circ1 has RED  color and 0.5 radius.
    circ2 has BLUE color and 1.5 radius.

### Accesarea membrilor privati mosteniti, din exteriorul subclasei¶

In cazul metodelor private, acestea nu pot fi accesate decat in interiorul corpului subclasei. In cazul atributelor, acestea nu pot fi accesate in exteriorul corpului claselor/ subclaselor direct, prin utilizarea operatorului punct `.`. Pentru asigurarea accesului la atributele private, se definesc in corpul superclasei metode publice de acces sau decoratori de acces la atributele private, iar accesul la acestea se realizeaza prin intermediul acestor metode/decoratori de acces.

In [11]:

    # Exemplul 9 - accesarea membrilor privati mosteniti, din exteriorul corpului subclasei

    # implementare superclasa

    class GeometricForms:
        __no_of_forms = 0
        def __init__(self, a_color = ''):
            self.__color = a_color
            GeometricForms.__no_of_forms += 1

        @classmethod
        def get_no_of_forms(cls):
            return cls.__no_of_forms

        @property
        def color(self):
            return self.__color

        @color.setter
        def color(self, new_color):
            color_list = ('RED', 'GREEN', 'BLUE')
            if new_color in color_list:
                self.__color = new_color
            else:
                self.__color = ''
                print('error color')

    # implementare subclasa

    class Circle(GeometricForms):

        def __init__(self, a_color = 'RED', a_radius = 1.0):
            super().__init__(a_color)
            self.__radius = a_radius

        @property
        def radius(self):
            return self.__radius

        @radius.setter
        def radius(self, new_radius):
            if type(new_radius) == float and new_radius > 0.0:
                self.__radius = new_radius
            else:
                self.__radius = 1.0

    # creare obiecte

    circ1 = Circle()
    circ2 = Circle()

    # acces atribut privat de clasa mostenit

    n = Circle.get_no_of_forms()

    # acces atribut privat de obiect mostenit

    circ1_col = circ1.color
    circ2_col = circ2.color

    # acces atribut public de obiect

    circ1_rad = circ1.radius
    circ2_rad = circ2.radius

    # afisare rezultate

    print(f'There are {n} circles.')
    print(f'circ1 has {circ1_col} color and {circ1_rad} radius.')
    print(f'circ2 has {circ2_col} color and {circ2_rad} radius.')

    # acces atribut public de obiect mostenit

    circ1.color = 'GREEN'
    circ2.color = 'BLUE'

    # acces atribut public de obiect

    circ1.radius = 0.5
    circ2.radius = 1.5

    # actualizare atribute

    circ1_col = circ1.color
    circ2_col = circ2.color
    circ1_rad = circ1.radius
    circ2_rad = circ2.radius

    # afisare rezultate

    print()
    print(f'circ1 has {circ1_col} color and {circ1_rad} radius.')
    print(f'circ2 has {circ2_col} color and {circ2_rad} radius.')

    There are 2 circles.
    circ1 has RED color and 1.0 radius.
    circ2 has RED color and 1.0 radius.

    circ1 has GREEN color and 0.5 radius.
    circ2 has BLUE color and 1.5 radius.

# 4. Proiectarea unui arbore de clase¶

|  |  | Animal |  |  |
|  | / |  | \ |  |
| Pet |  |  |  | Wild |
/ |  | \ |  | / |  | \
Cat |  | Dog |  | Lion |  | Wolf

### 4.1 Strategia de proiectare a claselor - sublcaselor¶

- in clase se introduc doar membrii comuni tuturor subclaselor:
  - atribute comune tuturor subclaselor
  - metode care modeleaza un comportament comun tuturor subclaselor

- in subclase se introduc membrii specifici subclasei (care individiualizeaza subclasa):
  - atribute specifice subclasei
  - metode care modeleaza comportamentul care individualizeaza subclasa

Animal
---------------------------------------------------------------------------------------------------------------
__name : object private attribute; str type; stores the name of the Animal obj
__health : object private attribute; int type [0,10]; stores the health level of the Animal obj
__coord : object private attribute; tuple of int type; stores the coordinate (x,y) = [0,100] x [0,100]of the Animal obj
---------------------------------------------------------------------------------------------------------------
eat() : prints out a message about the behaviour of the animal
sleep() : prints out a message about the behaviour of the animal
run() : returns the new coordinates (x,y) of the animal depending on the direction
---------------------------------------------------------------------------------------------------------------

Pet
---------------------------------------------------------------------------------------------------------------
play() : prints out a message about the behaviour of the Pet object
---------------------------------------------------------------------------------------------------------------

Wild
---------------------------------------------------------------------------------------------------------------
hunt() : prints out a message about the behaviour of the Wild object
---------------------------------------------------------------------------------------------------------------

Cat
---------------------------------------------------------------------------------------------------------------
__no_of_cats : class private attribute; int type; stores the number of the Cat objects
__id : object private attribute; int type; stores the id of the Dog object
---------------------------------------------------------------------------------------------------------------
get_cats() : returns the number of Cat objects
meow() : prints out a message about the behaviour of the cat
---------------------------------------------------------------------------------------------------------------

Dog
---------------------------------------------------------------------------------------------------------------
__no_of_dogs : class private attribute; int type; stores the number of the Dog objects
__id : object private attribute; int type; stores the id of the Dog object
---------------------------------------------------------------------------------------------------------------
get_dogs() : returns the number of Dog objects
bark() : prints out a message about the behaviour of the dog
---------------------------------------------------------------------------------------------------------------

Lion
---------------------------------------------------------------------------------------------------------------
__no_of_lions : class private attribute; int type; stores the number of the Lion objects
__id : object private attribute; int type; stores the id of the Lion object
---------------------------------------------------------------------------------------------------------------
get_lions() : returns the number of Lion objects
roar() : prints out a message about the behaviour of the lion
---------------------------------------------------------------------------------------------------------------

Wolf
---------------------------------------------------------------------------------------------------------------
__no_of_wolfs : class private attribute; int type; stores the number of the Wolf objects
__id : object private attribute; int type; stores the id of the Wolf object
---------------------------------------------------------------------------------------------------------------
get_wolfs() : returns the number of Wolf objects
howl() : prints out a message about the behaviour of the wolf
---------------------------------------------------------------------------------------------------------------

In [12]:

    # Animal class implementation:

    class Animal:
        '''
        Animal class:
        -----------------------------------------------------------------------------------------------------------
        __name:               str type;   name of the animal;                                 private object attribute
        __health:             int type;   = [0,10]                                            private object attribute
                              health level of the animal;
        __coord:              tuple type; (x,y) coordinates of the animal;                    private object attribute
                              x = [0,100]
                              y = [0,100]
        -----------------------------------------------------------------------------------------------------------
        eat():                prints out a message about the behaviour of the animal          public regular method
        sleep():              prints out a message about the behaviour of the animal          public regular method
        run()                 returns the new coordinates (x,y)                               public regular method
                              of the animal, depending on the direction and speed params
        -----------------------------------------------------------------------------------------------------------
        '''

    # initializer:

        def __init__(self, n = '*', h = 10, c = (50,50)):
            self.__name   = n
            self.__health = h
            self.__coord  = c

    # getters for private attributes:

        @property
        def name(self):
            return self.__name

        @property
        def health(self):
            return self.__health

        @property
        def coord(self):
            return self.__coord

    # setters for private attributes:

        @name.setter
        def name(self, new_name):
            self.__name = new_name

        @health.setter
        def health(self, new_health):
            self.__health = new_health

        @coord.setter
        def coord(self, new_coord):
            self.__coord = new_coord

    # regular methods:

        def eat(self):
            '''
            implements the behaviour of the Animal during running action.
            '''
            print(f"I'm {self.__name} and now I'm eating!")

        def sleep(self):
            '''
            implements the behaviour of the Animal during taking a nap action.
            '''
            print(f"I'm {self.__name} and now I'm sleeping!")

        def run(self, direction, speed):
            '''
            implements the behaviour of the Animal during running action.
            - direction:
               - 'f' = running forward
               - 'b' = running backward
               - 'r' = running to right
               - 'l' = running to left
            - speed: integer [0,5]
            '''

            # define a speed_control variable:
            speed_ctrl = speed if 0 < speed <= 5 else 0

            # define a direction control variable:
            dir_ctrl = 's'
            if {'f', 'b', 'r', 'l'}.intersection(set(direction.lower())) != set({}):
                dir_ctrl = direction.lower()

            # define local functions to control the moving actions
            def to_forward(c, i):
                '''
                local function: implements the forward moving action
                increments y coordinate with i step or limits to max. value
                '''
                x, y = c[0], c[1]
                y = y + i if 0 <= y < 100 else 100
                return (x,y)

            def to_backward(c, i):
                '''
                local function: implements the backward moving action
                decrements y coordinate with i step or limits to min. value
                '''
                x, y = c[0], c[1]
                y = y - i if 0 <= y < 100 else 0
                return (x,y)

            def to_right(c, i):
                '''
                local function: implements to right moving action
                increments x coordinate with i step or limits to max. value
                '''
                x, y = c[0], c[1]
                x = x + i if 0 <= x < 100 else 100
                return (x,y)

            def to_left(c, i):
                '''
                local function: implements to left moving action
                decrements y coordinate with i step or limits to min. value
                '''
                x, y = c[0], c[1]
                x = x - i if 0 <= x < 100 else 0
                return (x,y)

            def stop(c):
                '''
                local function: returns the current coordinates
                '''
                return c

            # define path actions as lists:
            forward_path   = ['running forward',   to_forward(self.__coord, speed_ctrl)]
            backward_path  = ['running backward',  to_backward(self.__coord, speed_ctrl)]
            right_path     = ['running to right',  to_right(self.__coord, speed_ctrl)]
            left_path      = ['running to left',   to_left(self.__coord, speed_ctrl)]
            stop_path      = ['not running',       stop(self.__coord)]

            # define a controller to set the moving actions as a dictionary
            controller = {'f': forward_path,
                          'b': backward_path,
                          'r': right_path,
                          'l': left_path,
                          's': stop_path}

            # implement the running behaviour:
            print(f"I'm {self.__name} and now I'm { controller[dir_ctrl][0] }!")
            self.__coord = controller[dir_ctrl][1]

In [13]:

    # class members:

    dir(Animal)

Out[13]:

    ['__class__',
     '__delattr__',
     '__dict__',
     '__dir__',
     '__doc__',
     '__eq__',
     '__format__',
     '__ge__',
     '__getattribute__',
     '__gt__',
     '__hash__',
     '__init__',
     '__init_subclass__',
     '__le__',
     '__lt__',
     '__module__',
     '__ne__',
     '__new__',
     '__reduce__',
     '__reduce_ex__',
     '__repr__',
     '__setattr__',
     '__sizeof__',
     '__str__',
     '__subclasshook__',
     '__weakref__',
     'coord',
     'eat',
     'health',
     'name',
     'run',
     'sleep']

In [14]:

    # Animal class test:

    # create an Animal object

    animal_ob = Animal('Ali', 5)

    # printing out the object's attributes:

    print(f'name: {animal_ob.name}, health: {animal_ob.health}, location: {animal_ob.coord}')

    # testing the object's behaviour:

    animal_ob.eat()
    animal_ob.sleep()

    for i in range(5):
        animal_ob.run('f', 5)
        print(f'location:{animal_ob.coord}')

    for i in range(5):
        animal_ob.run('b', 1)
        print(f'location:{animal_ob.coord}')

    for i in range(5):
        animal_ob.run('r', 2)
        print(f'location:{animal_ob.coord}')

    for i in range(5):
        animal_ob.run('l', 3)
        print(f'location:{animal_ob.coord}')

    animal_ob.run('s', 3)
    print(f'location:{animal_ob.coord}')

    name: Ali, health: 5, location: (50, 50)
    I'm Ali and now I'm eating!
    I'm Ali and now I'm sleeping!
    I'm Ali and now I'm running forward!
    location:(50, 55)
    I'm Ali and now I'm running forward!
    location:(50, 60)
    I'm Ali and now I'm running forward!
    location:(50, 65)
    I'm Ali and now I'm running forward!
    location:(50, 70)
    I'm Ali and now I'm running forward!
    location:(50, 75)
    I'm Ali and now I'm running backward!
    location:(50, 74)
    I'm Ali and now I'm running backward!
    location:(50, 73)
    I'm Ali and now I'm running backward!
    location:(50, 72)
    I'm Ali and now I'm running backward!
    location:(50, 71)
    I'm Ali and now I'm running backward!
    location:(50, 70)
    I'm Ali and now I'm running to right!
    location:(52, 70)
    I'm Ali and now I'm running to right!
    location:(54, 70)
    I'm Ali and now I'm running to right!
    location:(56, 70)
    I'm Ali and now I'm running to right!
    location:(58, 70)
    I'm Ali and now I'm running to right!
    location:(60, 70)
    I'm Ali and now I'm running to left!
    location:(57, 70)
    I'm Ali and now I'm running to left!
    location:(54, 70)
    I'm Ali and now I'm running to left!
    location:(51, 70)
    I'm Ali and now I'm running to left!
    location:(48, 70)
    I'm Ali and now I'm running to left!
    location:(45, 70)
    I'm Ali and now I'm not running!
    location:(45, 70)

In [15]:

    # Pet class implementation:

    class Pet(Animal):
        '''
        Pet class:
        -----------------------------------------------------------------------------------------------------------
        -----------------------------------------------------------------------------------------------------------
        play():               prints out a message about the behaviour of the pet animal      public regular method
        -----------------------------------------------------------------------------------------------------------
        '''

    # initializer:

        def __init__(self, n = '*', h = 10, c = (50,50)):
            super().__init__(n, h, c)

    # regular methods:

        def play(self):
            '''
            prints out the behaviour of the pet
            '''
            pet_name = self.name
            print(f"I'm {pet_name} and now I'm playing!")

In [16]:

    # Pet class testing

    # creating a Pet object

    a_pet = Pet('Bili', 8)

    # display object's members:

    dir(a_pet)

Out[16]:

    ['_Animal__coord',
     '_Animal__health',
     '_Animal__name',
     '__class__',
     '__delattr__',
     '__dict__',
     '__dir__',
     '__doc__',
     '__eq__',
     '__format__',
     '__ge__',
     '__getattribute__',
     '__gt__',
     '__hash__',
     '__init__',
     '__init_subclass__',
     '__le__',
     '__lt__',
     '__module__',
     '__ne__',
     '__new__',
     '__reduce__',
     '__reduce_ex__',
     '__repr__',
     '__setattr__',
     '__sizeof__',
     '__str__',
     '__subclasshook__',
     '__weakref__',
     'coord',
     'eat',
     'health',
     'name',
     'play',
     'run',
     'sleep']

In [17]:

    # Pet class testing

    # printing out the object's attributes:

    print(f'name: {a_pet.name}, health: {a_pet.health}, location: {a_pet.coord}')

    # testing the object's behaviour:

    a_pet.eat()
    a_pet.sleep()
    a_pet.play()
    a_pet.run('f', 5)
    print(f'location:{a_pet.coord}')

    name: Bili, health: 8, location: (50, 50)
    I'm Bili and now I'm eating!
    I'm Bili and now I'm sleeping!
    I'm Bili and now I'm playing!
    I'm Bili and now I'm running forward!
    location:(50, 55)

In [18]:

    # Wild class implementation:

    class Wild(Animal):
        '''
        Wild class:
        -----------------------------------------------------------------------------------------------------------
        -----------------------------------------------------------------------------------------------------------
        hunt():               prints out a message about the behaviour of the Wild          public regular method
        -----------------------------------------------------------------------------------------------------------
        '''

    # initializer:

        def __init__(self, n = '*', h = 10, c = (50,50)):
            super().__init__(n, h, c)

    # regular methods:

        def hunt(self):
            '''
            prints out the behaviour of the wild animal
            '''
            wild_name = self.name
            print(f"I'm {wild_name} and now I'm hunting!")

In [19]:

    # Wild class testing

    # creating an Wild object

    a_wild = Wild('Cili')

    # display object's members:

    dir(a_wild)

Out[19]:

    ['_Animal__coord',
     '_Animal__health',
     '_Animal__name',
     '__class__',
     '__delattr__',
     '__dict__',
     '__dir__',
     '__doc__',
     '__eq__',
     '__format__',
     '__ge__',
     '__getattribute__',
     '__gt__',
     '__hash__',
     '__init__',
     '__init_subclass__',
     '__le__',
     '__lt__',
     '__module__',
     '__ne__',
     '__new__',
     '__reduce__',
     '__reduce_ex__',
     '__repr__',
     '__setattr__',
     '__sizeof__',
     '__str__',
     '__subclasshook__',
     '__weakref__',
     'coord',
     'eat',
     'health',
     'hunt',
     'name',
     'run',
     'sleep']

In [20]:

    # Wild class testing

    # printing out the object's attributes:

    print(f'name: {a_wild.name}, health: {a_wild.health}, location: {a_wild.coord}')

    # testing the object's behaviour:

    a_wild.eat()
    a_wild.sleep()
    a_wild.hunt()
    a_wild.run('f', 5)
    print(f'location:{a_wild.coord}')

    name: Cili, health: 10, location: (50, 50)
    I'm Cili and now I'm eating!
    I'm Cili and now I'm sleeping!
    I'm Cili and now I'm hunting!
    I'm Cili and now I'm running forward!
    location:(50, 55)

In [21]:

    # Cat class implementation:

    class Cat(Pet):
        '''
        Cat class:
        -----------------------------------------------------------------------------------------------------------
        __no_of_cats:       int type;                                                        private class attribute
                            stores the current number of Cat objects
        __id:               int type;                                                        private object attribute
                            stores the id number of the Cat objects
        -----------------------------------------------------------------------------------------------------------
        get_cats():         returns the current number of Cat objects                        public class method

        meow():             prints out a message about the specific behaviour of the cat     public regular method
        -----------------------------------------------------------------------------------------------------------
        '''

    # publi class attributes:

        __no_of_cats = 0

    # initializer:

        def __init__(self, n = '*', h = 10, c = (50,50)):
            super().__init__(n, h, c)
            Cat.__no_of_cats += 1
            self.__id = Cat.__no_of_cats

    # classmethods:

        @classmethod
        def get_cats(cls):
            return cls.__no_of_cats

    # regular methods:

        def meow(self):
            '''
            prints out the specific behaviour of the cat
            '''
            cat_name = self.name
            print(f"I'm {cat_name} and now I'm meowing!")

In [22]:

    # Cat class testing

    # creating Cat objects

    names = ['Moto', 'Sunny', 'Gimi']
    cats = []

    for i in range(len(names)):
        cats.append(Cat(names[i], 10, (50, 50+i)))

    # display an object's members:

    dir(cats[0])

Out[22]:

    ['_Animal__coord',
     '_Animal__health',
     '_Animal__name',
     '_Cat__id',
     '_Cat__no_of_cats',
     '__class__',
     '__delattr__',
     '__dict__',
     '__dir__',
     '__doc__',
     '__eq__',
     '__format__',
     '__ge__',
     '__getattribute__',
     '__gt__',
     '__hash__',
     '__init__',
     '__init_subclass__',
     '__le__',
     '__lt__',
     '__module__',
     '__ne__',
     '__new__',
     '__reduce__',
     '__reduce_ex__',
     '__repr__',
     '__setattr__',
     '__sizeof__',
     '__str__',
     '__subclasshook__',
     '__weakref__',
     'coord',
     'eat',
     'get_cats',
     'health',
     'meow',
     'name',
     'play',
     'run',
     'sleep']

In [23]:

    # Cat class testing

    # printing out the number of cats:

    print(f'The number of cats is {Cat.get_cats()}.', end = '\n'*2)

    The number of cats is 3.

In [24]:

    # Cat class testing

    # printing out the cat's attributes:

    for cat in cats:
        print(f'name: {cat.name}, health: {cat.health}, location: {cat.coord}')
        cat.eat()
        cat.sleep()
        cat.play()
        cat.meow()

    # testing the object's behaviour:
    cats[0].run('f', 5)
    print(f'location:{cats[0].coord}')

    name: Moto, health: 10, location: (50, 50)
    I'm Moto and now I'm eating!
    I'm Moto and now I'm sleeping!
    I'm Moto and now I'm playing!
    I'm Moto and now I'm meowing!
    name: Sunny, health: 10, location: (50, 51)
    I'm Sunny and now I'm eating!
    I'm Sunny and now I'm sleeping!
    I'm Sunny and now I'm playing!
    I'm Sunny and now I'm meowing!
    name: Gimi, health: 10, location: (50, 52)
    I'm Gimi and now I'm eating!
    I'm Gimi and now I'm sleeping!
    I'm Gimi and now I'm playing!
    I'm Gimi and now I'm meowing!
    I'm Moto and now I'm running forward!
    location:(50, 55)

In [25]:

    # Dog class implementation:

    class Dog(Pet):
        '''
        Dog class:
        -----------------------------------------------------------------------------------------------------------
        __no_of_dogs:       int type;                                                        private class attribute
                            stores the current number of Dog objects
        __id:               int type;                                                        private object attribute
                            stores the id number of the Dog objects
        -----------------------------------------------------------------------------------------------------------
        get_dogs():         returns the current number of Dog objects                        public class method

        bark():             prints out a message about the specific behaviour of the dog     public regular method
        -----------------------------------------------------------------------------------------------------------
        '''

    # public class attributes:

        __no_of_dogs = 0

    # initializer:

        def __init__(self, n = '*', h = 10, c = (50,50)):
            super().__init__(n, h, c)
            Dog.__no_of_dogs += 1
            self.__id = Dog.__no_of_dogs

    # classmethods:

        @classmethod
        def get_dogs(cls):
            return cls.__no_of_dogs

    # regular methods:

        def bark(self):
            '''
            prints out the specific behaviour of the dog
            '''
            dog_name = self.name
            print(f"I'm {dog_name} and now I'm barking!")

In [26]:

    # Lion class implementation:

    class Lion(Wild):
        '''
        Lion class:
        -----------------------------------------------------------------------------------------------------------
        __no_of_lions:      int type;                                                        private class attribute
                            stores the current number of Lion objects
        __id:               int type;                                                        private object attribute
                            stores the id number of the Lion objects
        -----------------------------------------------------------------------------------------------------------
        get_lions():        returns the current number of Lion objects                        public class method

        roar():             prints out a message about the specific behaviour of the lion     public regular method
        -----------------------------------------------------------------------------------------------------------
        '''

    # public class attributes:

        __no_of_lions = 0

    # initializer:

        def __init__(self, n = '*', h = 10, c = (50,50)):
            super().__init__(n, h, c)
            Lion.__no_of_lions += 1
            self.__id = Lion.__no_of_lions

    # classmethods:

        @classmethod
        def get_lions(cls):
            return cls.__no_of_lions

    # regular methods:

        def roar(self):
            '''
            prints out the specific behaviour of the lion
            '''
            lion_name = self.name
            print(f"I'm {lion_name} and now I'm roaring!")

In [27]:

    # Wolf class implementation:

    class Wolf(Wild):
        '''
        Wolf class:
        -----------------------------------------------------------------------------------------------------------
        __no_of_wolfs:      int type;                                                        private class attribute
                            stores the current number of Wolf objects
        __id:               int type;                                                        private object attribute
                            stores the id number of the Wolf objects
        -----------------------------------------------------------------------------------------------------------
        get_wolfs():        returns the current number of Wolfn objects                        public class method

        howl():             prints out a message about the specific behaviour of the wolf      public regular method
        -----------------------------------------------------------------------------------------------------------
        '''

    # public class attributes:

        __no_of_wolfs = 0

    # initializer:

        def __init__(self, n = '*', h = 10, c = (50,50)):
            super().__init__(n, h, c)
            Wolf.__no_of_wolfs += 1
            self.__id = Wolf.__no_of_wolfs

    # classmethods:

        @classmethod
        def get_wolfs(cls):
            return cls.__no_of_wolfs

    # regular methods:

        def howl(self):
            '''
            prints out the specific behaviour of the wolf
            '''
            wolf_name = self.name
            print(f"I'm {wolf_name} and now I'm howling!")

In [28]:

    # Dog class testing

    # creating Dog objects

    names = ['Ali', 'Bili', 'Cili']
    dogs = []

    for i in range(len(names)):
        dogs.append(Dog(names[i], 10, (50, 50+i)))

    # display an object's members:

    print(dir(dogs[0]), end = '\n'*2)

    # printing out the number of dogs:

    print(f'The number of dogs is {Dog.get_dogs()}.', end = '\n'*2)

    # printing out the dogs's attributes:

    for dog in dogs:
        print(f'name: {dog.name}, health: {dog.health}, location: {dog.coord}')
        dog.eat()
        dog.sleep()
        dog.play()
        dog.bark()
        print()

    # testing the object's behaviour:

    dogs[0].run('f', 5)
    print(f'location:{dogs[0].coord}')

    ['_Animal__coord', '_Animal__health', '_Animal__name', '_Dog__id', '_Dog__no_of_dogs', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'bark', 'coord', 'eat', 'get_dogs', 'health', 'name', 'play', 'run', 'sleep']

    The number of dogs is 3.

    name: Ali, health: 10, location: (50, 50)
    I'm Ali and now I'm eating!
    I'm Ali and now I'm sleeping!
    I'm Ali and now I'm playing!
    I'm Ali and now I'm barking!

    name: Bili, health: 10, location: (50, 51)
    I'm Bili and now I'm eating!
    I'm Bili and now I'm sleeping!
    I'm Bili and now I'm playing!
    I'm Bili and now I'm barking!

    name: Cili, health: 10, location: (50, 52)
    I'm Cili and now I'm eating!
    I'm Cili and now I'm sleeping!
    I'm Cili and now I'm playing!
    I'm Cili and now I'm barking!

    I'm Ali and now I'm running forward!
    location:(50, 55)

In [29]:

    # Lion class testing

    # creating Lion objects

    names = ['Ali', 'Bili', 'Cili']
    lions = []

    for i in range(len(names)):
        lions.append(Lion(names[i], 10, (50, 50+i)))

    # display an object's members:

    print(dir(lions[0]), end = '\n'*2)

    # printing out the number of dogs:

    print(f'The number of lions is {Lion.get_lions()}.', end = '\n'*2)

    # printing out the lions's attributes:

    for lion in lions:
        print(f'name: {lion.name}, health: {lion.health}, location: {lion.coord}')
        lion.eat()
        lion.sleep()
        lion.hunt()
        lion.roar()
        print()

    # testing the object's behaviour:

    lions[0].run('f', 5)
    print(f'location:{lions[0].coord}')

    ['_Animal__coord', '_Animal__health', '_Animal__name', '_Lion__id', '_Lion__no_of_lions', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'coord', 'eat', 'get_lions', 'health', 'hunt', 'name', 'roar', 'run', 'sleep']

    The number of lions is 3.

    name: Ali, health: 10, location: (50, 50)
    I'm Ali and now I'm eating!
    I'm Ali and now I'm sleeping!
    I'm Ali and now I'm hunting!
    I'm Ali and now I'm roaring!

    name: Bili, health: 10, location: (50, 51)
    I'm Bili and now I'm eating!
    I'm Bili and now I'm sleeping!
    I'm Bili and now I'm hunting!
    I'm Bili and now I'm roaring!

    name: Cili, health: 10, location: (50, 52)
    I'm Cili and now I'm eating!
    I'm Cili and now I'm sleeping!
    I'm Cili and now I'm hunting!
    I'm Cili and now I'm roaring!

    I'm Ali and now I'm running forward!
    location:(50, 55)

In [30]:

    # Wolf class testing

    # creating Wolf objects

    names = ['Ali', 'Bili', 'Cili']
    wolfs = []

    for i in range(len(names)):
        wolfs.append(Wolf(names[i], 10, (50, 50+i)))

    # display an object's members:

    print(dir(wolfs[0]), end = '\n'*2)

    # printing out the number of dogs:

    print(f'The number of wolfs is {Wolf.get_wolfs()}.', end = '\n'*2)

    # printing out the wolfs's attributes:

    for wolf in wolfs:
        print(f'name: {lion.name}, health: {lion.health}, location: {lion.coord}')
        wolf.eat()
        wolf.sleep()
        wolf.hunt()
        wolf.howl()
        print()

    # testing the object's behaviour:

    wolfs[0].run('f', 5)
    print(f'location:{wolfs[0].coord}')

    ['_Animal__coord', '_Animal__health', '_Animal__name', '_Wolf__id', '_Wolf__no_of_wolfs', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'coord', 'eat', 'get_wolfs', 'health', 'howl', 'hunt', 'name', 'run', 'sleep']

    The number of wolfs is 3.

    name: Cili, health: 10, location: (50, 52)
    I'm Ali and now I'm eating!
    I'm Ali and now I'm sleeping!
    I'm Ali and now I'm hunting!
    I'm Ali and now I'm howling!

    name: Cili, health: 10, location: (50, 52)
    I'm Bili and now I'm eating!
    I'm Bili and now I'm sleeping!
    I'm Bili and now I'm hunting!
    I'm Bili and now I'm howling!

    name: Cili, health: 10, location: (50, 52)
    I'm Cili and now I'm eating!
    I'm Cili and now I'm sleeping!
    I'm Cili and now I'm hunting!
    I'm Cili and now I'm howling!

    I'm Ali and now I'm running forward!
    location:(50, 55)