<jupyter_start><jupyter_text>![imagenes](logo.png)  Atributos, métodos especiales y polimorfismo. En Python se pueden identificar a los atributos y métodos especiales (magic methods) de un objeto por que su nombre está encerrado entre dobles guiones bajos "*\_\_*".Estos atributos se utilizan para definir comportamientos básicos de un objeto y son comunes con otras clases.Puede consultar diversos métodos especiales [en este sitio](http://minhhh.github.io/posts/a-guide-to-pythons-magic-methods).A continuación se muestran algunos de los atributos y métodos especiales más representativos.<jupyter_code>dir(1)
dir(str)<jupyter_output><empty_output><jupyter_text>Métodos de operadores.Python es un lenguaje de muy alto nivel de abstracción que por ende, realiza operaciones de manera distinta a otros. Uno de esos casos es cuando el intérprete identifica un operador.Cuando el intérprete identifica a un operador en una expresión, no realiza la operación relacionada con el operador directamente, sino que invoca un método especial.Por ejemplo, el método *\_\_add\_\_()* se ejecuta cuando el intérprete se encuentra a la izquierda del símbolo de suma "*+*".**Ejemplo:**<jupyter_code>dir(object)
class SuperficieCuadrada():
    '''Realiza operaciones con superficies de cuadrados.'''
    lado = 1
    
    def superficie(self):
        '''Calcula la superficie de un cuadrado.'''
        return self.lado ** 2
    
    def __add__(self, elemento):
        '''Realiza operaciones de suma.'''
        if type(elemento) is SuperficieCuadrada:
            return self.superficie() + elemento.superficie()
        elif type(elemento) in (int, float):
            return self.superficie() + elemento
        else:
            raise NotImplementedError("No sé qué hacer")
cuadros = (SuperficieCuadrada(), SuperficieCuadrada())
cuadros[0].lado = 25
cuadros[1].lado = 12
cuadros[0] + cuadros[1]
cuadros[0].__add__(cuadros[1])
cuadros[0] + 12.34
cuadros[0].__add__(12.34)
12.34 + cuadros[0]
12.34.__add__(cuadros[0])
cuadros[1] + 2j
2j + cuadros[1]
cuadros[1].__add__(2j)
12 + cuadros[0]
sum(cuadros)<jupyter_output><empty_output><jupyter_text>Métodos de operador recíprocos.En el ejemplo previo, el orden del operador tiene relevancia, ya que los objetos de tipo _int_ y de tipo _float_, los cuales se invocan primero por estar a la izquierda del operador, no cuentan con una implementación de *\_\_add\_\_* para la clase *SuperficieCuadrada*.Lo mismo ocurre con la función _sum()_, la cual comienza a realizar la suma desde _0_, el cual es de tipo _int_.Cuando un objeto de tipo numérico no encuentra una implementación adecuada, busca un método recíproco en el objeto a la derecha del operador. En este caso busca al método *\_\_radd\_\_()*.Los métodos de operador recíprocos de son:* *\_\_radd\_\_()** *\_\_rdiv\_\_()** *\_\_rmod\_\_()** *\_\_rmul\_\_()** *\_\_rsub\_\_()***Ejemplo:**<jupyter_code>class SuperficieCuadrada():
    '''Realiza operaciones con superficies de cuadrados.'''
    lado = 1
    
    def superficie(self):
        '''Calcula la superfice de un cuadrado.'''
        return self.lado ** 2
    
    def __add__(self, elemento):
        '''Realiza operaciones de suma.'''
        if type(elemento) is SuperficieCuadrada:
            return self.superficie() + elemento.superficie()
        elif type(elemento) in (int, float):
            return self.superficie() + elemento
        else:
            raise NotImplementedError("No sé qué hacer.")
            
    def __radd__(self, elemento):
        '''Realiza operaciones de suma cuando el otro objeto no la tiene implementada.'''
        return self.__add__(elemento)
cuadrados = [SuperficieCuadrada(), SuperficieCuadrada(), SuperficieCuadrada()]
cuadrados[0].lado = 13.5
cuadrados[1].lado = 50.33
cuadrados[2].lado = 23.1
12.5 + cuadrados[1]
cuadrados[1] + 21
sum(cuadrados)
cuadrados[2] + 15.4
1j + cuadrados[0]<jupyter_output><empty_output><jupyter_text>Métodos de inicio y finalización de objetos. El método *\_\_new\_\_()*.Es el método que se ejecuta para crear una instancia del objeto. Se utiliza de forma muy esporádica y en la mayoría de los casos para modificar clases de objetos inmutables. El estudio de este método queda fuera del alcance de este curso introductorio. El método *\_\_init\_\_()*.Es el primer método que se ejecuta un vez instanciado un objeto. Los argumentos que se ingresan dentro del paréntesis al crear un objetos son transferidos a este método.**Ejemplo:**<jupyter_code>class PoblacionCensada():
    '''Crea censos de población. '''
    
    def __init__(self, nombre, numero=0):
        
        print("Se ha creado la población {} con {} habitantes.".format(nombre, numero))
        self.nombre = nombre
        self.numero = numero
urbana = PoblacionCensada("Texcoco", 20000)
urbana.nombre
urbana.numero<jupyter_output><empty_output><jupyter_text>El método *\_\_init\_\_()* se utiliza generalmente para inicializar el estado de un objeto.  El método *\_\_del\_\_()*.Es el método que se ejecuta cuando un objeto es desechado.**Ejemplo:**<jupyter_code>class Ente():
    '''Ejemplifica el ciclo de vida de un objeto.'''
    
    def __init__(self, nombre='Juan'):
        print('Hola. Mi nombre es {}.'.format(nombre))
        self.nombre = nombre
    
    def __del__(self):
        print ('{} ha dejado de existir.'.format(self.nombre))
entidades = [Ente('Hugo'), Ente('Paco'), Ente('Luis')]
del entidades[1]
del entidades
Ente('Jose')<jupyter_output><empty_output><jupyter_text>Atributos de información. El atributo *\_\_doc\_\_* .Es el atributo que contiene la docstring que se utiliza para documentar al código. El atributo *\_\_class\_\_*.Regresa la clase de la que ha sido instanciado un objeto. El método *\_\_dir\_\_()*.Regresa la lista de atributos contenidos en un objeto.**Ejemplos:**<jupyter_code>class Ente():
    '''Ejemplifica el ciclo de vida de un objeto.'''
    
    def __init__(self, nombre='Juan'):
        print('Hola. Mi nombre es {}.'.format(nombre))
        self.nombre = nombre
    
    def __del__(self):
        print ('{} ha dejado de existir.'.format(self.nombre))
entidad = Ente()
entidad.__doc__
entidad.__class__
entidad.__dir__()<jupyter_output><empty_output><jupyter_text>Métodos de conversión. El método ```__repr__()```.Se utiliza para desplegar información que será accedida y despelgada por el intérprete. El método *\_\_str\_\_()*Se utiliza para desplegar información que será convertida al tipo _str_. La función _str()_ es la implementación de este método. El método *\_\_format\_\_()*Se utiliza para desplegar información que se ajuste a el método *format* de los objetos de tipo _str_. El método *\_\_int\_\_()*Se utiliza para desplegar información que será convertida al tipo _int_.  La función _int()_ es la implementación de este método. El método *\_\_float\_\_()*Se utiliza para desplegar información que será convertida al tipo _float_.  La función _float()_ es la implementación de este método. El método *\_\_bool\_\_()*Se utiliza para desplegar información que será convertida al tipo _bool_.  La función _bool()_ es la implementación de este método.**Ejemplo:**<jupyter_code>class Ente():
    '''Ejemplifica el ciclo de vida de un objeto.'''
    
    def __init__(self, nombre='Juan'):
        print('Hola. Mi nombre es {}.'.format(nombre))
        self.nombre = nombre
    
    def __del__(self):
        print ('{} ha dejado de existir.'.format(self.nombre))
    
    def __str__(self):
        '''Regresa el contenido del atributo nombre convertido a mayúsculas.'''
        return self.nombre.upper()
    
    def __bool__(self):
        '''Regresa el resultado de evaluar si el atributo nombre tiene en mayúsculas 
        la primera letra de cada palabra que contiene.''' 
        return self.nombre.istitle()
    
    def __repr__(self):
        '''Regresa la representación del objeto.'''
        return "Objeto de la clase Ente"
ente = Ente('<NAME>')
ente
ente.__repr__()
str(ente)
print(ente)
ente.__str__()
bool(ente)
ente.__bool__()<jupyter_output><empty_output><jupyter_text>El método *\_\_iter\_\_()*Convierte a las instancias de la clase en objetos iterables.**Ejemplo:**<jupyter_code>class Ente():
    '''Ejemplifica el ciclo de vida de un objeto.'''
    
    def __init__(self, nombre='Juan'):
        print('Hola. Mi nombre es {}.'.format(nombre))
        self.nombre = nombre
    
    def __del__(self):
        print ('{} ha dejado de existir.'.format(self.nombre))
    
    def __str__(self):
        '''Regresa el contenido del atributo nombre convertido a mayúsculas.'''
        return self.nombre.upper()
    
    def __bool__(self):
        '''Regresa el resultado de evaluar si el atributo nombre tiene en mayúsculas 
        la primera letra de cada palabra que contiene.''' 
        return self.nombre.istitle()
    
    def __iter__(self):
        '''Crea un iterador que regresa cada letra contenida en el atributo nombre convertida 
        a minúscuclas.'''
        for letra in self.nombre:
            yield letra.lower() 
persona = Ente('JAVIER')
for letra in persona:
    print(letra)
iterador = iter(persona)
type(iterador)
iterador.__next__()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10911074740861974, 'fraction_numerical': 0.00851063829787234, 'mean_word_length': 3.7296181630546954, 'pattern_counts': {'":': 0, '<': 34, '<?xml version=': 0, '>': 34, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '8', 'dir': 'jupyter-structured-clean-dedup', 'id': '547993', 'license': 'MIT', 'n_tokens_mistral': 3146, 'n_tokens_neox': 3156, 'n_words': 969, 'path': 'C03_atributos_y_metodos_especiales.ipynb', 'repo_name': 'scidatmath2020/Programacion_orientada_objetos_Python', 'size': '9169'}