Herança e polimorfismo

Herança e polimorfismo - este é um conceito muito importante em Python. Você deve entendê-lo melhor se quiser aprender.

Herança

Uma das principais vantagens da Programação Orientada a Objetos é a reutilização. A herança é um dos mecanismos para conseguir o mesmo. A herança permite que o programador crie primeiro uma classe geral ou base e depois a estenda para uma classe mais especializada. Ele permite que o programador escreva um código melhor.

Usando a herança, você pode usar ou herdar todos os campos de dados e métodos disponíveis em sua classe base. Mais tarde, você pode adicionar seus próprios métodos e campos de dados, portanto, a herança fornece uma maneira de organizar o código, em vez de reescrevê-lo do zero.

Na terminologia orientada a objetos, quando a classe X estende a classe Y, então Y é chamado de super / pai / classe base e X é chamado de subclasse / filho / classe derivada. Um ponto a ser observado aqui é que apenas os campos de dados e métodos que não são privados são acessíveis por classes filhas. Métodos e campos de dados privados são acessíveis apenas dentro da classe.

sintaxe para criar uma classe derivada é -

class BaseClass:
   Body of base class
class DerivedClass(BaseClass):
   Body of derived class

Atributos de herança

Agora veja o exemplo abaixo -

Resultado

Primeiro criamos uma classe chamada Date e passamos o objeto como um argumento, here-object é uma classe embutida fornecida pelo Python. Posteriormente, criamos outra classe chamada time e chamamos a classe Date como um argumento. Por meio dessa chamada, obtemos acesso a todos os dados e atributos da classe Date na classe Time. Por causa disso, quando tentamos obter o método get_date do objeto da classe Time tm que criamos mais cedo possível.

Hierarquia de pesquisa Object.Attribute

  • A instância
  • A classe
  • Qualquer classe da qual esta classe herda

Exemplos de herança

Vamos dar uma olhada no encerramento do exemplo de herança -

Vamos criar algumas classes para participar de exemplos -

  • Animal - Aula simula um animal
  • Gato - Subclasse de Animal
  • Cachorro - Subclasse de Animal

Em Python, construtor de classe usado para criar um objeto (instância), e atribuir o valor para os atributos.

Construtor de subclasses sempre chamado a um construtor de classe pai para inicializar o valor para os atributos na classe pai, então ele começa a atribuir valor para seus atributos.

Resultado

No exemplo acima, vemos os atributos ou métodos de comando que colocamos na classe pai de forma que todas as subclasses ou classes filho herdam essa propriedade da classe pai.

Se uma subclasse tentar herdar métodos ou dados de outra subclasse, ocorrerá um erro, como vemos quando a classe Dog tenta chamar métodos swatstring () dessa classe cat, isso gera um erro (como AttributeError em nosso caso).

Polimorfismo (“MUITAS FORMAS”)

O polimorfismo é um recurso importante de definição de classe em Python, utilizado quando você nomeia métodos comumente em classes ou subclasses. Isso permite que as funções usem entidades de diferentes tipos em momentos diferentes. Portanto, ele fornece flexibilidade e baixo acoplamento para que o código possa ser estendido e facilmente mantido ao longo do tempo.

Isso permite que as funções usem objetos de qualquer uma dessas classes polimórficas sem a necessidade de estar ciente das distinções entre as classes.

O polimorfismo pode ser executado por meio de herança, com subclasses fazendo uso de métodos da classe base ou substituindo-os.

Vamos entender o conceito de polimorfismo com nosso exemplo de herança anterior e adicionar um método comum chamado show_affection em ambas as subclasses -

A partir do exemplo, podemos ver que se refere a um projeto em que o objeto de tipo diferente pode ser tratado da mesma maneira ou mais especificamente duas ou mais classes com método do mesmo nome ou interface comum devido ao mesmo método (show_affection no exemplo abaixo) é chamado com qualquer tipo de objeto.

Resultado

