Python orientado a objetos - blocos de construção

Neste capítulo, discutiremos os termos orientados a objetos e conceitos de programação em detalhes. A classe é apenas uma fábrica para uma instância. Esta fábrica contém o blueprint que descreve como fazer as instâncias. Uma instância ou objeto é construído a partir da classe. Na maioria dos casos, podemos ter mais de uma instância de uma classe. Cada instância tem um conjunto de atributos e esses atributos são definidos em uma classe, portanto, espera-se que cada instância de uma classe específica tenha os mesmos atributos.

Pacotes de classe: comportamento e estado

Uma classe permitirá que você agrupe o comportamento e o estado de um objeto. Observe o diagrama a seguir para melhor compreensão -

Os pontos a seguir são dignos de nota ao discutir pacotes de classe -

  • A palavra behavior é idêntico a function - é um pedaço de código que faz algo (ou implementa um comportamento)

  • A palavra state é idêntico a variables - é um lugar para armazenar valores dentro de uma classe.

  • Quando afirmamos o comportamento e o estado de uma classe juntos, isso significa que uma classe empacota funções e variáveis.

As classes têm métodos e atributos

Em Python, a criação de um método define o comportamento de uma classe. A palavra método é o nome OOP dado a uma função que é definida dentro de uma classe. Para resumir -

  • Class functions - é sinônimo de methods

  • Class variables - é sinônimo de name attributes.

  • Class - um projeto para uma instância com comportamento exato.

  • Object - uma das instâncias da classe, executa a funcionalidade definida na classe.

  • Type - indica a classe à qual a instância pertence

  • Attribute - Qualquer valor de objeto: object.attribute

  • Method - um “atributo chamável” definido na classe

Observe o seguinte trecho de código, por exemplo -

var = “Hello, John”
print( type (var)) # ‘str’> or <class 'str'>
print(var.upper()) # upper() method is called, HELLO, JOHN

Criação e Instanciação

O código a seguir mostra como criar nossa primeira classe e, em seguida, sua instância.

class MyClass(object):
   pass
# Create first instance of MyClass
this_obj = MyClass()
print(this_obj)
# Another instance of MyClass
that_obj = MyClass()
print (that_obj)

Aqui criamos uma classe chamada MyClasse que não faz nenhuma tarefa. O argumentoobject dentro MyClass classe envolve herança de classe e será discutida em capítulos posteriores. pass no código acima indica que este bloco está vazio, ou seja, é uma definição de classe vazia.

Vamos criar uma instância this_obj do MyClass() aula e imprima-a como mostrado -

<__main__.MyClass object at 0x03B08E10>
<__main__.MyClass object at 0x0369D390>

Aqui, criamos uma instância de MyClass.O código hexadecimal se refere ao endereço onde o objeto está sendo armazenado. Outra instância está apontando para outro endereço.

Agora vamos definir uma variável dentro da classe MyClass() e obter a variável da instância dessa classe, conforme mostrado no código a seguir -

class MyClass(object):
   var = 9

# Create first instance of MyClass
this_obj = MyClass()
print(this_obj.var)

# Another instance of MyClass

that_obj = MyClass()
print (that_obj.var)

Resultado

Você pode observar a seguinte saída ao executar o código fornecido acima -

9
9

Como a instância sabe de qual classe é instanciada, quando solicitada por um atributo de uma instância, a instância procura o atributo e a classe. Isso é chamado deattribute lookup.

Métodos de Instância

Uma função definida em uma classe é chamada de method.Um método de instância requer uma instância para chamá-lo e não requer nenhum decorador. Ao criar um método de instância, o primeiro parâmetro é sempreself. Embora possamos chamá-lo (self) por qualquer outro nome, é recomendável usar self, pois é uma convenção de nomenclatura.

class MyClass(object):
   var = 9
   def firstM(self):
      print("hello, World")
obj = MyClass()
print(obj.var)
obj.firstM()

Resultado

Você pode observar a seguinte saída ao executar o código fornecido acima -

9
hello, World

Observe que no programa acima, definimos um método com self como argumento. Mas não podemos chamar o método, pois não declaramos nenhum argumento para ele.

class MyClass(object):
   def firstM(self):
      print("hello, World")
      print(self)
obj = MyClass()
obj.firstM()
print(obj)

Resultado

Você pode observar a seguinte saída ao executar o código fornecido acima -

hello, World
<__main__.MyClass object at 0x036A8E10>
<__main__.MyClass object at 0x036A8E10>

Encapsulamento

O encapsulamento é um dos fundamentos da OOP. OOP nos permite ocultar a complexidade do funcionamento interno do objeto, o que é vantajoso para o desenvolvedor das seguintes maneiras -

  • Simplifica e torna mais fácil entender o uso de um objeto sem conhecer os internos.

  • Qualquer mudança pode ser facilmente administrada.

A programação orientada a objetos depende muito do encapsulamento. Os termos encapsulamento e abstração (também chamados de ocultação de dados) são freqüentemente usados ​​como sinônimos. Eles são quase sinônimos, pois a abstração é obtida por meio do encapsulamento.

O encapsulamento nos fornece o mecanismo de restringir o acesso a alguns dos componentes do objeto, isso significa que a representação interna de um objeto não pode ser vista de fora da definição do objeto. O acesso a esses dados normalmente é obtido por meio de métodos especiais -Getters e Setters.

Esses dados são armazenados em atributos de instância e podem ser manipulados de qualquer lugar fora da classe. Para protegê-lo, esses dados só devem ser acessados ​​usando métodos de instância. O acesso direto não deve ser permitido.

class MyClass(object):
   def setAge(self, num):
      self.age = num

   def getAge(self):
      return self.age

zack = MyClass()
zack.setAge(45)
print(zack.getAge())

