Python 3 - Orientado a Objetos

Python tem sido uma linguagem orientada a objetos desde o tempo em que existiu. Devido a isso, criar e usar classes e objetos são extremamente fáceis. Este capítulo ajuda você a se tornar um especialista no uso do suporte de programação orientada a objetos do Python.

Se você não tem nenhuma experiência anterior com programação orientada a objetos (OO), pode querer consultar um curso introdutório sobre ela ou pelo menos um tutorial de algum tipo para que você tenha uma compreensão dos conceitos básicos.

No entanto, aqui está uma pequena introdução à Programação Orientada a Objetos (OOP) para ajudá-lo -

Visão geral da terminologia OOP

  • Class- Um protótipo definido pelo usuário para um objeto que define um conjunto de atributos que caracterizam qualquer objeto da classe. Os atributos são membros de dados (variáveis ​​de classe e variáveis ​​de instância) e métodos, acessados ​​via notação de ponto.

  • Class variable- Uma variável que é compartilhada por todas as instâncias de uma classe. Variáveis ​​de classe são definidas dentro de uma classe, mas fora de qualquer um dos métodos da classe. Variáveis ​​de classe não são usadas com tanta frequência quanto as variáveis ​​de instância.

  • Data member - Uma variável de classe ou variável de instância que contém dados associados a uma classe e seus objetos.

  • Function overloading- A atribuição de mais de um comportamento a uma função específica. A operação executada varia de acordo com os tipos de objetos ou argumentos envolvidos.

  • Instance variable - Uma variável que é definida dentro de um método e pertence apenas à instância atual de uma classe.

  • Inheritance - A transferência das características de uma classe para outras classes que dela derivam.

  • Instance- Um objeto individual de uma determinada classe. Um objeto obj que pertence a uma classe Circle, por exemplo, é uma instância da classe Circle.

  • Instantiation - A criação de uma instância de uma classe.

  • Method - Um tipo especial de função que é definido em uma definição de classe.

  • Object- Uma instância única de uma estrutura de dados que é definida por sua classe. Um objeto compreende membros de dados (variáveis ​​de classe e variáveis ​​de instância) e métodos.

  • Operator overloading - A atribuição de mais de uma função a um determinado operador.

Criação de classes

A instrução de classe cria uma nova definição de classe. O nome da classe segue imediatamente a palavra-chave class seguida por dois pontos da seguinte forma -

class ClassName:
   'Optional class documentation string'
   class_suite
  • A classe tem uma string de documentação, que pode ser acessada via ClassName.__doc__.

  • o class_suite consiste em todas as instruções do componente que definem os membros da classe, atributos de dados e funções.

Exemplo

A seguir está um exemplo de uma classe Python simples -

class Employee:
   'Common base class for all employees'
   empCount = 0

   def __init__(self, name, salary):
      self.name = name
      self.salary = salary
      Employee.empCount += 1
   
   def displayCount(self):
     print ("Total Employee %d" % Employee.empCount)

   def displayEmployee(self):
      print ("Name : ", self.name,  ", Salary: ", self.salary)
  • A variável empCount é uma variável de classe cujo valor é compartilhado entre todas as instâncias de a nesta classe. Isso pode ser acessado como Employee.empCount de dentro ou fora da classe.

  • O primeiro método __init __ () é um método especial, que é chamado de construtor de classe ou método de inicialização que o Python chama quando você cria uma nova instância dessa classe.

  • Você declara outros métodos de classe como funções normais, com a exceção de que o primeiro argumento para cada método é self . Python adiciona o argumento self à lista para você; você não precisa incluí-lo ao chamar os métodos.

Criação de objetos de instância

Para criar instâncias de uma classe, você chama a classe usando o nome da classe e passa quaisquer argumentos que seu método __init__ aceite.

This would create first object of Employee class
emp1 = Employee("Zara", 2000)
This would create second object of Employee class
emp2 = Employee("Manni", 5000)

Acessando Atributos