Então, todos os animais mostram afeto (show_affection), mas o fazem de forma diferente. O comportamento de “mostrar afeto” é, portanto, polimórfico no sentido de que agia de forma diferente dependendo do animal. Assim, o conceito abstrato de “animal” não realmente “mostra_afeição”, mas animais específicos (como cães e gatos) têm uma implementação concreta da ação “mostrar_afeição”.

O próprio Python possui classes que são polimórficas. Por exemplo, a função len () pode ser usada com vários objetos e todos retornam a saída correta com base no parâmetro de entrada.

Substituindo

Em Python, quando uma subclasse contém um método que substitui um método da superclasse, você também pode chamar o método da superclasse chamando

Super (Subclasse, self) .method em vez de self.method.

Exemplo

class Thought(object):
   def __init__(self):
      pass
   def message(self):
      print("Thought, always come and go")

class Advice(Thought):
   def __init__(self):
      super(Advice, self).__init__()
   def message(self):
      print('Warning: Risk is always involved when you are dealing with market!')

Herdando o Construtor

Se observarmos nosso exemplo de herança anterior, __init__ estava localizado na classe pai no up, porque a classe filha cachorro ou gato não tinha o método __init__ nele. Python usou a pesquisa de atributo de herança para encontrar __init__ na classe animal. Quando criamos a classe filha, primeiro ela procurará o método __init__ na classe dog, então não o encontrou e então olhou para a classe pai Animal e encontrou lá e chamou-a lá. Portanto, à medida que o design de nossa classe se tornou complexo, podemos desejar inicializar uma instância primeiro processando-a por meio do construtor da classe pai e, em seguida, pelo construtor da classe filha.

Resultado

No exemplo acima, todos os animais têm um nome e todos os cães uma raça específica. Chamamos o construtor da classe pai com super. Portanto, o cachorro tem seu próprio __init__, mas a primeira coisa que acontece é que chamamos de super. Super é uma função incorporada e é projetada para relacionar uma classe a sua superclasse ou sua classe pai.

Nesse caso, dizemos que obtém a superclasse dog e passa a instância dog para qualquer método que digamos aqui o construtor __init__. Em outras palavras, estamos chamando a classe pai Animal de __init__ com o objeto cachorro. Você pode perguntar por que não dizemos apenas Animal __init__ com a instância dog, poderíamos fazer isso, mas se o nome da classe animal mudasse, em algum momento no futuro. E se quisermos reorganizar a hierarquia de classes, para que o cachorro herde de outra classe. Usar o super neste caso nos permite manter as coisas modulares e fáceis de mudar e manter.

Portanto, neste exemplo, podemos combinar a funcionalidade geral __init__ com uma funcionalidade mais específica. Isso nos dá a oportunidade de separar a funcionalidade comum da funcionalidade específica que pode eliminar a duplicação de código e relacionar as classes umas às outras de uma forma que reflita o design geral do sistema.

Conclusão

  • __init__ é como qualquer outro método; pode ser herdado

  • Se uma classe não tiver um construtor __init__, o Python verificará sua classe pai para ver se consegue encontrar um.

  • Assim que encontra um, Python o chama e para de procurar

  • Podemos usar a função super () para chamar métodos na classe pai.

  • Podemos querer inicializar no pai, bem como em nossa própria classe.

Herança múltipla e a árvore de pesquisa

Como o próprio nome indica, herança múltipla em Python é quando uma classe herda de várias classes.

Por exemplo, uma criança herda traços de personalidade de ambos os pais (mãe e pai).

Sintaxe de herança múltipla do Python

Para fazer com que uma classe herde de várias classes pais, escrevemos os nomes dessas classes dentro dos parênteses para a classe derivada enquanto a definimos. Separamos esses nomes com vírgulas.

Abaixo está um exemplo disso -

>>> class Mother:
   pass

>>> class Father:
   pass

>>> class Child(Mother, Father):
   pass

>>> issubclass(Child, Mother) and issubclass(Child, Father)
True

