Skip to content

12. As classes e os objetos

A classe é o molde a partir do qual são criados os objetos. Diz-se que o objeto é uma instância de uma classe.

Image

Nota: a pasta [shared] foi colocada na pasta [Sources Root] do projeto.

12.1. script [classes_01]: uma classe Objeto

O script [classes_01] mostra uma utilização obsoleta das classes:


# uma classe
class Objet(object):
    """une classe Objet vide"""


# qualquer variável do tipo [Objet] pode ter atributos por definição
obj1 = Objet()
obj1.attr1 = "un"
obj1.attr2 = 100
# exibe o objeto
print(f"objet1=[{obj1}, {type(obj1)},{id(obj1)},{obj1.attr1},{obj1.attr2}]")
# altera o objeto
obj1.attr2 += 100
# exibe o objeto
print(f"objet1=[{obj1.attr1},{obj1.attr2}]")
# atribui a referência obj1 a obj2
obj2 = obj1
# altera o objeto para o qual aponta obj2
obj2.attr2 = 0
# exibe os dois objetos — obj1 aponta agora para um objeto modificado
print(f"objet1=[{obj1.attr1},{obj1.attr2}]")
print(f"objet2=[{obj2.attr1},{obj2.attr2}]")
# obj1 e obj2 apontam para o mesmo objeto
print(f"objet1=[{obj1}, {id(obj1)},{obj1.attr1},{obj1.attr2}]")
print(f"objet2=[{obj2}, {id(obj2)},{obj2.attr1},{obj2.attr2}]")
print(obj1 == obj2)
# tipo da instância obj1
print(f"type(obj1)={type(obj1)}")
print(f"isinstance(obj1,Objet)={isinstance(obj1, Objet)}, isinstance(obj1,object)={isinstance(obj1, object)}")
# qualquer tipo é um objeto em Python
print(f"type(4)={type(4)}")
print(f"isinstance(4, int)={isinstance(4, int)}, isinstance(4, object)={isinstance(4, object)}")

Notas:

  • linhas 2-3: uma classe [Objet] vazia;
  • linha 2: a classe pode ser declarada de três formas:
    • class Objet;
    • class Objeto();
    • class Objet(object);
  • linha 3: outra forma de comentário. Este, precedido por três «, pode estender-se por várias linhas;
  • linha 7: instanciação da classe Objet. O resultado é um endereço, como se pode ver nas linhas 24-26;
  • linhas 8-9: inicialização direta de dois atributos do objeto;
  • linha 17: cópia de referências. As variáveis obj1 e obj2 são dois ponteiros (referências) para o mesmo objeto;
  • linha 19: altera-se o objeto apontado por [obj2]. Como [obj1] e [obj2] apontam para o mesmo objeto, as exibições dos objetos [obj1, obj2] nas linhas 21 e 22 irão mostrar que o objeto apontado por [obj1] foi alterado;
  • linhas 24-26: estas linhas têm como objetivo demonstrar a igualdade entre as variáveis [obj1] e [obj2]. A exibição da linha 26 irá demonstrá-lo. Nesta comparação, são os endereços [obj1] e [obj2] que são iguais;
  • cada objeto Python é identificado por um número único que se obtém com a expressão [id(objet)]. As linhas 24 e 25 irão mostrar que os números dos objetos a que apontam [obj1] e [obj2] são idênticos, demonstrando assim que estas duas referências apontam para o mesmo objeto;
  • linhas 27-29: a função [isinstance(expr,Type)] retorna o valor booleano True se a expressão [expr] for do tipo [Type]. Aqui, vamos ver que [obj1] é do tipo [Objet], o que parece natural, mas também é do tipo [object]. A classe [object] é a classe pai de todas as classes Python. Por propriedade da herança de classes, uma classe filha F possui todas as propriedades da sua classe pai P e a função [isinstance(instance de F, P)] retorna True;
  • linhas 30-32: mostram que o tipo [int] é também um tipo [object]. Todos os tipos do Python derivam da classe [object];