Você acessa os atributos do objeto usando o operador ponto com objeto. A variável de classe seria acessada usando o nome da classe da seguinte forma -

emp1.displayEmployee()
emp2.displayEmployee()
print ("Total Employee %d" % Employee.empCount)

Agora, juntando todos os conceitos -

#!/usr/bin/python3

class Employee:
   'Common base class for all employees'
   empCount = 0

   def __init__(self, name, salary):
      self.name = name
      self.salary = salary
      Employee.empCount += 1
   
   def displayCount(self):
     print ("Total Employee %d" % Employee.empCount)

   def displayEmployee(self):
      print ("Name : ", self.name,  ", Salary: ", self.salary)


#This would create first object of Employee class"
emp1 = Employee("Zara", 2000)
#This would create second object of Employee class"
emp2 = Employee("Manni", 5000)
emp1.displayEmployee()
emp2.displayEmployee()
print ("Total Employee %d" % Employee.empCount)

Quando o código acima é executado, ele produz o seguinte resultado -

Name :  Zara ,Salary:  2000
Name :  Manni ,Salary:  5000
Total Employee 2

Você pode adicionar, remover ou modificar atributos de classes e objetos a qualquer momento -

emp1.salary = 7000  # Add an 'salary' attribute.
emp1.name = 'xyz'  # Modify 'age' attribute.
del emp1.salary  # Delete 'age' attribute.

Em vez de usar as instruções normais para acessar atributos, você pode usar as seguintes funções -

  • o getattr(obj, name[, default]) - para acessar o atributo do objeto.

  • o hasattr(obj,name) - para verificar se um atributo existe ou não.

  • o setattr(obj,name,value)- para definir um atributo. Se o atributo não existir, ele será criado.

  • o delattr(obj, name) - para excluir um atributo.

hasattr(emp1, 'salary')    # Returns true if 'salary' attribute exists
getattr(emp1, 'salary')    # Returns value of 'salary' attribute
setattr(emp1, 'salary', 7000) # Set attribute 'salary' at 7000
delattr(emp1, 'salary')    # Delete attribute 'salary'

Atributos de classe integrados

Cada classe Python segue seguindo atributos embutidos e eles podem ser acessados ​​usando o operador ponto como qualquer outro atributo -

  • __dict__ - Dicionário contendo o namespace da classe.

  • __doc__ - String de documentação de classe ou nenhum, se indefinido.

  • __name__ - Nome da classe.

  • __module__- Nome do módulo no qual a classe é definida. Este atributo é "__main__" no modo interativo.

  • __bases__ - Uma tupla possivelmente vazia contendo as classes base, na ordem de sua ocorrência na lista de classes base.

Para a classe acima, vamos tentar acessar todos esses atributos -

#!/usr/bin/python3

class Employee:
   'Common base class for all employees'
   empCount = 0

   def __init__(self, name, salary):
      self.name = name
      self.salary = salary
      Employee.empCount += 1
   
   def displayCount(self):
     print ("Total Employee %d" % Employee.empCount)

   def displayEmployee(self):
      print ("Name : ", self.name,  ", Salary: ", self.salary)

emp1 = Employee("Zara", 2000)
emp2 = Employee("Manni", 5000)
print ("Employee.__doc__:", Employee.__doc__)
print ("Employee.__name__:", Employee.__name__)
print ("Employee.__module__:", Employee.__module__)
print ("Employee.__bases__:", Employee.__bases__)
print ("Employee.__dict__:", Employee.__dict__ )

Quando o código acima é executado, ele produz o seguinte resultado -

Employee.__doc__: Common base class for all employees
Employee.__name__: Employee
Employee.__module__: __main__
Employee.__bases__: (<class 'object'>,)
Employee.__dict__: {
   'displayCount': <function Employee.displayCount at 0x0160D2B8>, 
   '__module__': '__main__', '__doc__': 'Common base class for all employees', 
   'empCount': 2, '__init__': 
   <function Employee.__init__ at 0x0124F810>, 'displayEmployee': 
   <function Employee.displayEmployee at 0x0160D300>,
   '__weakref__': 
   <attribute '__weakref__' of 'Employee' objects>, '__dict__': 
   <attribute '__dict__' of 'Employee' objects>
}