zack.setAge("Fourty Five")
print(zack.getAge())

Resultado

Você pode observar a seguinte saída ao executar o código fornecido acima -

45
Fourty Five

Os dados devem ser armazenados apenas se estiverem corretos e válidos, usando construções de tratamento de exceção. Como podemos ver acima, não há restrição na entrada do usuário para o método setAge (). Pode ser uma string, um número ou uma lista. Portanto, precisamos verificar o código acima para garantir que o armazenamento seja correto.

class MyClass(object):
   def setAge(self, num):
      self.age = num

   def getAge(self):
      return self.age
zack = MyClass()
zack.setAge(45)
print(zack.getAge())
zack.setAge("Fourty Five")
print(zack.getAge())

Construtor Init

O __initO método __ é implicitamente chamado assim que um objeto de uma classe é instanciado. Isso inicializará o objeto.

x = MyClass()

A linha de código mostrada acima criará uma nova instância e atribuirá este objeto à variável local x.

A operação de instanciação, isto é calling a class object, cria um objeto vazio. Muitas classes gostam de criar objetos com instâncias personalizadas para um estado inicial específico. Portanto, uma classe pode definir um método especial chamado '__init __ ()' como mostrado -

def __init__(self):
   self.data = []

O Python chama __init__ durante a instanciação para definir um atributo adicional que deve ocorrer quando uma classe é instanciada e que pode estar configurando alguns valores iniciais para aquele objeto ou executando uma rotina necessária na instanciação. Portanto, neste exemplo, uma nova instância inicializada pode ser obtida por -

x = MyClass()

O método __init __ () pode ter um ou vários argumentos para uma maior flexibilidade. O init significa inicialização, pois inicializa os atributos da instância. É chamado de construtor de uma classe.

class myclass(object):
   def __init__(self,aaa, bbb):
      self.a = aaa
      self.b = bbb

x = myclass(4.5, 3)
print(x.a, x.b)

Resultado

4.5 3

Atributos de classe

O atributo definido na classe é chamado de “atributos de classe” e os atributos definidos na função são chamados de “atributos de instância”. Durante a definição, esses atributos não são prefixados por self, pois são propriedade da classe e não de uma instância particular.

Os atributos da classe podem ser acessados ​​pela própria classe (className.attributeName), bem como pelas instâncias da classe (inst.attributeName). Portanto, as instâncias têm acesso aos atributos da instância e também aos atributos da classe.

>>> class myclass():
   age = 21
>>> myclass.age
21
>>> x = myclass()
>>> x.age
21
>>>

Um atributo de classe pode ser substituído em uma instância, embora não seja um bom método para quebrar o encapsulamento.

Há um caminho de pesquisa para atributos em Python. O primeiro é o método definido dentro da classe e, em seguida, a classe acima dele.

>>> class myclass(object):
   classy = 'class value'
>>> dd = myclass()
>>> print (dd.classy) # This should return the string 'class value'
class value
>>>
>>> dd.classy = "Instance Value"
>>> print(dd.classy) # Return the string "Instance Value"
Instance Value
>>>
>>> # This will delete the value set for 'dd.classy' in the instance.
>>> del dd.classy
>>> >>> # Since the overriding attribute was deleted, this will print 'class
value'.

>>> print(dd.classy)
class value
>>>

Estamos substituindo o atributo de classe 'classy' na instância dd. Quando é sobrescrito, o interpretador Python lê o valor sobrescrito. Mas, uma vez que o novo valor é excluído com 'del', o valor substituído não está mais presente na instância e, portanto, a pesquisa vai um nível acima e o obtém da classe.

Trabalhar com dados de classe e instância

Nesta seção, vamos entender como os dados da classe se relacionam com os dados da instância. Podemos armazenar dados em uma classe ou em uma instância. Quando projetamos uma classe, decidimos quais dados pertencem à instância e quais dados devem ser armazenados na classe geral.

Uma instância pode acessar os dados da classe. Se criarmos várias instâncias, essas instâncias podem acessar seus valores de atributos individuais, bem como os dados gerais da classe.

Assim, os dados de uma classe são os dados compartilhados entre todas as instâncias. Observe o código abaixo para melhor compreensão -

class InstanceCounter(object):
   count = 0 # class attribute, will be accessible to all instances
   def __init__(self, val):
      self.val = val
      InstanceCounter.count +=1 # Increment the value of class attribute, accessible through class name
# In above line, class ('InstanceCounter') act as an object
   def set_val(self, newval):
      self.val = newval

   def get_val(self):
      return self.val

   def get_count(self):
      return InstanceCounter.count
a = InstanceCounter(9)
b = InstanceCounter(18)
c = InstanceCounter(27)

for obj in (a, b, c):
   print ('val of obj: %s' %(obj.get_val())) # Initialized value ( 9, 18, 27)
   print ('count: %s' %(obj.get_count())) # always 3

Resultado

val of obj: 9
count: 3
val of obj: 18
count: 3
val of obj: 27
count: 3

Resumindo, os atributos de classe são iguais para todas as instâncias de classe, enquanto os atributos de instância são específicos para cada instância. Para duas instâncias diferentes, teremos dois atributos de instância diferentes.

class myClass:
   class_attribute = 99

   def class_method(self):
      self.instance_attribute = 'I am instance attribute'

print (myClass.__dict__)

Resultado

Você pode observar a seguinte saída ao executar o código fornecido acima -

{'__module__': '__main__', 'class_attribute': 99, 'class_method': 
      
       , '__dict__': 
       
        , '__weakref__': 
        
         , '__doc__': None} 
        
       
      

The instance attribute myClass.__dict__ as shown −

>>> a = myClass()
>>> a.class_method()
>>> print(a.__dict__)
{'instance_attribute': 'I am instance attribute'}