Resultados


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/01/classes_01.py
objet1=[<__main__.Objet object at 0x0000025C3F469BB0>, <class '__main__.Objet'>,2595221838768,un,100]
objet1=[un,200]
objet1=[un,0]
objet2=[un,0]
objet1=[<__main__.Objet object at 0x0000025C3F469BB0>, 2595221838768,un,0]
objet2=[<__main__.Objet object at 0x0000025C3F469BB0>, 2595221838768,un,0]
True
type(obj1)=<class '__main__.Objet'>
isinstance(obj1,Objet)=True, isinstance(obj1,object)=True
type(4)=<class 'int'>
isinstance(4, int)=True, isinstance(4, object)=True

Process finished with exit code 0

12.2. Script [classes_02]: uma classe Pessoa

O script [classes_02] mostra que os atributos de uma classe são públicos: são acessíveis diretamente a partir do exterior da classe. Trata-se, mais uma vez, de uma utilização das classes desaconselhada. No entanto, apresentamo-la porque, por vezes, é possível encontrar este tipo de código (o Python aceita-o) e, nessas situações, é necessário ser capaz de o compreender.


# classe Pessoa
class Personne:
    # atributos da classe
    # não declarados — podem ser criados dinamicamente

    # método
    def identité(self: object) -> str:
        # à primeira vista, utiliza atributos inexistentes
        return f"[{self.prénom},{self.nom},{self.âge}]"


# ---------------------------------- main
# os atributos são públicos e podem ser criados dinamicamente
# instanciação da classe
p = Personne()
# inicialização direta de atributos da classe
p.prénom = "Paul"
p.nom = "de la Hûche"
p.âge = 48
# chamada de um método da classe
print(f"personne={p.identité()}")
# tipo da instância p
print(f"type(p)={type(p)}")
print(f"isinstance(Personne)={isinstance(p, Personne)}, isinstance(object)={isinstance(p, object)}")

Notas:

  • linhas 2-9: uma classe com um método;
  • linha 7: qualquer método de uma classe deve ter como primeiro parâmetro o objeto self, que designa o objeto atual. O método [identité] devolve uma cadeia de caracteres;
  • linha 15: instanciação de um objeto [Personne];
  • linhas 16-19: mostram que os atributos do objeto podem ser criados dinamicamente (não existem na definição da classe);
  • linha 9: os atributos da classe são designados pela notação [self.attribut];
  • as linhas 23-24 irão mostrar que o objeto [p] é simultaneamente uma instância da classe [Personne] e da classe [object];

Resultados


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/01/classes_02.py
personne=[Paul,de la Hûche,48]
type(p)=<class '__main__.Personne'>
isinstance(Personne)=True, isinstance(object)=True

Process finished with exit code 0

12.3. Script [classes_03]: a classe Pessoa com um construtor

O script [classes_03] mostra a utilização normal de uma classe:


# classe Pessoa
class Personne:
    # construtor — inicializa três atributos
    def __init__(self: object, prénom: str, nom: str, âge: int):
        # nome próprio: nome próprio da pessoa
        # apelido: apelido da pessoa
        # idade: idade da pessoa

        self.prénom = prénom
        self.nom = nom
        self.âge = âge

    # retorna os atributos da classe na forma de uma cadeia de caracteres
    def identité(self: object) -> str:
        return f"[{self.prénom},{self.nom},{self.âge}]"


# ---------------------------------- mão
# um objeto Pessoa
p = Personne("Paul", "de la Hûche", 48)
# chamada de um método
print(f"personne={p.identité()}")

Notas:

  • linha 4: o construtor de uma classe chama-se __init__. Tal como acontece com os outros métodos, o seu primeiro parâmetro é self;
  • linha 20: um objeto Personne é criado com o construtor da classe;
  • linhas 13-15: o método [identité] devolve uma cadeia de caracteres que representa o conteúdo do objeto;
  • linha 22: exibição da identidade da pessoa;

Resultados


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/01/classes_03.py
personne=[Paul,de la Hûche,48]

Process finished with exit code 0

12.4. Script [classes_04]: métodos estáticos

Definimos, na pasta [modules], a seguinte classe [Utils] (utils.py):

Image


class Utils:
    # método estático
    @staticmethod
    def is_string_ok(string: str) -> bool:
        # string é uma cadeia de caracteres
        erreur = not isinstance(string, str)
        if not erreur:
            # a cadeia está vazia?
            erreur = string.strip() == ''
        # resultado
        return not erreur

