Python orientado a objetos - recursos avançados

Neste, veremos alguns dos recursos avançados que o Python fornece

Sintaxe central em nosso design de classe

Nisto veremos como o Python nos permite tirar proveito dos operadores em nossas classes. Python é em grande parte objetos e métodos chamam objetos e isso continua mesmo quando está oculto por alguma sintaxe conveniente.

>>> var1 = 'Hello'
>>> var2 = ' World!'
>>> var1 + var2
'Hello World!'
>>>
>>> var1.__add__(var2)
'Hello World!'
>>> num1 = 45
>>> num2 = 60
>>> num1.__add__(num2)
105
>>> var3 = ['a', 'b']
>>> var4 = ['hello', ' John']
>>> var3.__add__(var4)
['a', 'b', 'hello', ' John']

Então, se tivermos que adicionar o método mágico __add__ às nossas próprias classes, poderíamos fazer isso também. Vamos tentar fazer isso.

Temos uma classe chamada Sumlist que tem um construtor __init__ que leva list como um argumento chamado my_list.

class SumList(object):
   def __init__(self, my_list):
      self.mylist = my_list
   def __add__(self, other):
     new_list = [ x + y for x, y in zip(self.mylist, other.mylist)]

     return SumList(new_list)
   
   def __repr__(self):
      return str(self.mylist)

aa = SumList([3,6, 9, 12, 15])

bb = SumList([100, 200, 300, 400, 500])
cc = aa + bb # aa.__add__(bb)
print(cc) # should gives us a list ([103, 206, 309, 412, 515])

Resultado

[103, 206, 309, 412, 515]

Mas existem muitos métodos que são gerenciados internamente por outros métodos mágicos. Abaixo estão alguns deles,

'abc' in var # var.__contains__('abc')
var == 'abc' # var.__eq__('abc')
var[1] # var.__getitem__(1)
var[1:3] # var.__getslice__(1, 3)
len(var) # var.__len__()
print(var) # var.__repr__()

Herdando de tipos integrados

As classes também podem herdar de tipos integrados, o que significa que herda de qualquer tipo integrado e aproveita todas as funcionalidades lá encontradas.

No exemplo a seguir, estamos herdando do dicionário, mas então estamos implementando um de seus métodos __setitem__. Este (setitem) é invocado quando definimos a chave e o valor no dicionário. Como esse é um método mágico, ele será chamado implicitamente.

class MyDict(dict):

   def __setitem__(self, key, val):
      print('setting a key and value!')
      dict.__setitem__(self, key, val)

dd = MyDict()
dd['a'] = 10
dd['b'] = 20

for key in dd.keys():
   print('{0} = {1}'.format(key, dd[key]))

Resultado

setting a key and value!
setting a key and value!
a = 10
b = 20

Vamos estender nosso exemplo anterior, abaixo chamamos dois métodos mágicos chamados __getitem__ e __setitem__ melhor invocados quando lidamos com índice de lista.

# Mylist inherits from 'list' object but indexes from 1 instead for 0!
class Mylist(list): # inherits from list
   def __getitem__(self, index):
      if index == 0:
         raise IndexError
      if index > 0:
         index = index - 1
         return list.__getitem__(self, index) # this method is called when

# we access a value with subscript like x[1]
   def __setitem__(self, index, value):
      if index == 0:
         raise IndexError
      if index > 0:
      index = index - 1
      list.__setitem__(self, index, value)

x = Mylist(['a', 'b', 'c']) # __init__() inherited from builtin list

print(x) # __repr__() inherited from builtin list

x.append('HELLO'); # append() inherited from builtin list

print(x[1]) # 'a' (Mylist.__getitem__ cutomizes list superclass
               # method. index is 1, but reflects 0!

print (x[4]) # 'HELLO' (index is 4 but reflects 3!

Resultado

['a', 'b', 'c']
a
HELLO