Herança múltipla refere-se à capacidade de herdar de duas ou mais de duas classes. A complexidade surge conforme a criança herda dos pais e os pais herdam da classe dos avós. Python sobe em uma árvore herdada em busca de atributos que estão sendo solicitados para serem lidos de um objeto. Ele verificará o na instância, dentro da classe, depois na classe pai e, por último, na classe dos avós. Agora surge a questão em que ordem as classes serão pesquisadas - primeiro a respiração ou primeiro a profundidade. Por padrão, Python vai com a profundidade primeiro.

É por isso que no diagrama abaixo o Python procura o método dothis () primeiro na classe A. Portanto, a ordem de resolução do método no exemplo abaixo será

Mro- D→B→A→C

Observe o diagrama de herança múltipla abaixo -

Vamos examinar um exemplo para entender o recurso “mro” de um Python.

Resultado

Exemplo 3

Vamos dar outro exemplo de herança múltipla em “formato de diamante”.

O diagrama acima será considerado ambíguo. Do nosso exemplo anterior, entendendo “ordem de resolução do método”. Isto é, mro será D → B → A → C → A, mas não é. Ao obter o segundo A do C, o Python irá ignorar o A. anterior, então o mro será, neste caso, será D → B → C → A.

Vamos criar um exemplo com base no diagrama acima -

Resultado

A regra simples para entender a saída acima é - se a mesma classe aparecer na ordem de resolução do método, as aparições anteriores dessa classe serão removidas da ordem de resolução do método.

Em conclusão -

  • Qualquer classe pode herdar de várias classes

  • Python normalmente usa uma ordem “primeiro em profundidade” ao pesquisar classes herdadas.

  • Mas quando duas classes herdam da mesma classe, o Python elimina as primeiras aparições dessa classe do mro.

Decoradores, métodos estáticos e de classe

Funções (ou métodos) são criados pela instrução def.

Embora os métodos funcionem exatamente da mesma maneira que uma função, exceto um ponto onde o primeiro argumento do método é um objeto de instância.

Podemos classificar métodos com base em como eles se comportam, como

  • Simple method- definido fora de uma classe. Esta função pode acessar os atributos da classe alimentando o argumento da instância:

def outside_func(():
  • Instance method -

def func(self,)
  • Class method - se precisarmos usar atributos de classe

@classmethod
def cfunc(cls,)
  • Static method - não tenho nenhuma informação sobre a aula

@staticmethod
def sfoo()

Até agora vimos o método de instância, agora é a hora de obter algumas informações sobre os outros dois métodos,

Método de Classe

O decorador @classmethod é um decorador de função embutido que passa a classe em que foi chamado ou a classe da instância em que foi chamado como primeiro argumento. O resultado dessa avaliação obscurece sua definição de função.

sintaxe

class C(object):
   @classmethod
   def fun(cls, arg1, arg2, ...):
      ....
fun: function that needs to be converted into a class method
returns: a class method for function

Eles têm acesso a este argumento cls, ele não pode modificar o estado da instância do objeto. Isso exigiria acesso a si mesmo.

  • Ele está vinculado à classe e não ao objeto da classe.

  • Os métodos de classe ainda podem modificar o estado da classe que se aplica a todas as instâncias da classe.

Método Estático

Um método estático não leva nem um parâmetro self nem cls (classe), mas é livre para aceitar um número arbitrário de outros parâmetros.

syntax

class C(object):
   @staticmethod
   def fun(arg1, arg2, ...):
   ...
returns: a static method for function funself.
  • Um método estático não pode modificar o estado do objeto nem o estado da classe.
  • Eles têm restrições quanto aos dados que podem acessar.

Quando usar o que

  • Geralmente usamos o método de classe para criar métodos de fábrica. Os métodos de fábrica retornam o objeto de classe (semelhante a um construtor) para diferentes casos de uso.

  • Geralmente usamos métodos estáticos para criar funções de utilidade.