Notas

  • linha 3: a anotação [@staticmethod] indica que o método assim anotado é um método de classe e não um método de instância. Isto é visível pelo facto de o primeiro parâmetro do método assim anotado não ser a palavra-chave [self]. Assim, o método estático não tem acesso aos atributos do objeto. Em vez de escrever:
u=Utils()
print(u.is_string_ok("abcd")

escreve-se

print(Utils.is_string_ok("abcd")

Como acima escrevemos [Utils.is_string_ok], o método [is_string_ok] é denominado método de classe (a classe Utils, neste caso). Para tal, o método [Utils.is_string_ok] deve ser anotado com a palavra-chave [@staticmethod].

O método estático [Utils.is_string_ok] permite, neste caso, verificar se um dado é uma cadeia de caracteres não vazia.

O script [classes_04] utiliza a classe [Utils] da seguinte forma:


# configurar a aplicação
import config

config = config.configure()

# o syspath está configurado — já é possível efetuar as importações
from utilitaires import Utils

print(Utils.is_string_ok("  "))
print(Utils.is_string_ok(47))
print(Utils.is_string_ok(" q "))
  • linhas 1-4: utiliza-se um script de configuração;

O script de configuração [config.py] é o seguinte:


def configure():
    import os

    # pasta do ficheiro de configuração
    script_dir = os.path.dirname(os.path.abspath(__file__))

    # caminhos absolutos das pastas a incluir no syspath
    absolute_dependencies = [
        f"{script_dir}/shared",
        "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/venv/lib/site-packages",
        "C:/myprograms/Python38/lib",
        "C:/myprograms/Python38/DLLs"
    ]

    # atualização do syspath
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    # a configuração é devolvida
    return {}
  • linha 9: a pasta [shared] será colocada no Python Path;

O resultado da execução é o seguinte:

1
2
3
4
5
6
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/01/classes_04.py
False
False
True

Process finished with exit code 0

12.5. Script [classes_05]: verificações de validade dos atributos

O script [classes_05] introduz novos conceitos:

  • definição de um tipo de exceção proprietário;
  • definição do método [_str_], que é o método de identidade por predefinição das classes;
  • definição de propriedades;

# configura-se a aplicação
import config

config = config.configure()

# o syspath está configurado — já é possível efetuar as importações
from utilitaires import Utils


# uma classe de exceção proprietária derivada de [BaseException]
class MyException(BaseException):
    # não se faz nada: classe vazia
    pass


# classe Pessoa
class Personne:
    # construtor
    def __init__(self: object, prénom: str = "x", nom: str = "y", âge: int = 0):
        # nome próprio: nome próprio da pessoa
        # apelido: apelido da pessoa
        # idade: idade da pessoa

        # armazenamento dos parâmetros
        # as inicializações serão feitas através dos setters
        self.prénom = prénom
        self.nom = nom
        self.âge = âge

    # método toString da classe
    def __str__(self: object) -> str:
        return f"[{self.__prénom},{self.__nom},{self.__âge}]"

    # getters
    @property
    def prénom(self) -> str:
        return self.__prénom

    @property
    def nom(self) -> str:
        return self.__nom

    @property
    def âge(self) -> int:
        return self.__âge

    # setter
    @prénom.setter
    def prénom(self, prénom: str):
        # o nome próprio não pode estar vazio
        if Utils.is_string_ok(prénom):
            self.__prénom = prénom.strip()
        else:
            raise MyException("Le prénom doit être une chaîne de caractères non vide")

    @nom.setter
    def nom(self, nom: str):
        # o nome próprio não pode estar vazio
        if Utils.is_string_ok(nom):
            self.__nom = nom.strip()
        else:
            raise MyException("Le nom doit être une chaîne de caractères non vide")

    @âge.setter
    def âge(self, âge: int):
        # a idade deve ser um número inteiro >=0
        erreur = False
        if isinstance(âge, int):
            if âge >= 0:
                self.__âge = âge
            else:
                erreur = True
        else:
            erreur = True
        # erro?
        if erreur:
            raise MyException("L'âge doit être un entier >=0")


# ---------------------------------- main
# um objeto Pessoa
try:
    # instanciação da classe Pessoa
    p = Personne("Paul", "de la Hûche", 48)
    # exibição do objeto p
    print(f"personne={p}")
except MyException as erreur:
    # exibição do erro
    print(erreur)

# outro objeto Pessoa
try:
    # instanciação da classe Pessoa
    p = Personne("xx", "yy", "zz")
    # exibição do objeto p
    print(f"personne={p}")
except MyException as erreur:
    # exibição de erro
    print(erreur)

# outra pessoa, desta vez sem parâmetros
try:
    # instanciação da classe Pessoa
    p = Personne()
    # exibição do objeto p
    print(f"personne={p}")
except MyException as erreur:
    # exibição da mensagem de erro
    print(erreur)

# não é possível aceder aos atributos privados __attr da classe
p.__prénom = "Gaëlle"
print(f"p.prénom={p.prénom}")
print(f"p.__prénom={p.__prénom}")
p.prénom = "Sébastien"
print(f"p.prénom={p.prénom}")
print(f"p.__prénom={p.__prénom}")

Notas:

  • linhas 10-13: uma classe MyException derivada da classe BaseException (veremos este ponto um pouco mais adiante). Não acrescenta qualquer funcionalidade a esta última. Existe apenas para dispor de uma exceção proprietária;
  • linha 19: o construtor tem valores por predefinição para os seus parâmetros. Assim, a operação p=Personne() é equivalente a p=Personne("x","y",0);
  • linhas 34-45: as propriedades da classe. Trata-se de métodos anotados com a palavra-chave [@property]. São utilizados para devolver o valor dos atributos;
  • linhas 47-77: os setter da classe. São métodos anotados com a palavra-chave [@attributsetter]. São utilizados para definir o valor dos atributos;
  • linhas 48-54: o setter do atributo [prénom]. Este método será chamado sempre que for atribuído um valor ao atributo [prénom]:
p=Personne(…)
p.prénom=valeur

A linha 2 provocará a chamada de [p.prénom(valeur)]. A vantagem de utilizar um setter para atribuir um valor a um atributo é que, sendo o setter uma função, é possível verificar a validade do valor atribuído ao atributo;

  • linha 51: verifica-se se o valor atribuído ao atributo [prénom] é uma cadeia de caracteres não vazia. Para tal, utiliza-se o método estático [Utils.isStringOk] visto anteriormente;
  • linha 52: o valor atribuído ao atributo [prénom] é limpo dos «espaços» no início e no fim da cadeia e atribuído ao atributo [self.__prénom]. Portanto, não é o atributo [prénom] que é utilizado aqui. Não seria possível fazê-lo de outra forma, caso contrário teríamos uma chamada recursiva infinita. Poderíamos ter utilizado qualquer nome de atributo. O facto de termos utilizado o atributo [__prénom] com dois sublinhados no início do identificador tem um significado especial: os atributos precedidos por dois sublinhados são privados da classe. Isto significa que não são visíveis a partir do exterior da mesma. Por isso, não é possível escrever:
p=Personne(…)
p.__prénom=valeur

Na verdade, veremos em breve que é possível escrevê-lo, mas que isso não altera o nome próprio. Tem outro efeito;

  • linhas 53-54: se o valor atribuído ao nome próprio não estiver correto, é lançada uma exceção. Assim, o código que faz a chamada saberá que a sua chamada está incorreta;
  • linhas 35-37: a propriedade [prénom]. Será chamada sempre que escrevermos [p.prénom] numa expressão. Nesse caso, será chamado o método [p.prénom()]. Na linha 37, devolve-se o valor do atributo [__prénom], uma vez que se verificou que o setter do atributo [prénom] atribuía o seu valor ao atributo privado [__prénom];
  • linhas 56-62: o setter do atributo [nom] é construído de forma análoga ao do atributo [prénom]. O mesmo se aplica ao do atributo [âge] nas linhas 64-77;
  • embora as propriedades [prénom, nom, valeur] não sejam os verdadeiros atributos — que, na realidade, são [__prénom, __nom, __âge] —, continuaremos a referir-nos a elas como atributos da classe, uma vez que são utilizadas como tal;
  • linhas 19-28: o construtor da classe utiliza implicitamente os setters dos atributos [prénom, nom, âge]. Com efeito, ao escrever, na linha 26, [self.prénom = prénom], é implicitamente o método [prénom(self, prénom)] que será chamado. A validade do parâmetro [prénom] será então verificada. O mesmo acontecerá com os outros dois atributos [nom, âge];
  • com este modelo, não é possível atribuir valores incorretos aos atributos [prénom, nom, âge] da classe;
  • linhas 30-32: a função __str__ substitui o método que anteriormente se chamava identité. O nome [__str__] (com dois sublinhados à frente e atrás) não é aleatório. Veremos isso mais adiante;
  • linhas 83-86: instanciação de uma pessoa e, em seguida, exibição da sua identidade;
  • linha 84: instância;
  • linha 86: exibição. A operação solicita a exibição da pessoa p sob a forma de uma cadeia de caracteres. O interpretador Python chama então automaticamente o método p.__str__(), caso este exista. Este método desempenha o mesmo papel que o método toString() em Java ou nas linguagens .NET;
  • linhas 87-89: tratamento de uma eventual exceção do tipo MyException. Em seguida, exibe o erro;
  • linhas 91-99: o mesmo se aplica a uma segunda pessoa instanciada com parâmetros errados;
  • linhas 102-109: o mesmo se aplica a uma terceira instância criada com os parâmetros por predefinição: não são passados quaisquer parâmetros. São, então, utilizados os valores por predefinição desses parâmetros no construtor;
  • linhas 112-117: foi definido que o atributo [__prénom] era privado, pelo que, normalmente, não é acessível a partir do exterior da classe. Pretende-se verificar isso;
  • linhas 112-114: atribui-se um valor ao atributo [__prénom] e, em seguida, verifica-se o valor dos atributos [__prénom] e [prénom], que normalmente são iguais;
  • linhas 115-117: repete-se a operação, inicializando desta vez o atributo [prénom];

Resultados


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/01/classes_05.py
personne=[Paul,de la Hûche,48]
L'âge doit être un entier >=0
personne=[x,y,0]
p.prénom=x
p.__prénom=Gaëlle
p.prénom=Sébastien
p.__prénom=Gaëlle

Process finished with exit code 0

Notas

  • linhas 5-6: verifica-se que a atribuição [p.__prénom = "Gaëlle"] não alterou o valor do atributo [prénom], linha 5;
  • linhas 7-8: verifica-se que a atribuição [p.prénom = "Sébastien"] não alterou o valor do atributo [__prénom], linha 8;

O que se pode deduzir disto? Que, provavelmente, a operação [p.__prénom = "Gaëlle"] criou um atributo público [__prénom] na classe, mas que este é diferente do atributo privado [__prénom] manipulado no seu interior;

12.6. Script [classes_06]: adição de um método de inicialização do objeto

O script [classes_06] adiciona um método à classe [Personne]:


# Configurar a aplicação
import config

config = config.configure()

# o syspath está configurado — é possível efetuar as importações
from utilitaires import Utils


# uma classe de exceção proprietária derivada de [BaseException]
class MyException(BaseException):
    # não se faz nada: classe vazia
    pass


# classe Pessoa
class Personne:
    # construtor
    def __init__(self: object, prénom: str = "x", nom: str = "y", âge: int = 0):
        # nome próprio: nome próprio da pessoa
        # apelido: apelido da pessoa
        # idade: idade da pessoa

        # armazenamento dos parâmetros
        # as inicializações serão efetuadas através dos setters
        self.prénom = prénom
        self.nom = nom
        self.âge = âge

    # outro método de inicialização
    def init_with_personne(self: object, p: object):
        # inicializa o objeto atual com uma pessoa p
        self.__init__(p.prénom, p.nom, p.âge)

    # método toString da classe
    def __str__(self: object) -> str:
        return f"[{self.__prénom},{self.__nom},{self.__âge}]"

    # getters


    # setters



# ---------------------------------- main
# um objeto Pessoa
try:
    # instanciação da classe Pessoa
    p = Personne("Paul", "de la Hûche", 48)
    # exibição do objeto p
    print(f"personne={p}")
except MyException as erreur:
    # exibição da mensagem de erro
    print(erreur)

# outro objeto Pessoa
try:
    # instanciação da classe Pessoa
    p = Personne("xx", "yy", "zz")
    # exibição do objeto p
    print(f"p={p}")
except MyException as erreur:
    # exibição de mensagem de erro
    print(erreur)

# outra pessoa, desta vez sem parâmetros
try:
    # instanciação da classe Pessoa
    p = Personne()
    # exibição do objeto p
    print(f"p={p}")
except MyException as erreur:
    # exibição de mensagem de erro
    print(erreur)

# outra Pessoa obtida por cópia
try:
    # instanciação da classe Pessoa
    p2 = Personne()
    p2.init_with_personne(p)
    # exibição do objeto p2
    print(f"p2={p2}")
except MyException as erreur:
    # exibição de mensagem de erro
    print(erreur)

Notas:

  • a diferença em relação ao script anterior encontra-se nas linhas 30-33. Foi adicionado o método initWithPersonne. Este recorre ao construtor __init__. Não é possível, tal como nas linguagens tipadas, ter métodos com o mesmo nome diferenciados pela natureza dos seus parâmetros ou do seu resultado. Por conseguinte, não é possível ter vários construtores que criem o objeto a partir de parâmetros diferentes, neste caso um objeto do tipo Personne;

Resultados


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/01/classes_06.py
personne=[Paul,de la Hûche,48]
L'âge doit être un entier >=0
p=[x,y,0]
p2=[x,y,0]

Process finished with exit code 0

12.7. Script [classes_07]: uma lista de objetos Pessoa

Passamos agora a colocar as classes [MyException] e [Personne] num módulo para podermos utilizá-las sem ter de copiar o seu código:

Image

Ambas as classes estarão no módulo [myclasses.py] acima.

O script [classes_07] mostra que é possível obter uma lista de objetos:


# configurar a aplicação
import config

config = config.configure()

# o syspath está configurado — já é possível efetuar as importações
from myclasses import Personne

# ---------------------------------- principal
# criação de uma lista de objetos «pessoa»
groupe = [Personne("Paul", "Langevin", 48), Personne("Sylvie", "Lefur", 70)]
# identidade dessas pessoas
for i in range(len(groupe)):
    print(f"groupe[{i}]={groupe[i]}")

Notas:

  • linha 7: importa-se a classe [Personne];
  • linha 11: uma lista de objetos do tipo [Personne];
  • linhas 13-14: percorre-se esta lista para apresentar cada um dos seus elementos;
  • linha 14: a função [print] irá apresentar a cadeia de caracteres que representa o objeto [groupe[i]]. Por predefinição, será chamado o método [__str__];

Resultados


C:\Users\serge\.virtualenvs\cours-python-v02\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/v-02/classes/01/classes_07.py
groupe[0]=[Paul,Langevin,48]
groupe[1]=[Sylvie,Lefur,70]

Process finished with exit code 0

12.8. Script [classes_08]: criação de uma classe derivada da classe Pessoa

No módulo [myclasses], definimos a seguinte classe [Enseignant]:


# classe Professor
class Enseignant(Personne):
    # construtor
    def __init__(self, prénom: str = "x", nom: str = "x", âge: int = 0, discipline: str = "x"):
        # nome próprio: nome próprio da pessoa
        # apelido: apelido da pessoa
        # idade: idade da pessoa
        # disciplina: disciplina lecionada

        # inicialização do pai/mãe
        Personne.__init__(self, prénom, nom, âge)
        # outras iniciais
        self.discipline = discipline

    # toString
    def __str__(self) -> str:
        return f"enseignant[{super().__str__()},{self.discipline}]"

    # propriedades
    @property
    def discipline(self) -> str:
        return self.__discipline

    @discipline.setter
    def discipline(self, discipline: str):
        # a disciplina deve ser uma cadeia de caracteres não vazia
        if Utils.is_string_ok(discipline):
            self.__discipline = discipline
        else:
            raise MyException("La discipline doit être une chaîne de caractères non vide")
  • linha 2: declara a classe Enseignant como uma classe derivada da classe Personne. Uma classe derivada possui todas as propriedades (atributos e métodos) da sua classe pai, além das que lhe são próprias;
  • linha 13: a classe [Enseignant] define um novo atributo [discipline];
  • linha 11: o construtor da classe derivada Enseignant deve chamar o construtor da classe pai Personne, passando-lhe os parâmetros que este espera;
  • linha 17: a função [super()] devolve a classe pai. Aqui, chama-se a função [__str__] da classe pai;
  • linhas 19-30: definem-se os getter e setter do novo atributo [discipline];

O script [classes_08] utiliza a classe [Enseignant] da seguinte forma:


# configura-se a aplicação
import config

config = config.configure()

# o syspath está configurado — já se podem fazer as importações
from myclasses import Personne, Enseignant

# ---------------------------------- main
# criação de um array de objetos Pessoa e derivados
groupe = [Enseignant("Paul", "Langevin", 48, "anglais"), Personne("Sylvie", "Lefur", 70)]
# identidade dessas pessoas
for i in range(len(groupe)):
    print(f"groupe[{i}]={groupe[i]}")

Notas:

  • linha 7: importam-se as classes [Personne] e [Enseignant] definidas no ficheiro [myclasses.py];
  • linhas 11-14: define-se um grupo de pessoas cuja identidade é posteriormente apresentada;

Resultados


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/01/classes_08.py
groupe[0]=enseignant[[Paul,Langevin,48],anglais]
groupe[1]=[Sylvie,Lefur,70]

Process finished with exit code 0

12.9. Script [classes_09]: segunda classe derivada da classe Pessoa

O script [classes_09] introduz a classe [Etudiant], derivada da classe [Personne]. Esta é definida da seguinte forma no módulo [myclasses]:


# classe «Estudante»
class Etudiant(Personne):
    # construtor
    def __init__(self: object, prénom: str = "x", nom: str = "y", âge: int = 0, formation: str = "x"):
        Personne.__init__(self, prénom, nom, âge)
        self.formation = formation

    # toString
    def __str__(self: object) -> str:
        return f"étudiant[{super().__str__()},{self.formation}]"

    # propriedades
    @property
    def formation(self) -> str:
        return self.__formation

    @formation.setter
    def formation(self, formation: str):
        # a formação deve ser uma cadeia não vazia
        if Utils.is_string_ok(formation):
            self.__formation = formation
        else:
            raise MyException("La formation doit être une chaîne de caractères non vide")

O script [classes_09] utiliza a classe [Etudiant] da seguinte forma:


# configura-se a aplicação
import config

config = config.configure()

# o syspath está configurado — já é possível efetuar as importações
from myclasses import Personne, Enseignant, Etudiant

# ---------------------------------- main
# criação de um array de objetos Pessoa e derivados
groupe = [Enseignant("Paul", "Langevin", 48, "anglais"), Personne("Sylvie", "Lefur", 70),
          Etudiant("Steve", "Boer", 22, "iup2 qualité")]
# identidade destas pessoas
for personne in groupe:
    # exibição da pessoa
    print(personne)

Notas:

  • este script é semelhante ao anterior.

Resultados


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/01/classes_09.py
enseignant[[Paul,Langevin,48],anglais]
[Sylvie,Lefur,70]
étudiant[[Steve,Boer,22],iup2 qualité]

Process finished with exit code 0

12.10. Script [classes_10]: a propriedade [__dict__]

O script [classes_10] apresenta a propriedade [__dict__], que iremos utilizar frequentemente daqui em diante:


# configuração da aplicação
import config

config = config.configure()

# o syspath está configurado — já é possível efetuar as importações
from myclasses import Etudiant

# ---------------------------------- main
# criação de um aluno
étudiant=Etudiant("Steve", "Boer", 22, "iup2 qualité")
# dicionário de propriedades
print(étudiant.__dict__)

Comentários

  • linhas 1-4: a aplicação é configurada;
  • linha 7: a classe [Etudiant] é importada;
  • linha 11: instância de um estudante;
  • linha 13: utilização do método predefinido [__dict__] (2 caracteres sublinhados antes e depois do identificador);

Os resultados são os seguintes:

1
2
3
4
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/01/classes_10.py
{'_Personne__prénom': 'Steve', '_Personne__nom': 'Boer', '_Personne__âge': 22, '_Etudiant__formation': 'iup2 qualité'}

Process finished with exit code 0
  • na linha 2, obtém-se um dicionário cujas chaves são as propriedades do objeto, precedidas pelo nome da classe a que pertencem. Utilizaremos este dicionário para estabelecer uma ponte entre o objeto e o dicionário;