No exemplo acima, definimos uma lista de três itens em Mylist e implicitamente o método __init__ é chamado e quando imprimimos o elemento x, obtemos a lista de três itens (['a', 'b', 'c']). Em seguida, acrescentamos outro elemento a esta lista. Mais tarde, pedimos o índice 1 e o índice 4. Mas se você vir a saída, estamos obtendo o elemento do (índice-1) que pedimos. Como sabemos, a indexação de lista começa em 0, mas aqui a indexação começa em 1 (é por isso que estamos obtendo o primeiro item da lista).

Convenções de Nomenclatura

Nele, veremos os nomes que usaremos para variáveis, especialmente variáveis ​​privadas e convenções usadas por programadores Python em todo o mundo. Embora as variáveis ​​sejam designadas como privadas, não há privacidade no Python e isso por design. Como qualquer outra linguagem bem documentada, Python tem convenções de nomenclatura e estilo que promove, embora não as imponha. Existe um guia de estilo escrito por “Guido van Rossum” the originator of Python, that describe the best practices and use of name and is called PEP8. Here is the link for this, https://www.python.org/dev/peps/pep-0008/

PEP significa proposta de aprimoramento do Python e é uma série de documentação distribuída entre a comunidade Python para discutir as alterações propostas. Por exemplo, é recomendado todos,

  • Nomes de módulos - all_lower_case
  • Nomes de classes e nomes de exceção - CamelCase
  • Nomes globais e locais - all_lower_case
  • Funções e nomes de métodos - all_lower_case
  • Constantes - ALL_UPPER_CASE

Estas são apenas recomendações, você pode variar se quiser. Mas, como a maioria dos desenvolvedores segue essas recomendações, talvez seu código seja menos legível.

Por que se conformar com a convenção?

Podemos seguir a recomendação PEP que ela nos permite obter,

  • Mais familiar para a grande maioria dos desenvolvedores
  • Mais claro para a maioria dos leitores de seu código.
  • Irá corresponder ao estilo de outros colaboradores que trabalham na mesma base de código.
  • Marca de um desenvolvedor de software profissional
  • Todo mundo vai aceitar você.

Nomenclatura de variável - 'Pública' e 'Privada'

Em Python, quando estamos lidando com módulos e classes, designamos algumas variáveis ​​ou atributos como privados. Em Python, não existe uma variável de instância “privada” que não pode ser acessada exceto dentro de um objeto. Privado significa simplesmente que eles não devem ser usados ​​pelos usuários do código, em vez disso, eles devem ser usados ​​internamente. Em geral, uma convenção está sendo seguida pela maioria dos desenvolvedores Python, ou seja, um nome prefixado com um sublinhado, por exemplo. _attrval (exemplo abaixo) deve ser tratado como uma parte não pública da API ou de qualquer código Python, seja uma função, um método ou um membro de dados. Abaixo está a convenção de nomenclatura que seguimos,

  • Atributos ou variáveis ​​públicas (destinados a serem usados ​​pelo importador deste módulo ou usuário desta classe) -regular_lower_case

  • Atributos ou variáveis ​​privadas (uso interno pelo módulo ou classe) -_single_leading_underscore

  • Atributos privados que não devem ser subclassificados -__double_leading_underscore

  • Atributos mágicos -__double_underscores__(use-os, não os crie)

class GetSet(object):

   instance_count = 0 # public
   
   __mangled_name = 'no privacy!' # special variable

   def __init__(self, value):
      self._attrval = value # _attrval is for internal use only
      GetSet.instance_count += 1

   @property
   def var(self):
      print('Getting the "var" attribute')
      return self._attrval

   @var.setter
   def var(self, value):
      print('setting the "var" attribute')
      self._attrval = value

   @var.deleter
   def var(self):
      print('deleting the "var" attribute')
      self._attrval = None

cc = GetSet(5)
cc.var = 10 # public name
print(cc._attrval)
print(cc._GetSet__mangled_name)

Resultado

setting the "var" attribute
10
no privacy!