Destruindo objetos (coleta de lixo)

O Python exclui objetos desnecessários (tipos internos ou instâncias de classe) automaticamente para liberar espaço de memória. O processo pelo qual o Python recupera periodicamente blocos de memória que não estão mais em uso é denominado coleta de lixo.

O coletor de lixo do Python é executado durante a execução do programa e é disparado quando a contagem de referência de um objeto chega a zero. A contagem de referência de um objeto muda conforme o número de aliases que apontam para ele muda.

A contagem de referência de um objeto aumenta quando ele recebe um novo nome ou é colocado em um contêiner (lista, tupla ou dicionário). A contagem de referência do objeto diminui quando ele é excluído com del , sua referência é reatribuída ou sua referência sai do escopo. Quando a contagem de referência de um objeto chega a zero, o Python a coleta automaticamente.

a = 40      # Create object <40>
b = a       # Increase ref. count  of <40> 
c = [b]     # Increase ref. count  of <40> 

del a       # Decrease ref. count  of <40>
b = 100     # Decrease ref. count  of <40> 
c[0] = -1   # Decrease ref. count  of <40>

Normalmente, você não notará quando o coletor de lixo destrói uma instância órfã e recupera seu espaço. No entanto, uma classe pode implementar o método especial __del __ () , chamado de destruidor, que é invocado quando a instância está para ser destruída. Este método pode ser usado para limpar quaisquer recursos que não sejam de memória usados ​​por uma instância.

Exemplo

Este destruidor __del __ () imprime o nome da classe de uma instância que está prestes a ser destruída -

#!/usr/bin/python3

class Point:
   def __init__( self, x=0, y=0):
      self.x = x
      self.y = y
   def __del__(self):
      class_name = self.__class__.__name__
      print (class_name, "destroyed")

pt1 = Point()
pt2 = pt1
pt3 = pt1
print (id(pt1), id(pt2), id(pt3))   # prints the ids of the obejcts
del pt1
del pt2
del pt3

Quando o código acima é executado, ele produz o seguinte resultado -

140338326963984 140338326963984 140338326963984
Point destroyed

Note- Idealmente, você deve definir suas classes em um arquivo separado, então você deve importá-los em seu arquivo de programa principal usando a instrução de importação .

No exemplo acima, assumindo que a definição de uma classe Point está contida em point.py e não há nenhum outro código executável nele.

#!/usr/bin/python3
import point

p1 = point.Point()

Herança de classe

Em vez de começar do zero, você pode criar uma classe derivando-a de uma classe pré-existente listando a classe pai entre parênteses após o novo nome da classe.

A classe filha herda os atributos de sua classe pai e você pode usar esses atributos como se estivessem definidos na classe filha. Uma classe filha também pode substituir membros de dados e métodos do pai.

Sintaxe

As classes derivadas são declaradas de maneira muito semelhante à classe pai; no entanto, uma lista de classes base para herdar é fornecida após o nome da classe -

class SubClassName (ParentClass1[, ParentClass2, ...]):
   'Optional class documentation string'
   class_suite

Exemplo

#!/usr/bin/python3

class Parent:        # define parent class
   parentAttr = 100
   def __init__(self):
      print ("Calling parent constructor")

   def parentMethod(self):
      print ('Calling parent method')

   def setAttr(self, attr):
      Parent.parentAttr = attr

   def getAttr(self):
      print ("Parent attribute :", Parent.parentAttr)

class Child(Parent): # define child class
   def __init__(self):
      print ("Calling child constructor")

   def childMethod(self):
      print ('Calling child method')

c = Child()          # instance of child
c.childMethod()      # child calls its method
c.parentMethod()     # calls parent's method
c.setAttr(200)       # again call parent's method
c.getAttr()          # again call parent's method

