Python orientado a objetos: funciones avanzadas

En esto, veremos algunas de las características avanzadas que proporciona Python.

Sintaxis principal en el diseño de nuestra clase

En esto veremos cómo Python nos permite aprovechar los operadores en nuestras clases. Python es en gran parte objetos y los métodos llaman a objetos y esto continúa incluso cuando está oculto por alguna sintaxis 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']

Entonces, si tenemos que agregar el método mágico __add__ a nuestras propias clases, ¿podríamos hacerlo también? Intentemos hacer eso.

Tenemos una clase llamada Sumlist que tiene un constructor __init__ que toma list como un argumento llamado 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])

Salida

[103, 206, 309, 412, 515]

Pero hay muchos métodos que son administrados internamente por otros métodos mágicos. A continuación hay algunos de ellos,

'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__()

Herencia de tipos integrados

Las clases también pueden heredar de los tipos integrados, esto significa que hereda de cualquier integrado y aprovechar todas las funciones que se encuentran allí.

En el siguiente ejemplo, heredamos del diccionario, pero luego implementamos uno de sus métodos __setitem__. Este (setitem) se invoca cuando establecemos la clave y el valor en el diccionario. Como se trata de un método mágico, se llamará implícitamente.

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]))

Salida

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

Extendamos nuestro ejemplo anterior, a continuación hemos llamado a dos métodos mágicos llamados __getitem__ y __setitem__ mejor invocados cuando tratamos con el í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!

Salida

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

En el ejemplo anterior, establecemos una lista de tres elementos en Mylist e implícitamente se llama al método __init__ y cuando imprimimos el elemento x, obtenemos la lista de tres elementos (['a', 'b', 'c']). Luego agregamos otro elemento a esta lista. Más tarde pedimos el índice 1 y el índice 4. Pero si ve el resultado, estamos obteniendo el elemento del (índice-1) que hemos pedido. Como sabemos, la indexación de la lista comienza desde 0, pero aquí la indexación comienza desde 1 (es por eso que obtenemos el primer elemento de la lista).

Convenciones de nombres

En esto, veremos los nombres que usaremos para las variables, especialmente las variables privadas y las convenciones utilizadas por los programadores de Python en todo el mundo. Aunque las variables están designadas como privadas, no hay privacidad en Python y esto por diseño. Como cualquier otro lenguaje bien documentado, Python tiene convenciones de nomenclatura y estilo que promueve aunque no las hace cumplir. Hay una guía de estilo escrita 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 propuesta de mejora de Python y es una serie de documentación que se distribuye entre la comunidad de Python para discutir los cambios propuestos. Por ejemplo se recomienda todo,

  • Nombres de módulo: all_lower_case
  • Nombres de clases y nombres de excepciones - CamelCase
  • Nombres globales y locales - all_lower_case
  • Funciones y nombres de métodos: all_lower_case
  • Constantes: ALL_UPPER_CASE

Estas son solo la recomendación, puede variar si lo desea. Pero como la mayoría de los desarrolladores siguen estas recomendaciones, me parece que su código es menos legible.

¿Por qué ajustarse a la convención?

Podemos seguir la recomendación de PEP que nos permita obtener,

  • Más familiar para la gran mayoría de desarrolladores
  • Más claro para la mayoría de los lectores de su código.
  • Coincidirá con el estilo de otros contribuyentes que trabajen en la misma base de código.
  • Marca de un desarrollador de software profesional
  • Todos te aceptarán.

Nomenclatura de variable: 'Pública' y 'Privada'

En Python, cuando se trata de módulos y clases, designamos algunas variables o atributos como privados. En Python, no existe una variable de instancia "Privada" a la que no se pueda acceder excepto dentro de un objeto. Privado simplemente significa que simplemente no están destinados a ser utilizados por los usuarios del código, sino que están destinados a ser utilizados internamente. En general, la mayoría de los desarrolladores de Python siguen una convención, es decir, un nombre precedido por un guión bajo, por ejemplo. _attrval (ejemplo a continuación) debe tratarse como una parte no pública de la API o cualquier código Python, ya sea una función, un método o un miembro de datos. A continuación se muestra la convención de nomenclatura que seguimos,

  • Atributos o variables públicos (destinados a ser utilizados por el importador de este módulo o el usuario de esta clase) -regular_lower_case

  • Atributos o variables privados (uso interno del módulo o clase) -_single_leading_underscore

  • Atributos privados que no deberían ser subclasificados -__double_leading_underscore

  • Atributos mágicos -__double_underscores__(úsalas, no las crees)

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)

Salida

setting the "var" attribute
10
no privacy!