Quando o código acima é executado, ele produz o seguinte resultado -

Calling child constructor
Calling child method
Calling parent method
Parent attribute : 200

De forma semelhante, você pode conduzir uma classe a partir de várias classes pai da seguinte maneira -

class A:        # define your class A
.....

class B:         # define your calss B
.....

class C(A, B):   # subclass of A and B
.....

Você pode usar as funções issubclass () ou isinstance () para verificar os relacionamentos de duas classes e instâncias.

  • o issubclass(sub, sup) a função booleana retorna True, se a subclasse fornecida sub é de fato uma subclasse da superclasse sup.

  • o isinstance(obj, Class)a função booleana retorna True, se obj é uma instância da classe Class ou é uma instância de uma subclasse de Class

Métodos de substituição

Você sempre pode substituir seus métodos de classe pai. Uma razão para sobrescrever os métodos parent é que você pode desejar funcionalidades especiais ou diferentes em sua subclasse.

Exemplo

#!/usr/bin/python3

class Parent:        # define parent class
   def myMethod(self):
      print ('Calling parent method')

class Child(Parent): # define child class
   def myMethod(self):
      print ('Calling child method')

c = Child()          # instance of child
c.myMethod()         # child calls overridden method

Quando o código acima é executado, ele produz o seguinte resultado -

Calling child method

Métodos de sobrecarga de base

A tabela a seguir lista algumas funcionalidades genéricas que você pode substituir em suas próprias classes -

Sr. Não. Método, descrição e chamada de amostra
1

__init__ ( self [,args...] )

Construtor (com quaisquer argumentos opcionais)

Chamada de amostra: obj = className (args)

2

__del__( self )

Destruidor, apaga um objeto

Amostra de chamada: del obj

3

__repr__( self )

Representação de string avaliável

Chamada de amostra: repr (obj)

4

__str__( self )

Representação de string para impressão

Amostra de chamada: str (obj)

5

__cmp__ ( self, x )

Comparação de objetos

Chamada de amostra: cmp (obj, x)

Operadores de sobrecarga

Suponha que você tenha criado uma classe Vector para representar vetores bidimensionais. O que acontece quando você usa o operador de adição para adicioná-los? O mais provável é que Python grite com você.

Você poderia, no entanto, definir o método __add__ em sua classe para realizar a adição de vetores e, em seguida, o operador mais se comportaria de acordo com a expectativa -

Exemplo

#!/usr/bin/python3

class Vector:
   def __init__(self, a, b):
      self.a = a
      self.b = b

   def __str__(self):
      return 'Vector (%d, %d)' % (self.a, self.b)
   
   def __add__(self,other):
      return Vector(self.a + other.a, self.b + other.b)

v1 = Vector(2,10)
v2 = Vector(5,-2)
print (v1 + v2)

Quando o código acima é executado, ele produz o seguinte resultado -

Vector(7,8)

Ocultação de dados

Os atributos de um objeto podem ou não ser visíveis fora da definição da classe. Você precisa nomear os atributos com um prefixo de sublinhado duplo e esses atributos não ficarão diretamente visíveis para os de fora.

Exemplo

#!/usr/bin/python3

class JustCounter:
   __secretCount = 0
  
   def count(self):
      self.__secretCount += 1
      print (self.__secretCount)

counter = JustCounter()
counter.count()
counter.count()
print (counter.__secretCount)

Quando o código acima é executado, ele produz o seguinte resultado -

1
2
Traceback (most recent call last):
   File "test.py", line 12, in <module>
      print counter.__secretCount
AttributeError: JustCounter instance has no attribute '__secretCount'

Python protege esses membros alterando internamente o nome para incluir o nome da classe. Você pode acessar atributos como object._className__attrName . Se você substituir sua última linha como a seguir, isso funcionará para você -

.........................
print (counter._JustCounter__secretCount)

Quando o código acima é executado, ele produz o seguinte resultado -

1
2
2