Python Básico
Carregando, aguarde alguns segundos.

8 - Classes

Classes são importantes estruturas declarativas de blocos do Python, usadas na construção da POO - Programação Orientada a Objetos (OOP - Object Oriented Programming), agrupando atributos e métodos relacionados entre si.

Na POO, Os dados de uma classe são os atributos e os comportamentos são métodos. Atributos são os correspondentes das variáveis na programação funcional, enquanto os métodos são os correspondentes das funções, quando pertencem a uma classe.

A classe é a representação de uma ou mais objetos instanciados (criados) a partir do construtor da classe.

As classes com atributos e métodos juntos definem um "tipo" de objeto, permitindo a criação de instâncias individuais de objetos da classe, compartilhando a mesma estrutura e comportamento, mas possuindo seus próprios dados.

Existem 2 tipos de atributos e 3 tipos de métodos de uma classe em Python:

  • Atributos de instância: atributos pertencentes a cada instância de objeto de classe.
  • Atributos de classe: atributos pertencentes a uma classe que são compartilhados entre todas as instâncias de objetos da classe.
  • Métodos de instância: métodos compartilhados por todas instâncias de objeto de classe, que têm o parâmetro self apontando o endereço na memória de cada instância de objeto.
  • Métodos de classe: métodos da classe, sendo acessada diretamente sem ser instanciada, que têm o parâmetro cls apontando o endereço na memória da classe, que é comum, acessada e compartilhada entre todas instâncias.
  • Métodos estáticos: métodos da classe, sendo acessada diretamente sem ser instanciada, que podem receber parâmetros mas não têm nem o parâmetro self nem o cls, que não podem acessar métodos de instância mas podem acessar e métodos os atributos de classe.
flowchart TD classe[Classe] declaracao[Declaração] metodos[Métodos] atributos[Atributos] I[Instância] classe-->declaracao classe-->I declaracao-->metodos declaracao-->atributos atributos-->|de|atrib_classe[Classe] atributos-->|de|atrib_instância[Instância] met_instancia-->especiais[Especiais] metodos-->|"@classmethod"|met_classe[Classe] metodos-->|de|met_instancia[Instância] metodos-->|"@staticmethod"|met_estaticos[Estáticos] especiais --> esp_construtor["Construtor()"] esp_construtor --> esp_const_init["__init__()"] especiais --> esp_atribuicao[Atribuição] esp_atribuicao-->esp_atrib_eq["__eq__()"] esp_atrib_eq-->esp_atrib_ne["__ne__()"] esp_atrib_ne-->esp_atrib_lt["__lt__()"] esp_atrib_lt-->esp_atrib_le["__le__()"] esp_atrib_le-->esp_atrib_gt["__gt__()"] esp_atrib_gt-->esp_atrib_ge["__ge__()"] especiais --> esp_compartimento[Compartimento] esp_compartimento-->esp_comp_inst_iter["__iter__()"] esp_comp_inst_iter-->esp_comp_inst_next["__next__()"] esp_comp_inst_next-->esp_comp_inst_len["__len__()"] esp_comp_inst_len-->esp_comp_inst_reversed["__reversed__()"] esp_comp_inst_reversed-->esp_comp_inst_contains["__contains__()"] esp_comp_inst_contains-->esp_comp_inst_getitem["__getitem__()"] esp_comp_inst_getitem-->esp_comp_inst_setitem["__setitem__()"] esp_comp_inst_setitem-->esp_comp_inst_delitem["__delitem__()"] especiais --> esp_aritmeticos[Aritméticos] esp_aritmeticos --> esp_arit_esq_dir[Esq_Dir] esp_arit_esq_dir-->esp_arit_add["__add__()"] esp_arit_add-->esp_arit_sub["__sub__()"] esp_arit_sub-->esp_arit_mul["__mul__()"] esp_arit_mul-->esp_arit_div["__div__()"] esp_arit_div-->esp_arit_mod["__mod__()"] esp_arit_mod-->esp_arit_floordiv["__floordiv__()"] esp_arit_floordiv-->esp_arit_truediv["__truediv__()"] esp_arit_truediv-->esp_arit_pow["__pow__()"] esp_aritmeticos --> esp_arit_dir_esq[Dir/Esq] esp_arit_dir_esq-->esp_arit_radd["__radd__()"] esp_arit_radd-->esp_arit_rsub["__rsub__()"] esp_arit_rsub-->esp_arit_rmul["__rmul__()"] esp_arit_rmul-->esp_arit_rdiv["__rdiv__()"] esp_arit_rdiv-->esp_arit_rmod["__rmod__()"] esp_arit_rmod-->esp_arit_rfloordiv["__rfloordiv__()"] esp_arit_rfloordiv-->esp_arit_rtruediv["__rtruediv__()"] esp_arit_rtruediv-->esp_rarit_pow["__rpow__()"] esp_aritmeticos --> esp_arit_atribuicao[Atribuição] esp_arit_atribuicao-->esp_arit_iadd["__iadd__()"] esp_arit_iadd-->esp_arit_isub["__isub__()"] esp_arit_isub-->esp_arit_imul["__imul__()"] esp_arit_imul-->esp_arit_idiv["__idiv__()"] esp_arit_idiv-->esp_arit_imod["__imod__()"] esp_arit_imod-->esp_arit_ifloordiv["__ifloordiv__()"] esp_arit_ifloordiv-->esp_arit_itruediv["__itruediv__()"] esp_arit_itruediv-->esp_arit_ipow["__ipow__()"] esp_aritmeticos --> esp_arit_unitario[Unitário] esp_arit_unitario-->esp_arit_neg["__neg__()"] esp_arit_neg-->esp_arit_pos["__pos__()"] esp_arit_pos-->esp_arit_abs["__abs__()"] esp_arit_abs-->esp_arit_floor["__floor__()"] esp_arit_floor-->esp_arit_ceil["__ceil__()"] esp_arit_ceil-->esp_arit_round["__round__()"] esp_arit_round-->esp_arit_trunc["__trunc__()"] esp_arit_trunc-->esp_arit_invert["__invert__()"] especiais --> esp_conversao[Conversão] esp_conversao-->esp_arit_int["__int__()"] esp_arit_int-->esp_arit_float["__float__()"] esp_arit_float-->esp_arit_bool["__bool__()"] esp_arit_bool-->esp_arit_complex["__complex__()"] esp_arit_complex-->esp_arit_hex["__hex__()"] esp_arit_hex-->esp_arit_oct["__oct__()"] esp_arit_oct-->esp_arit_bin["__bin__()"] esp_arit_bin-->esp_arit_ord["__ord__()"] esp_arit_ord-->esp_arit_chr["__chr__()"]

Exemplo: a classe pessoa implementa métodos de instância, de classe e estáticos, e atributos de instância e de classe.

class Pessoa:
    
    status_verbalizar = False # Atributo de classe

    # Construtor para inicializar os atributos da classe
    def __init__(self, nome, idade):
        self.nome = nome   # Atributo de instância
        self.idade = idade # Atributo de instância
    
    # Método para apresentar a pessoa
    def nome_idade(self): # Método de instância
        if Pessoa.verificar():
            Pessoa.apresentar_nome_idade(self.nome,self.idade)
        return self.nome, self.idade

    @classmethod
    def verbalizar(cls,status=True):
        cls.status_verbalizar = status

    @staticmethod
    def verificar():
        return Pessoa.status_verbalizar

    @staticmethod
    def apresentar_nome_idade(nome,idade):
            print(f"Olá, meu nome é {nome} e tenho {idade} anos.")

Exemplo:

pessoa_1 = Pessoa("Pedro", 30)
nome, idade = pessoa_1.nome_idade() 
print(f"nome = '{nome}' e idade = {idade}")
print()

Pessoa.verbalizar()

pessoa_2 = Pessoa("Rosana", 25)
nome, idade = pessoa_2.nome_idade() 
print(f"nome = '{nome}' e idade = {idade}")
print()

Pessoa.verbalizar(False)

pessoa_3 = Pessoa("Reo", 40)
nome, idade = pessoa_3.nome_idade() 
print(f"nome = '{nome}' e idade = {idade}")

8.1 - Estrutura Básica de uma Classe

Uma classe em Python é definida com o cabeçalho usando a palavra-reservada class, seguida pelo nome e dois-pontos. Dentro do corpo da classe, definimos atributos e métodos.

class Pessoa:
    # Construtor para inicializar os atributos da classe
    def __init__(self, nome, idade):
        self.nome = nome
        self.idade = idade
    
    # Método para apresentar a pessoa
    def apresentar(self):
        return f"Olá, meu nome é {self.nome} e tenho {self.idade} anos."

Neste exemplo, Pessoa é uma classe com dois atributos (nome e idade) e um método (apresentar()), além do método-construtor (__init__()).

Atributos e Métodos

Atributos são variáveis que pertencem a uma classe e a suas instâncias, e são usados para armazenar dados.

No exemplo acima, nome e idade são atributos de instância, declarados dentro do método especial __init__(), que é o construtor da classe.

Métodos são funções definidas dentro de uma classe, que operam na classe e suas instâncias, acessando e modificando os atributos de classe e de instância, recebendo parâmetros na chamada do método.

No exemplo, apresentar() é um método de instância (primeiro parâmetro self) que retorna um texto usando os atributos nome e idade.

Instanciando Objetos

A classe é uma definição que consome memória com os atributos de classe e o código dos métodos de classe.

A cada vez que consumida a definição de uma classe em si não ocupe memória para os dados dos atributos, a criação (ou instanciamento) de objetos da classe aloca memória para os dados dos atributos para cada instância. Cada objeto instanciado de uma classe possui seus próprios valores para esses atributos, mas compartilha os mesmos métodos.

pessoa1 = Pessoa("Pedro", 30)
pessoa2 = Pessoa("Carlos", 25)

print(pessoa1.apresentar())
print(pessoa2.apresentar())

No exemplo acima, criamos duas instâncias da classe Pessoa, armazenadas nas variáveis pessoa1 e pessoa2.

Quando o método apresentar() é chamado, cada instância da classe Pessoa tem seu próprio valor para os atributos nome e idade.

Aqui, pessoa1 e pessoa2 são duas instâncias diferentes da classe Pessoa, com valores próprios para nome e idade, mas ambas compartilham o mesmo método apresentar() retornando um texto usando os atributos nome e idade.

O parâmetro self é usado para referenciar os atributos e métodos da instância da classe sendo referida e utilizada.

Herança e Polimorfismo

Herança consiste no uso do conceito de polimorfismo, que permite a criação de novas classes, denominadas classes descendentes, ou subclasses, baseadas em classes existentes, denominadas classes ancestrais ou superclasses. Polimorfismo permite que diferentes classes implementem novos métodos nas classes descendentes, e sobreponham métodos existentes com sua redeclaração de diferentes maneiras.

Polimorfismo é um conceito fundamental na programação orientada a objetos (OOP) que se refere à capacidade de um método, objeto ou variável de assumir várias formas. Originário do grego, "poli" significa muitos e "morfismo" significa formas, esse conceito é uma das pedras angulares que facilitam a flexibilidade e a extensibilidade em sistemas de software. Em programação, descreve a capacidade de métodos que têm o mesmo nome, mas comportamentos diferentes, para serem usados em objetos de diferentes tipos.

Existem principalmente dois tipos de polimorfismo em programação:

  • Polimorfismo de Tempo de Execução (ou Dinâmico)
  • Polimorfismo de Tempo de Compilação (ou Estático)

Polimorfismo de Tempo de Execução (ou Dinâmico)

Também conhecido como ligação tardia ou sobrescrita de métodos, ocorre quando um método em uma classe base é substituído por um método com o mesmo identificador (assinatura) em uma classe derivada.

A decisão sobre qual método deve ser chamado é adiada até o tempo de execução.

Esse tipo de polimorfismo é fundamental para alcançar a reutilização do código e a flexibilidade dos programas, permitindo que novos tipos de objetos sejam introduzidos sem alterar o código que usa esses objetos.

Isso é tipicamente implementado através de herança e a capacidade de uma referência de classe base apontar para um objeto de classe derivada e invocar métodos que foram substituídos.

Este tipo de polimorfismo é realizado através de substituição de método (também conhecido como overriding).

Aqui, uma classe derivada ou subclasse pode fornecer uma implementação específica de um método que já é fornecido por uma de suas superclasses.

O método que será executado é determinado em tempo de execução, o que significa que a decisão sobre qual versão do método será chamada depende do tipo de objeto que está sendo referenciado no momento da execução.

Por exemplo, considere uma classe base chamada Animal com um método fazer_som().

Classes derivadas como Cachorro e Gato podem substituir esse método para fornecer implementações específicas (por exemplo, cães latem e gatos miam).

class Animal:
    def fazer_som(self):
        raise NotImplementedError

class Cachorro(Animal):
    def fazer_som(self):
        return "Auau"

class Gato(Animal):
    def fazer_som(self):
        return "Miau"

def imprimir_som(animal):
    print(animal.fazer_som())

# Exemplo de polimorfismo em tempo de execução

animais = [Cachorro(), Gato()]
for animal in animais: imprimir_som(animal)

Polimorfismo de Tempo de Compilação (ou Estático)

Esse tipo de polimorfismo é resolvido durante a compilação.

O exemplo mais comum é a sobrecarga de métodos, onde o mesmo nome de método pode ter diferentes implementações com parâmetros diferentes.

A sobrecarga de operadores é outra forma de polimorfismo de tempo de compilação, permitindo que operadores padrão, como + e *, sejam redefinidos para funcionar de maneira diferente dependendo dos tipos de seus operandos.

Este tipo é também conhecido como sobrecarga de método (method overloading) e ocorre quando vários métodos têm o mesmo nome mas diferentes parâmetros dentro da mesma classe.

O método apropriado a ser chamado é determinado pelo compilador com base na assinatura do método (ou seja, número e tipos dos argumentos do método na classe).

Por exemplo, uma classe pode ter um método add() implementado várias vezes, cada uma aceitando diferentes tipos de parâmetros:

public class Exemplo { 
    public int add(int a, int b) {
        System.out.println("soma de inteiros");
        return a + b;
    }

    public float add(float a, float b) {
        System.out.println("soma de números reais");
        return a + b;}

    public string add(string a, string b) {
        System.out.println("soma de textos");
        return a + b;
    }
}

Exemplo exemplo = new Exemplo();
System.out.println(exemplo.add(1, 2));
System.out.println(exemplo.add(1.0f, 2.0f));
System.out.println(exemplo.add("1", "2"));

Será impresso:

soma de inteiros
3
soma de números reais
3.0
soma de textos
12

Cada uma dessas implementações pode ter um comportamento diferente, adequado aos tipos de dados que ela manipula.

No Python não ocorre assim.

class Math:
    def add(self, a, b):
        return a + b

    def add(self, a, b, c):
        return a + b + c

A segunda definição de add() substituirá a primeira em Python, mas em linguagens como Java ou C++, ambas podem coexistir.

No python, o polimorfismo deve ser tratado avaliando-se os tipos dos argumentos no corpo da função:

def add(a, b):
    if isinstance(a, int) and isinstance(b, int):
        print("soma de inteiros")
        return a + b
    elif isinstance(a, float) and isinstance(b, float):
        print("soma de números reais")
        return float(a) + float(b)
    elif isinstance(a, str) and isinstance(b, str):
        print("soma de textos")
        return a + b

O exemplo a seguir mostra o polimorfismo em tempo de execução:

print(add(1, 2))
print(add(1.0, 2.0))
print(add("1", "2"))

Por que é importante?

Polimorfismo permite que programadores usem uma interface comum para muitos tipos de ações, aumenta a flexibilidade e a capacidade de generalização do código, e ajuda na implementação de princípios como "programar para interfaces, não para implementações". Ele facilita a manutenção e expansão de sistemas, permitindo que novos objetos que aderem a uma determinada interface sejam integrados com facilidade.

8.2 - Métodos Especiais (Dunder Methods)

Em Python, a maioria das classes herda implicitamente da classe base object, e isso inclui uma série de métodos especiais (também conhecidos como dunder methods, porque seus nomes começam e terminam com dois sublinhados) que permitem que as classes interajam com várias funcionalidades do Python de forma padronizada.

Aqui estão alguns dos métodos especiais mais importantes:

  • __abs__, __add__, __and__, __array__
  • __bool__, __bytes__
  • __call__, __class__, __cmp__, __coerce__, __complex__, __contains__
  • __del__, __delattr__, __delete__, __delitem__, __delslice__, __dict__, __div__, __divmod__
  • __eq__
  • __float__, __floordiv__, __format__
  • __ge__, __get__, __getattr__, __getattribute__, __getitem__, __getslice__, __gt__
  • __hash__, __hex__
  • __iadd__, __iand__, __idiv__, __ifloordiv__, __ilshift__, __imod__, __imul__, __index__, __init__, __instancecheck__, __int__, __invert__, __ior__, __ipow__, __irshift__, __isub__, __iter__, __itruediv__, __ixor__
  • __le__, __len__, __long__, __lshift__, __lt__
  • __metaclass__
  • __mod__, __mro__, __mul__
  • __ne__, __neg__, __new__, __nonzero__
  • __oct__, __or__
  • __pos__, __pow__
  • __radd__, __rand__, __rcmp__, __rdiv__, __rdivmod__, __repr__, __reversed__, __rfloordiv__, __rlshift__, __rmod__, __rmul__, __ror__, __rpow__, __rrshift__, __rshift__, __rsub__, __rtruediv__, __rxor__
  • __set__, __setattr__, __setitem__, __setslice__, __slots__, __str__, __sub__, __subclasscheck__
  • __truediv__
  • __unicode__
  • __weakref__
  • __xor__

Aqui estão alguns métodos dunder comuns e seus propósitos.

Personalização básica de uma classe

Operação Método Dunder
x = Classe(…) x.__new__()
x = Classe(…) x.__init__()
str(x) x.__str__()
repr(x) x.__repr__()
del x x.__del__()
x == y x.__cmp__(y)
hash(x) x.__hash__()
format(x) x.__format__()

Explicação dos métodos:

  • x.__new__(cls [, … ]): Disparado para criar uma nova instância da classe cls. __new__() é um método estático. O valor de retorno de __new__() deve ser uma nova instância de objeto de cls. Se __new__() não retornar uma instância de cls, o método da nova instância __init__() não será invocado. __new__() permite que subclasses de tipos imutáveis ​​(como int, str ou tuple) personalizem a criação de instâncias.
  • x.__init__(self[, … ]): Disparado após a instância de uma classe ter sido criada por __new__(), mas antes de ser retornada ao chamador. Os argumentos são aqueles passados ​​para a expressão do construtor da classe. Como __new__() e __init__() trabalham juntos na construção de objetos (__new__() para criá-los e __init__() para personalizá-los), nenhum valor que não seja None pode ser retornado por __init__(); fazer isso fará com que a TypeError seja gerado em tempo de execução.
  • x.__str__(self): Disparado pelas funções integradas format() e print() de str(x) para calcular a representação de texto “informal” ou bem imprimível de um objeto. O valor de retorno deve ser um objeto de texto (str) .
  • x.__repr__(self): Disparado pela função interna repr() para calcular a representação de texto “oficial” de um objeto. O valor de retorno deve ser um objeto string. Se uma classe define __repr__() mas não __str__() , então __repr__() também é usado quando uma representação de texto “informal” de instâncias dessa classe é necessária. Isso é normalmente usado para depuração, então é importante que a representação seja rica em informações e inequívoca.
  • x.__del__(self): Disparado quando a instância está prestes a ser destruída.
  • x.__cmp__(self, outro): Disparado pela função cmp() para comparar dois objetos x (self) e y (outro). O valor de retorno deve ser -1, 0 ou 1.
  • x.__hash__(self): Disparado pela função integrada hash() e para operações em membros de coleções com hash, incluindo set, frozenset e dict. O método __hash__() deve retornar um número inteiro.
  • x.__format__(self,especificacao_formato): Disparado pela função integrada format() e, por extensão, avaliação de literais de texto formatados e do método x.format(), para produzir uma representação de texto “formatado” de um objeto.

Comparação entre operadores

Operação método chamado
x < y x.__lt__(y)
x <= y x.__le__(y)
x == y x.__eq__(y)
x != y x.__ne__(y)
x > y x.__gt__(y)
x >= y x.__ge__(y)

Explicação dos métodos:

  • x.__lt__(self,outro): x < y, onde x (self) é menor que y (outro)
  • x.__le__(self,outro): x <= y, onde x (self) é menor ou igual a y (outro)
  • x.__eq__(self,outro): x == y, onde x (self) é igual a y (outro)
  • x.__ne__(self,outro): x != y, onde x (self) não é igual a y (outro)
  • x.__gt__(self,outro): x > y, onde x (self) é maior que y (outro)
  • x.__ge__(self,outro): x >= y, onde x (self) é maior ou igual a y (outro)

Acessar atributos de uma classe

Operação Método disparado
x.atributo_inexistente x.__getattr__(self,nome)
x.atributo x.__getattribute__(self,nome)
x.atributo = valor x.__setattr__(self,nome,valor)
del x.atributo x.__delattr__(self,nome)
x.dir() x.__dir__(self)

Explicação dos métodos:

  • x.__getattr__(self,nome): Disparado quando o acesso ao atributo padrão falha com um AttributeError. Ou __getattribute__() gera um AttributeError porque nome não é um atributo de instância ou um atributo na árvore de classes para self. Ou __get__() de uma propriedade aumenta AttributeError. Este método deve retornar o valor do atributo (computado) ou gerar uma exceção AttributeError.
  • x.__getattribute__(self,nome): Disparado incondicionalmente para implementar acessos a atributos para instâncias da classe. Se a classe também definir __getattr__(), esta última não será chamada, a menos que __getattribute__() a chame explicitamente ou gere um AttributeError.
  • x.__setattr__(self,nome,valor): Disparado quando uma atribuição de atributo é realizada.
  • x.__delattr__(self,nome): É usado para exclusão de atributos.
  • x.__dir__(self): É disparada quando a função dir() do objeto é chamada. A função __dir__() não deve aceitar argumentos e retornar uma sequência de textos que representa os nomes acessíveis no módulo. Para uma personalização mais refinada do comportamento do módulo (definindo atributos, propriedades, etc.), pode-se definir o atributo __class__ de um objeto de módulo para uma subclasse de types.ModuleType

Implementar e invocar acessadores de atributos

Operação Método disparado
y = x.atributo x.__get__(self,instancia,owner=None)
x.atributo = valor x.__set__(self,instancia,valor)
del x.atributo x.__delete__(self,instancia)

Explicação dos métodos:

  • x.__get__(self,instancia,owner=None): Disparado para obter o atributo da classe proprietária (acesso ao atributo da classe) ou de uma instância dessa classe (acesso ao atributo da instância).
  • x.__set__(self,instancia,valor): Disparado para definir o atributo em uma instância da classe proprietária para um novo valor.
  • x.__delete__(self,instancia): Disparado para deletar o atributo em uma instância da classe proprietária.

Emulação de tipos genéricos em Python

Operação Método disparado
x[chave_atributo_classe] x.__class_getitem__(cls,chave)
x() x.__call__(self,args…)
len(x) x.__len__(self)
x.length_hint() x.__length_hint__(self)
y = x[chave] x.__getitem__(self,chave)
x[chave] = y x.__setitem__(self,chave,y)
del x[chave] x.__delitem__(self,chave)
x[chave_inexistente] x.__missing__(self,chave)
iter(x) x.__iter__(self)
reversed(x) x.__reversed__(self)
contains(x,item) x.__contains__(self,item)

Chaves aceitas para os tipos:

  • Tipos de sequência: números inteiros e objetos de fatia.
  • Tipos de dicionário: números inteiros ou textos.
  • Tipos de módulo: nomes de atributos.
  • Tipos de instância: nomes de atributos de instância.
  • Tipos de classe: nomes de atributos de classe.

Explicação dos métodos:

  • x.__class_getitem__(cls,chave): Retorna um objeto que representa a especialização de uma classe genérica por argumentos de tipo encontrados em chave.
  • x.__call__(self[,args…]): Disparado quando a instância é chamada como uma função; se este método for definido, x(arg1, arg2, ...) traduz-se aproximadamente em type(x).__call__(x, arg1, ...).
  • x.__len__(self): Disparado com a função integrada len(). Deve retornar o comprimento do objeto, um número inteiro >=0.
  • x.__length_hint__(self): Disparado com operator.length_hint(). Deve retornar um comprimento estimado para o objeto (que pode ser maior ou menor que o comprimento real). O comprimento deve ser um número inteiro >=0.
  • x.__getitem__(self,chave): Disparado com a avaliação de self[chave].
  • x.__setitem__(self,chave,valor): Disparado com a atribuição a self[chave].
  • x.__delitem__(self,chave): Disparado com a exclusão de self[chave].
  • x.__missing__(self,chave): Disparado por dict.__getitem__() para implementar subclasses de dict self[chave] quando a chave não está no dicionário.
  • x.__iter__(self): Disparado quando um iterador é necessário para um contêiner. Este método deve retornar um novo objeto iterador que pode iterar sobre todos os objetos no contêiner.
  • x.__reversed__(self): Disparado (se presente) pelo reversed() integrado para implementar a iteração reversa.
  • x.__contains__(self, item ): Disparado com operadores de teste de associação.

Operações aritméticas binárias (+ - * @ / // % divmod() pow() ** << >> & ^ |)

Os métodos aritméticos abaixo são disparados quando o objeto x (self) está do lado esquerdo do operador na expressão e y (outro) está do lado direito.

Operação Método disparado
x + y x.__add__(self,outro)
x - y x.__sub__(self,outro)
x * y x.__mul__(self,outro)
x @ y x.__matmul__(self,outro)
x / y x.__truediv__(self,outro)
x // y x.__floordiv__(self,outro)
x % y x.__mod__(self,outro)
x.divmod(y) x.__divmod__(self,outro)
x ** y x.__pow__(self,outro)
x << y x.__lshift__(self,outro)
x >> y x.__rshift__(self,outro)
x & y x.__and__(self,outro)
x ^ y x.__xor__(self,outro)
x | y x.__or__(self,outro)

Explicação dos métodos:

  • x.__add__(self,outro): Disparado com a operação aritmética x + y (self + outro).
  • x.__sub__(self,outro): Disparado com a operação aritmética x - y (self - outro).
  • x.__mul__(self,outro): Disparado com a operação aritmética x * y (self * outro).
  • x.__matmul__(self,outro): Disparado com a operação aritmética x @ y (self @ outro).
  • x.__truediv__(self,outro): Disparado com a operação aritmética x / y (self / outro).
  • x.__floordiv__(self,outro): Disparado com a operação aritmética x // y (self // outro).
  • x.__mod__(self,outro): Disparado com a operação aritmética x % y (self % outro).
  • x.__divmod__(self,outro): Disparado com a operação aritmética x.divmod(y) (self.divmod(outro)).
  • x.__pow__(self, outro [,módulo]): Disparado com a operação aritmética x ** y (self ** outro).
  • x.__lshift__(self,outro): Disparado com a operação aritmética x << y (self << outro).
  • x.__rshift__(self,outro): Disparado com a operação aritmética x >> y (self >> outro).
  • x.__and__(self,outro): Disparado com a operação aritmética x & y (self & outro).
  • x.__xor__(self,outro): Disparado com a operação aritmética x ^ y (self ^ outro).
  • x.__or__(self,outro): Disparado com a operação aritmética x | y (self | outro).

Os métodos aritméticos abaixo são disparados quando o objeto y (outro) está do lado esquerdo do operador na expressão e x (self) está do lado direito.

Operação Método disparado
y + x x.__radd__(self,outro)
y - x x.__rsub__(self,outro)
y * x x.__rmul__(self,outro)
y @ x x.__rmatmul__(self,outro)
y / x x.__rtruediv__(self,outro)
y // x x.__rfloordiv__(self,outro)
y % x x.__rmod__(self,outro)
y divmod(x) x.__rdivmod__(self,outro)
y ** x x.__rpow__(self,outro)
y << x x.__rlshift__(self,outro)
y >> x x.__rrshift__(self,outro)
y & x x.__rand__(self,outro)
y ^ x x.__rxor__(self,outro)
y | x x.__ror__(self,outro)

Explicação dos métodos:

  • x.__radd__(self,outro): Disparado com a operação aritmética y + x (outro + self).
  • x.__rsub__(self,outro): Disparado com a operação aritmética y - x (outro - self).
  • x.__rmul__(self,outro): Disparado com a operação aritmética y * x (outro * self).
  • x.__rmatmul__(self,outro): Disparado com a operação aritmética y @ x (outro @ self).
  • x.__rtruediv__(self,outro): Disparado com a operação aritmética y / x (outro / self).
  • x.__rfloordiv__(self,outro): Disparado com a operação aritmética y // x (outro // self).
  • x.__rmod__(self,outro): Disparado com a operação aritmética y % x (outro % self).
  • x.__rdivmod__(self,outro): Disparado com a operação aritmética y divmod(x) (outro divmod(self)).
  • x.__rpow__(self, outro [,módulo]): Disparado com a operação aritmética y ** x (outro ** self).
  • x.__rlshift__(self,outro): Disparado com a operação aritmética y << x (outro << self).
  • x.__rrshift__(self,outro): Disparado com a operação aritmética y >> x (outro >> self).
  • x.__rand__(self,outro): Disparado com a operação aritmética y & x (outro & self).
  • x.__rxor__(self,outro): Disparado com a operação aritmética y ^ x (outro ^ self).
  • x.__ror__(self,outro): Disparado com a operação aritmética y | x (outro | self).

Os métodos aritméticos de atribuição abaixo são disparados quando o objeto y (outro) está do lado direito do operador de atribuição aritmética em x (self) do lado esquerdo.

Operação Método disparado
x += y x.__iadd__(self,outro)
x -= y x.__isub__(self,outro)
x *= y x.__imul__(self,outro)
x @= y x.__imatmul__(self,outro)
x /= y x.__itruediv__(self,outro)
x //= y x.__ifloordiv__(self,outro)
x %= y x.__imod__(self,outro)
x **= y x.__ipow__(self,outro)
x <<= y x.__ilshift__(self,outro)
x >>= y x.__irshift__(self,outro)
x &= y x.__iand__(self,outro)
x ^= y x.__ixor__(self,outro)
x |= y x.__ior__(self,outro)

Explicação dos métodos:

  • x.__iadd__(self,outro): Disparado com a operação aritmética x += y (self += outro).
  • x.__isub__(self,outro): Disparado com a operação aritmética x -= y (self -= outro).
  • x.__imul__(self,outro): Disparado com a operação aritmética x *= y (self *= outro).
  • x.__imatmul__(self,outro): Disparado com a operação aritmética x @= y (self @= outro).
  • x.__itruediv__(self,outro): Disparado com a operação aritmética x /= y (self /= outro).
  • x.__ifloordiv__(self,outro): Disparado com a operação aritmética x //= y (self //= outro).
  • x.__imod__(self,outro): Disparado com a operação aritmética x %= y (self %= outro).
  • x.__ipow__(self, outro [,módulo]): Disparado com a operação aritmética x **= y (self **= outro).
  • x.__ilshift__(self,outro): Disparado com a operação aritmética x <<= y (self <<= outro).
  • x.__irshift__(self,outro): Disparado com a operação aritmética x >>= y (self >>= outro).
  • x.__iand__(self,outro): Disparado com a operação aritmética x &= y (self &= outro).
  • x.__ixor__(self,outro): Disparado com a operação aritmética x ^= y (self ^= outro).
  • x.__ior__(self,outro): Disparado com a operação aritmética x |= y (self |= outro).

Chamado para implementar as operações aritméticas unárias (- + abs() ~).

Operação Método disparado
-x x.__neg__(self)
+x x.__pos__(self)
abs(x) x.__abs__(self)
~x x.__invert__(self)

Explicação dos métodos:

  • x.__neg__(self): Disparado pela função interna -x.
  • x.__pos__(self): Disparado pela função interna +x.
  • x.__abs__(self): Disparado pela função interna abs(x).
  • x.__invert__(self): Disparado pela função interna ~x.

Conversão

Chamado para implementar as funções internas de conversão int(), float(), complex(), bool(), bytes() e array(), retornando o valor apropriado do tipo.

Operação Método disparado
int(x) x.__int__(self)
float(x) x.__float__(self)
complex(x) x.__complex__(self)
bool(x) x.__bool__(self)
bytes(x) x.__bytes__(self)
array(x) x.__array__(self)

Explicação dos métodos:

  • x.__int__(self): Disparado pela função interna int() para converter um objeto para int.
  • x.__float__(self): Disparado pela função interna float() para converter um objeto para float.
  • x.__complex__(self): Disparado pela função interna complex() para converter um objeto para complex.
  • x.__bool__(self): é chamado para implementar o teste de valor verdade e a operação integrada bool(); deve retornar False ou True.
  • x.__bytes__(self): Disparado pela função interna bytes() para calcular uma representação de texto de bytes de um objeto. Isso deve retornar um objeto bytes.
  • x.__array__(self): Disparado pela função interna array() para converter um objeto para array.

Operações de arredondamento

Métodos dunder para implementar as funções internas index(), round(), trunc(), floor() e ceil():

Operação Método disparado
index(x) x.__index__(self)
round(x) x.__round__(self)
trunc(x) x.__trunc__(self)
floor(x) x.__floor__(self)
ceil(x) x.__ceil__(self)

Explicação dos métodos:

  • x.__index__(self): Disparado com a função interna operator.index() e sempre que o Python precisar converter sem perdas o objeto numérico em um objeto inteiro (como em fatiamento ou nas funções internas bin(), oct() e hex()).
  • x.__round__(self[, dígitos ]): Disparado com a função interna round() e sempre que o Python precisar arredondar o objeto numérico.
  • x.__trunc__(self): Disparado com a função interna trunc() e sempre que o Python precisar truncar o objeto numérico.
  • x.__floor__(self): Disparado com a função interna floor() e sempre que o Python precisar arredondar para o menor inteiro menor que o objeto numérico.
  • x.__ceil__(self): Disparado com a função interna ceil() e sempre que o Python precisar arredondar para o maior inteiro maior que o objeto numérico.

Gerenciamento de contexto

Os métodos dunder __enter__() e __exit__() são disparados na solicitação e na liberação pela instrução with no gerenciamento de contexto.

  • x.__enter__(self): Disparado na solicitação.
  • x.__exit__(self,exc_type,exc_value,traceback): Disparado na liberação.

Personalização de argumentos

O atributo __match_args__ é usado para personalizar argumentos posicionais na correspondência de padrões de classe.

  • x.__match_args__: Este atributo de classe pode receber uma tupla de textos. Quando esta classe é usada em um padrão de classe com argumentos posicionais, cada argumento posicional será convertido em um argumento de palavra-reservada, usando o valor correspondente em __match_args__ como palavra-reservada.

Emulação de tipos de buffer

Métodos dunder para implementar as funções internas buffer() e releasebuffer():

  • x.__buffer__(self, flags ): Disparado quando um buffer é solicitado de self
  • x.__release_buffer__(self, buffer ): Disparado quando um buffer não é mais necessário.

Outros métodos dunder

  • x.__slots__: Este atributo de classe pode receber um texto, iterável ou uma sequência de textos com nomes de variáveis ​​​​usados ​​​​por instâncias, reservando espaço para as variáveis ​​declaradas e evita a criação automática de __dict__e __weakref__ para cada instância.
  • x.__init__subclass__() : Sempre que uma classe herda de outra classe, __init_subclass__() é chamada na classe pai. Desta forma, é possível escrever classes que alterem o comportamento das subclasses.
  • x.__mro_entries__(self,bases): Se uma base que aparece em uma definição de classe não for uma instância de type, então o método __mro_entries__() será pesquisado na base. Se o método __mro_entries__() for encontrado, a base será substituída pelo resultado de uma chamada ao __mro_entries__() ao criar a classe.
  • classe.__instancecheck__(self,instancia): Retorna verdadeiro se instancia deve ser considerada uma instância (direta ou indireta) de classe. Se definido, chamado para implementar isinstance(instancia, classe).
  • classe.__subclasscheck__(self,subclasse): Retorna verdadeiro se subclasse deve ser considerada uma subclasse (direta ou indireta) de classe. Se definido, chamado para implementar issubclass(subclass, classe).

Os métodos dunder são fundamentais para integrar as classes personalizadas nas funcionalidades e operadores padrão do Python, definindo como os objetos interagem com diversas operações e funcionalidades da linguagem.

Eles permitem a personalização e extensão das classes de maneiras poderosas e flexíveis, facilitando a implementação de comportamentos específicos e integrando melhor os objetos ao ecossistema do Python.

8.2.1 - Método Especial Construtor (MEC)

O Método Especial Construtor (MEC) é responsável por inicializar os atributos da instância da classe, ao ser criada, usando o construtor, alocando espaço na memória do computador para armazenar os atributos.

  • __init__(self, *args, **kwargs): o construtor da classe

Exemplo:

# Declaração da classe Pessoa
class MEC:
    def __init__(self, atributo_no_argumento):
        print("entrou em __init__()")
        self.atributo_de_instancia = atributo_no_argumento

# Instanciação do objeto com uso do construtor
print("mec = MEC('Valor do Atributo de Instância')")
mec = MEC('Valor do Atributo de Instância')

# Imprimimos os atributos da instância
print(f"mec.atributo_de_instancia = '{mec.atributo_de_instancia}'")

# Imprimimos a representação do objeto
print(mec)

8.2.2 - Métodos Especiais de Representação (MER)

Os Métodos Especiais de Representação (MER) são utilizados na identificação e representação classes e objetos.

São:

  • __repr__(self): Retorna um texto que representa a instância de uma maneira que possa ser usada para recriá-la, sendo evocada pelo método obj.repr(). Útil para debugging.
  • __str__(self): Método que retorna um texto legível que representa a instância, sendo evocada pelo método obj.str() e pela função print().
class MER:
    def __init__(self, atributo_no_argumento):
        self.atributo_de_instancia = atributo_no_argumento

    def __str__(self):
        print("entrou em __str__()")
        return f"MER('{self.atributo_de_instancia}')"

    def __repr__(self):
        print("entrou em __repr__()")
        return f"MER(atributo_de_instancia='{self.atributo_de_instancia}')"

# Instanciação do objeto com uso do construtor
mer = MER("Valor do Atributo de Instância")

# A função str() chama __str__()
print(str(mer))
print()

# A função repr() chama __repr__()
print(repr(mer))

8.2.3 - Métodos Especiais de Comparação (MEP)

Os Métodos Especiais de Comparação (MEP) permitem que os objetos sejam comparados entre si.

Com eles podemos definir o comportamento dos operadores de igualdade, desigualdade, menor que, menor ou igual a, maior que e maior ou igual a para os objetos em geral.

Os principais MEP em Python são:

  • __eq__(self, outro): Define o comportamento do y de igualdade == com outro.
  • __ne__(self, outro): Define o comportamento do y de desigualdade != com outro.
  • __lt__(self, outro): Define o comportamento do y de menor < que outro.
  • __le__(self, outro): Define o comportamento do y de menor ou igual <= a outro.
  • __gt__(self, outro): Define o comportamento do y de maior > que outro.
  • __ge__(self, outro): Define o comportamento do y de maior ou igual >= a outro.
def dunder(a,b):
    print(f"entrou em {a}()")
    return b

class MEP:
    def __init__(self, nome):
        print("entrou em __init__()")
        self.nome = nome

    def __eq__(self, outro):
        return dunder("__eq__",f"{self.nome} igual {outro}")

    def __ne__(self, outro):
        return dunder("__ne__",f"{self.nome} diferente {outro}")

    def __lt__(self, outro):
        return dunder("__lt__",f"{self.nome} menor que {outro}")

    def __gt__(self, outro):
        return dunder("__gt__",f"{self.nome} maior que {outro}")

    def __le__(self, outro):
        return dunder("__le__",f"{self.nome} menor ou igual a {outro}")

    def __ge__(self, outro):
        return dunder("__ge__",f"{self.nome} maior ou igual a {outro}")

Instanciação do objeto mat da classe MEP com uso do construtor

print("mep = MEP('i')")
mep = MEP("i")

A função print_MEP() imprime o comando e o resultado do texto de uma expressão.

def print_MEP(comando_texto):
    print()
    print(f"Comando: {comando_texto}")
    print(f"Retorno: {eval(comando_texto)}")
print_MEP("mep == 123")
print_MEP("mep != 123")
print_MEP("mep < 123")
print_MEP("mep <= 123")
print_MEP("mep > 123")
print_MEP("mep >= 123")

8.2.4 - Métodos Especiais de Compartimento (MET)

Os Métodos Especiais de Compartimento (MET) permitem que os objetos sejam iterados.

Com eles podemos definir o comportamento dos métodos de iteração para os objetos em geral.

Os principais métodos de iteração em Python são:

  • __iter__(self): Define o comportamento do y de iteração.
  • __next__(self): Define o comportamento do y de proximo.
  • __len__(self): Define o comportamento do y de contagem.
  • __reversed__(self): Define o comportamento do y de contagem reversa.
  • __contains__(self): Define o comportamento do y de contém.
  • __getitem__(self): Define o comportamento do y de indexação.
  • __setitem__(self): Define o comportamento do y de definir.
  • __delitem__(self): Define o comportamento do y de excluir.
def dunder(a,b):
    print(f"entrou em {a}()")
    return b

class MET:
    def __init__(self, *args):
        print("entrou em __init__()")
        self.itens = list(args)

    def __iter__(self):
        return dunder("__iter__",iter(self.itens))

    def __next__(self):
        return dunder("__next__",next(self.itens))

    def __len__(self):
        return dunder("__len__",len(self.itens))

    def __reversed__(self):
        return dunder("__reversed__",reversed(self.itens))

    def __contains__(self, item):
        return dunder("__contains__",item in self.itens)

    def __getitem__(self, indice):
        return dunder("__getitem__",self.itens[indice])

    def __setitem__(self, indice, valor):
        return dunder("__setitem__",valor)

    def __delitem__(self, indice):
        print("entrou em __delitem__()")
        del self.itens[indice]

    def __add__(self, outro):
        return dunder("__add__",MET(*self.itens, *outro.itens))

    def __radd__(self, outro):
        return dunder("__radd__",MET(*outro.itens, *self.itens))

    def __iadd__(self, outro):
        print("entrou em __iadd__()")
        self.itens.extend(outro.itens)
        return self.itens

    def __repr__(self):
        return dunder("__repr__",f"MET({str(self.itens)})")

    def __str__(self):
        return dunder("__str__",str(self.itens))

A função print_MET() executa instruções usadas na apresentação das informações para demonstração dos métodos mágicos.

  • Recebe o comando como texto no argumento texto_comando
  • Imprime uma linha em branco para afastar a saída da anterior
  • Imprime o comando
  • Recebe na variável resultado o valor retornado pela avaliação com a função eval() do texto_comando recebido no argumento
  • Imprime o resultado
def print_MET(texto_comando):
    print()                            # imprime uma linha em branco
    print(f"Comando: {texto_comando}") # imprime o comando no argumento
    resultado = eval(texto_comando)    # avalia o comando em texto com eval()
    print(f"Resultado: {resultado}")   # imprime o resultado

No exemplo abaixo, a variável minhalista é uma instância da classe MET (Métodos Especiais de Compartimento), gerenciando uma lista com os itens [1, 2, 3, 4 e 5].

A função print_MET() é usada para imprimir os comandos de compartimento e os respectivos resultados.

Alguns códigos não usam a função print_MET() para imprimir o comando e o resultado.

print("Comando: minhalista = MET(1, 2, 3, 4, 5)")
minhalista = MET(1, 2, 3, 4, 5)

Após minhalista ser criada, executamos a instrução minhalista apenas com o nome da variável, disparando a execução da função __str__().

print_MET("minhalista")

Exemplo: iteração de minhalista, disparando a execução da função __iter__, e o retorno dos itens da lista para impressão.

print()
print("Comando: for item in minhalista: print(item)")
for item in minhalista:
    print(item)

Exemplos: len(), in e index()

print_MET("len(minhalista)")
print_MET("3 in minhalista")
print_MET("minhalista[2]")

Exemplos: reversed().

print_MET("list(reversed(minhalista))")

Exemplo: soma de objetos da classe MET com +, disparando a execução de diversos métodos especiais.

print_MET("minhalista + MET(9, 10)")

Exemplo: Atribuição com '='.

print("Comando: minhalista[2] = 10")
minhalista[2] = 10

Exemplo: Atribuição com '+='.

print("Comando: minhalista += MET(6, 7, 8)")
minhalista += MET(6, 7, 8)

Exemplo: Remoção de itens com del.

print("Comando: del minhalista[2]")
del minhalista[2]

8.2.5 - Métodos Especiais de Contexto (MEX)

Contexto é como é chamado o contexto do Python.

Os Métodos Especiais de Contexto (MEX) permitem que o Python funcione com o recurso de contexto (context manager).

Os MEX são:

  • __enter__(self): Define o comportamento do objeto ao ser adquirido.
  • __exit__(self, exc_type, exc_value, traceback): Define o comportamento do objeto ao ser liberado.

Permitem que a classe funcione com a instrução with, facilitando o gerenciamento de contexto usando recursos como arquivos ou conexões de rede.

class MEX:
    def __enter__(self):
        print("entrou em __enter__()")
        print("Recurso adquirido")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("entrou em __exit__()")
        print("Recurso liberado")

with MEX():
    print("Usando o recurso")

8.2.6 - Métodos Especiais de Aritmética (MEA)

Os Métodos Especiais de Aritmética (MEA) permitem que o Python funcione com o recurso de aritmética.

Os recursos de aritmética são:

  • __add__(self, outro): Define o comportamento do y de adição +.
  • __sub__(self, outro): Define o comportamento do y de subtração -.
  • __mul__(self, outro): Define o comportamento do y de multiplicação *.
  • __truediv__(self, outro): Define o comportamento do y de divisão /.
  • __floordiv__(self, outro): Define o comportamento do y de divisão inteira //.
  • __mod__(self, outro): Define o comportamento do y de resto da divisão %.
  • __pow__(self, outro): Define o comportamento do y de potência **.
  • __neg__(self): Define o comportamento do y de negação -.
  • __pos__(self): Define o comportamento do y de positivo +.
  • __abs__(self): Define o comportamento do y de valor absoluto abs().
  • __invert__(self): Define o comportamento do y de inversão ~.
  • __round__(self, ndigits=None): Define o comportamento do y de arredondamento round().
  • __trunc__(self): Define o comportamento do y de truncação trunc().
  • __ceil__(self): Define o comportamento do y de arredondamento para cima ceil().
  • __floor__(self): Define o comportamento do y de arredondamento para baixo floor().

Declaração de uma classe com MEA:

def dunder(a,b):
    print(f"entrou em {a}()")
    return b

class MEA:
    def __init__(self, num):
        self.real = float(num)

    def __str__(self):
        return dunder("__str__",f"MEA({self.real})")

    def __repr__(self):
        return dunder("__repr__",f"MEA(real={self.real})")

    def __float__(self):
        return dunder("__float__",self.real)

    def __add__(self, outro):
        return dunder("__add__",f"{self.real} mais {float(outro)}")

    def __sub__(self, outro):
        return dunder("__sub__",f"{self.real} menos {float(outro)}")

    def __mul__(self, outro):
        return dunder("__mul__",f"{self.real} vezes {float(outro)}")

    def __truediv__(self, outro):
        return dunder("__truediv__",f"{self.real} dividido por {float(outro)}")

    def __floordiv__(self, outro):
        return dunder("__floordiv__",f"{self.real} inteiro da divisão por {float(outro)}")

    def __mod__(self, outro):
        return dunder("__mod__",f"{self.real} resto da divisão por {float(outro)}")

    def __pow__(self, outro):
        return dunder("__pow__",f"{self.real} elevado a {float(outro)}")

    def __neg__(self):
        return dunder("__neg__",f"{self.real} negado")

    def __pos__(self):
        return dunder("__pos__",f"{self.real} positivo")

    def __abs__(self):
        return dunder("__abs__",f"{self.real} absoluto")

    def __invert__(self):
        return dunder("__invert__",f"{self.real} inversão")

    def __round__(self, ndigits=None):
        return dunder("__round__",f"{self.real} arredondado em {ndigits} casas decimais")

    def __trunc__(self):
        return dunder("__trunc__",f"{self.real} truncado")

    def __ceil__(self):
        return dunder("__ceil__",f"{self.real} arredondado para cima")

    def __floor__(self):
        return dunder("__floor__",f"{self.real} arredondado para baixo")

Declaramos a função print_MEA() que recebe o argumento texto_comando.

  • Recebe o comando como texto no argumento texto_comando
  • Imprime uma linha em branco para afastar a saída da anterior
  • Imprime o comando
  • Recebe na variável resultado o valor retornado pela avaliação com a função eval() do texto_comando recebido no argumento
  • Imprime o resultado
def print_MEA(texto_comando):
    print()                                   # imprime uma linha em branco
    print(f"Comando: print({texto_comando})") # imprime o comando no argumento
    resultado = eval(texto_comando)           # avalia o comando em texto com eval()
    print(f"Resultado: {resultado}")          # imprime o resultado

Declaramos as variáveis r1 e r2 como instâncias da classe MEA

print("r1 = MEA(5.5)")
r1 = MEA(5.5)

print("r2 = MEA(7.7)")
r2 = MEA(7.7)

Imprimimos os atributos de r1 e r2 que disparam a função print_MEA().

print_MEA(f"r1")
print_MEA(f"type(r1)")
print_MEA(f"isinstance(r1, MEA)")
print_MEA(f"isinstance(r1, float)")
print_MEA(f"str(r1)")
print_MEA(f"repr(r1)")

print()

print_MEA(f"r2")
print_MEA(f"type(r2)")
print_MEA(f"isinstance(r2, MEA)")
print_MEA(f"isinstance(r2, float)")
print_MEA(f"str(r2)")
print_MEA(f"repr(r2)")

Operações aritméticas disparando a execução de métodos mágicos:

print_MEA(f"r1 + r2")
print_MEA(f"r1 - r2")
print_MEA(f"r1 * r2")
print_MEA(f"r1 / r2")
print_MEA(f"r1 // r2")
print_MEA(f"r1 % r2")
print_MEA(f"r1 ** r2")
print_MEA(f"-r1")
print_MEA(f"+r1")
print_MEA(f"abs(r1)")
print_MEA(f"round(r1)")
print_MEA(f"~r1")

Importamos a biblioteca math para usar as funções trunc(), ceil() e floor().

print("import math")
import math

Imprimimos os atributos de math.

print_MEA(f"math.trunc(r1)")
print_MEA(f"math.ceil(r1)")
print_MEA(f"math.floor(r1)")

8.2.7 - Métodos Especiais de Conversão (MEV)

Os Métodos Especiais de Conversão (MEV) permitem que os objetos sejam convertidos de forma conveniente para outros tipos.

São:

  • __int__(self): Define o comportamento para a conversão para inteiro.
  • __float__(self): Define o comportamento para a conversão para float.
  • __bool__(self): Define o comportamento para a conversão para booleano.
  • __complex__(self): Define o comportamento para a conversão para complexo.
def dunder(a,b):
    print(f"entrou em {a}()")
    return b

class MEV:
    def __init__(self, valor):
        self.valor = valor

    def __int__(self):
        return dunder("__int__",int(self.valor))

    def __float__(self):
        return dunder("__float__",float(self.valor))

    def __bool__(self):
        return dunder("__bool__",bool(self.valor))

    def __complex__(self):
        return dunder("__complex__",complex(self.valor))

Exemplo:

num = MEV(10)

def print_MEV(texto_comando):
    print()
    print(f"Comando: {texto_comando}")
    print(f"Retorno: {eval(texto_comando)}")

print_MEV("int(num)")
print_MEV("float(num)")
print_MEV("bool(num)")
print_MEV("complex(num)")

8.2.8 - Métodos Especiais de Atributos (MEB)

Os Métodos Especiais de Atributos (MEB) permitem que os objetos sejam atribuidos de forma conveniente.

São:

  • __getattribute__(self, nome): Define o comportamento para o acesso de um atributo.
  • __getattr__(self, nome): Define o comportamento para o acesso de um atributo que não existe.
  • __setattr__(self, nome, valor): Define o comportamento para o atribuição de um atributo.
  • __delattr__(self, nome): Define o comportamento para o deletamento de um atributo.
def dunder(a,b):
    print(f"entrou em {a}()")
    return b

class MEB:
    def __init__(self, atributo):
        self.atributo_da_instancia = atributo

    def __getattribute__(self, nome):
        return dunder("__getattribute__",super().__getattribute__(nome))

    def __getattr__(self, nome):
        return dunder("__getattr__",f"Atributo '{nome}' inexistente")

    def __setattr__(self, nome, valor):
        print("entrou em __setattr__()")
        super().__setattr__(nome, valor)

    def __delattr__(self, nome):
        print("entrou em __delattr__()")
        super().__delattr__(nome)

Atribuimos na variável meb a instâncian do objeto da classe MEB.

print ("Comando: meb = MEB(10)")
meb = MEB(10)

Impressão dos atributos MEB da variável meb.

print()
print("comando: print(meb.atributo_da_instancia)")
print(meb.atributo_da_instancia)

print()
print("comando: meb.atributo_da_instancia = 20")
meb.atributo_da_instancia = 20

print()
try:
    print("comando: meb.atributo_inexistente = 20")
    print(meb.atributo_inexistente)
except AttributeError:
    print("Erro: objeto 'MEB' não tem 'atributo_inexistente'")

print()
print("comando: del meb.atributo_da_instancia")
del meb.atributo_da_instancia

8.2.9 - Método Especial de Chamada (MEH)

O Método Especial de Chamada (MEH) __call__() é executado quando o objeto é chamado como uma função.

class MEH:
    def __call__(self, *args, **kwargs):
        print(f"entrou em __call__()")
        return f"args={args}, kwargs={kwargs}"

Criamos a instância da classe MEH na variável meh.

print("Comando: meh = MEH()")
meh = MEH()

Imprimimos o retorno do objeto meh chamado como uma função.

print("Comando: meh(1, 2, 3, nome='Pedro')")
print(f"Retorno: {meh(1, 2, 3, nome='Pedro')}")

8.3 - Decoradores de Classes e Métodos

Decoradores são uma maneira poderosa de modificar o comportamento de funções ou métodos.

Em classes, eles podem ser aplicados a métodos ou à própria classe.

8.3.1 - Decoradores de Métodos de Instância

Decoradores de métodos aplicados a instâncias alteram o comportamento de chamadas de métodos.

@abstractmethod

O decorador @abstractmethod é usado em classes abstratas para indicar que um método deve ser implementado por qualquer classe que herde da classe abstrata.

Ele é utilizado juntamente com o módulo abc (Abstract Base Classes).

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def fazer_som(self):
        pass

class Cachorro(Animal):
    def fazer_som(self):
        return "Au au"

# cachorro = Cachorro()
# print(cachorro.fazer_som())

# animal = Animal()  # Isso causará um erro, pois Animal é abstrata

@property (Getter)

O decorador @property é usado para definir métodos que atuam como atributos.

Ele é útil para encapsular acesso a atributos privados e fornecer uma interface pública controlada para eles.

O getter é chamado quando o atributo é recuperado como um atributo normal.

class Pessoa:
    def __init__(self, nome, idade):
        self._nome = nome
        self._idade = idade

    @property
    def nome(self):
        return self._nome

    @property
    def idade(self):
        return self._idade

# Uso do getter
pessoa = Pessoa("Pedro", 30)
print(f"nome: {pessoa.nome}, idade: {pessoa.idade}")

@.setter

O decorador @.setter permite definir o comportamento do atributo quando é atribuído um novo valor.

class Pessoa:
    def __init__(self, nome, idade):
        self._nome = nome
        self._idade = idade

    @property
    def nome(self):
        return self._nome

    @nome.setter
    def nome(self, novo_nome):
        if isinstance(novo_nome, str):
            self._nome = novo_nome
        else:
            raise ValueError("Nome deve ser um texto")

# Uso do setter
pessoa = Pessoa("Pedro", 30)
pessoa.nome = "Maria"
print(pessoa.nome)

@.deleter

O decorador @.deleter permite definir o comportamento do atributo quando ele é deletado.

class Pessoa:
    def __init__(self, nome, idade):
        self._nome = nome
        self._idade = idade

    @property
    def nome(self):
        return self._nome

    @nome.setter
    def nome(self, novo_nome):
        if isinstance(novo_nome, str):
            self._nome = novo_nome
        else:
            raise ValueError("Nome deve ser um texto")

    @nome.deleter
    def nome(self):
        del self._nome

# Uso do deleter
pessoa = Pessoa("Pedro", 30)
del pessoa.nome

@dataclass

O decorador @dataclass do módulo dataclasses é usado para reduzir o boilerplate ao criar classes que são principalmente usadas para armazenar dados.

Ele automaticamente gera métodos como __init__, __repr__, __eq__, etc.

from dataclasses import dataclass

@dataclass
class Pessoa:
    nome: str
    idade: int

# Uso do dataclass
pessoa = Pessoa("Pedro", 30)
print(pessoa)

8.3.2 - Decorador de Métodos Estáticos (DME)

O Decoradores de método Estático (DME) @staticmethod permite que o método seja chamado de forma estática, sem que um objeto da classe seja criado e usando apenas com os argumentos passados.

Os métodos estáticos não recebem uma referência à instância ou à classe e são usados como funções normais que estão logicamente relacionadas à classe, o que significa que ele não recebem referência à instância (self) ou à classe (cls), não podendo modificar o estado da instância ou da classe, por não terem acesso a eles.

No exemplo a seguir, o decorador @staticmethod altera a forma como o método soma() na classe Matematica.

class Matematica:
    @staticmethod
    def soma(a, b):
        return a + b

print(Matematica.soma(5, 3))

No exemplo, o método soma() é um Método Estático da classe Matematica, que recebe dois argumentos e retorna o valor da soma deles, sem que um objeto da classe seja criado.

8.3.3 - Decorador de Métodos de Classe (DMC)

O Decorador de Metodo de Classe (DMC) @classmethod permite que uma função que receba a classe do método decorado como argumento, retornando uma nova classe ou modificando Atributos de Classe da classe.

O decorador @classmethod transforma um método em um método de classe, que recebe uma referência à classe (cls) como seu primeiro argumento, em vez da instância (self).

Os Atributos de Classe são atributos compartilhados por todas as instâncias da classe. Eles são definidos diretamente na classe, fora de qualquer método, inclusive do construtor.

Métodos de Classe são frequentemente usados para criar métodos de fábrica que criam instâncias da classe.

No exemplo a seguir, a função incrementar_contador() é um Decorador de Classe que incrementa o Atributo de Classecontador_instancia da classe.

class ClasseExemplo:

    contador_instancias = 0

    def __init__(self):
        ClasseExemplo.incrementar_contador()

    @classmethod
    def incrementar_contador(cls):
        cls.contador_instancias += 1

obj1 = ClasseExemplo()
obj2 = ClasseExemplo()
obj3 = ClasseExemplo()
obj4 = ClasseExemplo()

print(ClasseExemplo.contador_instancias)

No exemplo a seguir demonstra-se como o Atributo de Classe pode ser compartilhado por todas as instâncias da classe.

class Pessoa:
    total_pessoas = 0

    def __init__(self, nome, idade):
        self.nome = nome
        self.idade = idade
        Pessoa.total_pessoas += 1

p1 = Pessoa("Pedro", 30)
p2 = Pessoa("Carlos", 25)
print(Pessoa.total_pessoas)

No exemplo, total_pessoas é um Atributo de Classe, que é compartilhado por todas as instâncias da classe. O valor de Pessoa.total_pessoas é iniciado com 0.

Quando as instâncias p1 e p2 são criadas, o valor de Pessoa.total_pessoas é incrementado no construtor somando 2 com a instanciação dos dois objetos.

No exemplo a seguir, alterar_atributo_de_classe() é um Método de Classe que recebe um novo valor para o atributo atributo_de_classe.

Métodos de Classe recebem uma referência à classe como primeiro argumento (cls), permitindo-lhes modificar a classe ou criar instâncias da classe.

# Declaramos a classe AlgumaClasse com o Atributo de Classe
# atributo_de_classe = "inicial", criado no momento da declaração da classe.

class AlgumaClasse:
    atributo_de_classe = "Inicial"

    def __init__(self):
        self.atributo_de_instancia = AlgumaClasse.atributo_de_classe

    @classmethod
    def alterar_atributo_de_classe(cls, novo_atributo_de_classe):
        cls.atributo_de_classe = novo_atributo_de_classe

    def apresentar_atributo_de_instancia(self):
        return self.atributo_de_instancia

    def apresentar_atributo_de_classe(self):
        return self.atributo_de_classe

Exemplo: criação de objetos da classe AlgumaClasse.

Criamos duas instâncias de objetos da classe AlgumaClasse, armazenadas nas variáveis obj_1 e obj_2.

obj_1 = AlgumaClasse()
obj_2 = AlgumaClasse()

Imprimimos os atributos atributo_de_instancia e atributo_de_classe dos objetos obj_1 e obj_2.

print(f"obj1.atributo_de_instancia = {obj_1.apresentar_atributo_de_instancia()}")
print(f"obj1.atributo_de_classe    = {obj_1.apresentar_atributo_de_classe()}")
print(f"obj2.atributo_de_instancia = {obj_2.apresentar_atributo_de_instancia()}")
print(f"obj2.atributo_de_classe    = {obj_2.apresentar_atributo_de_classe()}")

Alteramos o atributo atributo_de_classe da classe AlgumaClasse, através do Método de Classe alterar_atributo_de_classe(), afetando todas as instâncias de objetos da classe.

AlgumaClasse.alterar_atributo_de_classe("Alterado")

Imprimimos atributo_de_classe dos objetos. Observamos que o valor do atributo_de_classe foi alterado refletindo em todas as instâncias, mas o valor de atributo_de_instancia permanece o mesmo inicializado pelo valor anterior de atributo_de_classe, antes de ser alterado.

print(f"Atributo de Instância de obj1 = {obj_1.apresentar_atributo_de_instancia()}")
print(f"Atributo de Classe de obj1    = {obj_1.apresentar_atributo_de_classe()}")
print(f"Atributo de Instância de obj2 = {obj_2.apresentar_atributo_de_instancia()}")
print(f"Atributo de Classe de obj2    = {obj_2.apresentar_atributo_de_classe()}")

No exemplo, o método alterar_atributo_de_classe() é um Método de Classe, que recebe uma referência à classe (cls) como primeiro argumento, alterando o valor do Atributo de Classe AlgumaClasse.atributo_de_classe.

No exemplo a seguir, a função registrar() é um Decorador de Classe que recebe uma classe como argumento e retorna a classe modificada.

def registrar(cls):
    cls.registrado = True
    return cls

@registrar
class MinhaClasse:
    pass

print(MinhaClasse.registrado)

No exemplo a seguir, a função criar_com_nome_padrao() é um Decorador de Classe que recebe uma classe como argumento e retorna a classe modificada.

class Pessoa:
    contador = 0

    def __init__(self, nome):
        self.nome = nome
        Pessoa.contador += 1

    @classmethod
    def criar_com_nome_padrao(cls):
        return cls("Nome Padrão")

# Uso do método de classe
pessoa = Pessoa.criar_com_nome_padrao()
print(pessoa.nome)
print(Pessoa.contador)

8.3.4 - Decoradores Customizados

Você pode criar seus próprios decoradores para adicionar funcionalidades específicas a métodos ou funções.

Aqui está um exemplo simples de um decorador customizado que mede o tempo de execução de uma função:

Além dos decoradores básicos como @staticmethod, @classmethod e @property, Python oferece muitos outros decoradores úteis, tanto no padrão da biblioteca quanto customizáveis, para adicionar funcionalidades e melhorar a eficiência e a organização do código.

Decoradores como @abstractmethod, @dataclass, e @lru_cache, assim como a capacidade de criar decoradores personalizados, são ferramentas valiosas no arsenal de um desenvolvedor Python.

import time

def medir_tempo(func):
    def wrapper(*args, **kwargs):
        inicio = time.time()
        resultado = func(*args, **kwargs)
        fim = time.time()
        print(f"Tempo de execução de {func.__name__}: {fim - inicio} segundos")
        return resultado
    return wrapper

class Matematica:
    @medir_tempo
    def soma(a, b):
        time.sleep(1)  # Simula uma operação demorada
        return a + b

# Uso do decorador customizado
resultado = Matematica.soma(5, 7)
print(resultado)

8.4 - Exemplo Prático: Classe MatBin

Vamos fazer um exemplo completo de matemática binária, base fundamental da computação moderna.

Todo o funcionamento dos computadores, desde a mais simples operação até as mais complexas, depende do sistema binário.

Os números podem ser convertidos de base, dividindo-se o valor na base atual a divisão pela nova base, até que o resto na nova base não possa mais ser dividido pela base atual.

O sistema binário utiliza apenas dois dígitos, 0 e 1, conhecidos como bits, para representar todas as informações e operações realizadas por um computador.

Bits e Bytes: A Linguagem dos Computadores

Os bits são a unidade mais básica de dados na computação. Um bit pode ter apenas dois valores: 0 ou 1. Apesar de sua simplicidade, os bits formam a base de toda a arquitetura computacional. Os bytes, compostos por oito bits, são a unidade padrão de armazenamento e manipulação de dados nos computadores. Combinando bits, podemos representar uma vasta gama de informações, desde números e caracteres até imagens e sons.

  • Operações Binárias: A matemática binária permite operações aritméticas e lógicas essenciais para o funcionamento dos computadores. Operações como adição, subtração, multiplicação e divisão podem ser realizadas em binário, utilizando regras simples que os circuitos digitais podem implementar facilmente. Além disso, operações lógicas como AND, OR, NOT e XOR são fundamentais para a tomada de decisões e controle de fluxo dentro dos programas de computador.
  • Circuitos Digitais e Processadores: Os circuitos digitais, que compõem os processadores de computadores, utilizam portas lógicas para realizar operações binárias. Estas portas são construídas a partir de transistores, que são dispositivos semicondutores que podem estar em um dos dois estados binários. A capacidade de manipular bits de forma rápida e eficiente é o que permite aos processadores executar instruções e realizar cálculos complexos em questão de nanosegundos.
  • Armazenamento de Dados: A matemática binária também é crucial para o armazenamento de dados. Memórias RAM, discos rígidos, SSDs e outros dispositivos de armazenamento utilizam sistemas binários para ler, escrever e armazenar informações. Cada bit de informação é armazenado em um estado de 0 ou 1, e a combinação desses bits em bytes e megabytes permite a criação e gestão de grandes volumes de dados.
  • Comunicação de Dados: A comunicação entre dispositivos digitais, como computadores, roteadores e smartphones, também se baseia na matemática binária. Os dados são transmitidos como sequências de bits através de cabos, fibras ópticas ou sinais sem fio. Protocolos de comunicação binária garantem que os dados sejam transferidos de maneira correta e eficiente, possibilitando a conexão e o funcionamento da internet e de outras redes.
  • Criptografia e Segurança: A segurança da informação na computação depende fortemente da matemática binária. Técnicas de criptografia utilizam operações binárias complexas para codificar e proteger dados. Chaves de criptografia, geralmente expressas em binário, são essenciais para garantir que as informações permaneçam seguras e acessíveis apenas a usuários autorizados.

A matemática binária é a espinha dorsal da computação moderna.

Desde a representação e manipulação de dados até a execução de instruções e a comunicação entre dispositivos, tudo depende do sistema binário.

A simplicidade e eficiência do sistema binário permitem que os computadores realizem uma ampla gama de tarefas de forma rápida e precisa, tornando-se uma parte indispensável da tecnologia e da vida cotidiana.

A compreensão e o domínio da matemática binária são fundamentais para qualquer profissional de computação e tecnologia.

8.4.1 - Conversão de Números Entre Diferentes Bases

A conversão de números entre diferentes bases é um processo essencial em várias disciplinas, especialmente em ciência da computação e matemática.

O método descrito envolve dividir o valor na base atual pela nova base repetidamente até que o quociente seja zero, coletando os restos para formar o número na nova base.

Processo de Conversão

Divisão Repetida:

  • O número na base atual é dividido pela nova base.
  • O resto da divisão é registrado como o dígito menos significativo (à direita) na nova base.
  • O quociente da divisão é usado como o novo número a ser dividido.

Iteração:

  • Este processo de divisão e registro de restos continua até que o quociente se torne zero.
  • Os restos registrados, lidos de baixo para cima (do último obtido ao primeiro), formam o número na nova base.

Exemplo Prático

Vamos converter o número decimal 10 para a base binária (base 2):

Na tabela a seguir, a primeira coluna registra a etapa da divisão, a segunda o quociente, a terceira o resto.

O resto correponde ao dígito menos significativo registrado.

Etapa Divisão Inteiro Resto
Primeira divisão 10÷2 5 0
Segunda Divisão 5÷2 2 1
Terceira Divisão 2÷2 1 0
Quarta Divisão 1÷2 0 1

O resultado é o número binário 1010 (lido de baixo para cima).

Agora vamos converter o número decimal 46 para a base binária.

Etapa Divisão Inteiro Resto
Primeira divisão 46÷2 23 0
Segunda Divisão 23÷2 11 1
Terceira Divisão 11÷2 5 1
Quarta Divisão 5÷2 2 1
Quinta Divisão 2÷2 1 0
Sexta Divisão 1÷2 0 1

O resultado é o número binário 101110 (lido de baixo para cima).

Importância e Aplicações

A conversão entre bases é vital em computação.

Sistemas de numeração binário (base 2), octal (base 8) e hexadecimal (base 16) são frequentemente usados em programação e design de circuitos digitais. Cada base oferece vantagens específicas:

  • Binário: Simplifica a lógica digital.
  • Octal: Facilita a leitura e compacta a representação binária.
  • Hexadecimal: Compacta ainda mais os números binários e é usado na programação para representar endereços de memória e valores de cores.

Converter números entre bases através da divisão repetida é um método robusto e direto.

Compreender este processo é fundamental para qualquer pessoa que trabalha com sistemas digitais, programação ou matemática, proporcionando uma base sólida para manipular e interpretar números em diferentes contextos.

8.4.2 - Bases Decimal, Binária, Octal e Hexadecimal

Vamos explorar a matemática das diferentes bases numéricas: decimal, binária, octal e hexadecimal.

Cada base numérica tem suas próprias características e utilizações, e entender como elas funcionam pode ser extremamente útil, especialmente em áreas como ciência da computação e eletrônica.

Sistema Decimal (Base 10)

O sistema decimal é o mais comum e é utilizado na vida cotidiana.

Ele é baseado em 10 dígitos: 0, 1, 2, 3, 4, 5, 6, 7, 8 e 9.

Cada posição em um número decimal representa uma potência de 10.

Exemplo

O número 345 em base decimal pode ser expresso como:

$345 = 3 \times 10^2 + 4 \times 10^1 + 5 \times 10^0$

Sistema Binário (Base 2)

O sistema binário é fundamental na computação e na eletrônica digital.

Ele utiliza apenas dois dígitos: 0 e 1.

Cada posição em um número binário representa uma potência de 2.

Exemplo

O número 1011 em base binária correspondente na base decimal pode ser expresso como:

$1011$ = $1 \times 2^3 + 0 \times 2^2 + 1 \times 2^1 + 1 \times 2^0$ = $8 + 0 + 2 + 1$ = $11$

Sistema Octal (Base 8)

O sistema octal utiliza oito dígitos: 0, 1, 2, 3, 4, 5, 6 e 7.

Cada posição em um número octal representa uma potência de 8.

O sistema octal é útil em computação porque é uma forma compacta de representar números binários.

Exemplo

O número 345 em base octal correspondente na base decimal pode ser expresso como:

$345$ = $3 \times 8^2 + 4 \times 8^1 + 5 \times 8^0$ = $172 + 32 + 5$ = $209$

Sistema Hexadecimal (Base 16)

O sistema hexadecimal é amplamente utilizado em programação e engenharia de computadores.

Ele utiliza dezesseis dígitos: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E e F, onde A representa 10, B representa 11, e assim por diante até F que representa 15.

Cada posição em um número hexadecimal representa uma potência de 16.

Exemplo

O número 1A3 em base hexadecimal pode ser expresso como:

$1A3$ = $1 \times 16^2 + 10 \times 16^1 + 3 \times 16^0$ = $256 + 160 + 48$ = $464$

Conversão Entre Bases

A conversão entre diferentes bases é um processo essencial em computação. Aqui estão alguns métodos comuns:

  • Decimal para Binário: Dividir o número decimal por 2 repetidamente e anotar os restos. O número binário é formado pelos restos lidos de baixo para cima.
  • Binário para Decimal: Multiplicar cada dígito binário pela potência de 2 correspondente e somar os resultados.
  • Decimal para Octal: Dividir o número decimal por 8 repetidamente e anotar os restos. O número octal é formado pelos restos lidos de baixo para cima.
  • Octal para Decimal: Multiplicar cada dígito octal pela potência de 8 correspondente e somar os resultados.
  • Decimal para Hexadecimal: Dividir o número decimal por 16 repetidamente e anotar os restos. O número hexadecimal é formado pelos restos lidos de baixo para cima.
  • Hexadecimal para Decimal: Multiplicar cada dígito hexadecimal pela potência de 16 correspondente e somar os resultados.

Decimal para Binário

A função decimal_para_binario converte um número decimal em um número binário, com entrada de um número decimal e saída do texto binário convertido.

  • Se o número decimal for 0, retorna "0".
  • Inicializa um texto binario vazio.
  • Enquanto o número decimal for maior que 0:
    • Calcula o resto da divisão do número decimal por 2.
    • Adiciona o resto à string binario (no início do texto).
    • Atualiza o número decimal para o quociente da divisão inteira por 2.
  • Retorna o texto binario.
def decimal_para_binario(decimal):
    if decimal == 0:
        return "0"
    binario = ""
    while decimal > 0:
        resto = decimal % 2
        binario = str(resto) + binario
        decimal = decimal // 2
    return binario

Exemplo: converter o número decimal 45 para binário.

numero_decimal = 45
numero_binario = decimal_para_binario(numero_decimal)
print(f"O número decimal {numero_decimal} em binário é {numero_binario}")

Este exemplo mostra a conversão de 45 (decimal) para 101101 (binário).

Você pode testar com outros números decimais para ver suas representações binárias.

print(decimal_para_binario(75))
print(decimal_para_binario(298))
print(decimal_para_binario(765))
print(decimal_para_binario(1623))

Decimal para Octal

A função decimal_para_octal converte um número decimal em um número octal, com entrada de um número decimal e saída do texto octal convertido.

  • Se o número decimal for 0, retorna "0".
  • Inicializa um texto octal vazio.
  • Enquanto o número decimal for maior que 0:
    • Calcula o resto da divisão do número decimal por 8.
    • Adiciona o resto à string octal (no início do texto).
    • Atualiza o número decimal para o quociente da divisão inteira por 8.
  • Retorna o texto octal.
def decimal_para_octal(decimal):
    if decimal == 0:
        return "0"
    octal = ""
    while decimal > 0:
        resto = decimal % 8
        octal = str(resto) + octal
        decimal = decimal // 8
    return octal

Exemplo: converter o número decimal 345 para octal.

numero_decimal = 345
numero_octal = decimal_para_octal(numero_decimal)
print(f"O número decimal {numero_decimal} em octal é {numero_octal}")

Este exemplo mostra a conversão de 345 (decimal) para 531 (octal).

Você pode testar com outros números decimais para ver suas representações octais.

Decimal para Hexadecimal

A função decimal_para_hexadecimal converte um número decimal em um número hexadecimal, com entrada de um número decimal e saída do texto hexadecimal convertido.

  • Se o número decimal for 0, retorna "0".
  • Define o texto hex_digits que contém os dígitos hexadecimais de 0 a F.
  • Inicializa um texto hexadecimal vazio.
  • Enquanto o número decimal for maior que 0:
    • Calcula o resto da divisão do número decimal por 16.
    • Adiciona o dígito hexadecimal correspondente ao resto à string hexadecimal (no início do texto).
    • Atualiza o número decimal para o quociente da divisão inteira por 16.
  • Retorna o texto hexadecimal.
def decimal_para_hexadecimal(decimal):
    if decimal == 0:
        return "0"
    hex_digits = "0123456789ABCDEF"
    hexadecimal = ""
    while decimal > 0:
        resto = decimal % 16
        hexadecimal = hex_digits[resto] + hexadecimal
        decimal = decimal // 16
    return hexadecimal

Exemplo: converter o número decimal 419 para hexadecimal.

numero_decimal = 419
numero_hexadecimal = decimal_para_hexadecimal(numero_decimal)
print(f"O número decimal {numero_decimal} em hexadecimal é {numero_hexadecimal}")

Este exemplo mostra a conversão de 419 (decimal) para 1A3 (hexadecimal).

Você pode testar com outros números decimais para ver suas representações hexadecimais.

Binário para Decimal

A função binario_para_decimal converte um número binário em um número decimal, com entrada de um número binário e saída do número decimal convertido.

  • Inicializa decimal como 0.
  • Inverte o texto binário para facilitar o cálculo das potências de 2.
  • Itera sobre cada dígito do número binário invertido:
    • Converte o dígito para inteiro.
    • Multiplica pela potência de 2 correspondente.
    • Soma o resultado ao valor de decimal.
  • Retorna o valor decimal.
def binario_para_decimal(binario):
    decimal = 0
    binario = binario[::-1]  # Inverte o texto para facilitar o cálculo
    for i in range(len(binario)):
        decimal += int(binario[i]) * (2 ** i)
    return decimal

Exemplo: converter o número binário 101101 para decimal.

numero_binario = "101101"
numero_decimal = binario_para_decimal(numero_binario)
print(f"O número binário {numero_binario} em decimal é {numero_decimal}")

Este exemplo mostra a conversão de 101101 (binário) para 45 (decimal).

Você pode testar com outros números binários para ver suas representações decimais.

Octal para Decimal

A função octal_para_decimal converte um número octal em um número decimal, com entrada de um número octal e saída do número decimal convertido.

  • Inicializa decimal como 0.
  • Inverte o texto octal para facilitar o cálculo das potências de 8.
  • Itera sobre cada dígito do número octal invertido:
    • Converte o dígito para inteiro.
    • Multiplica pela potência de 8 correspondente.
    • Soma o resultado ao valor de decimal.
  • Retorna o valor decimal.
def octal_para_decimal(octal):
    decimal = 0
    octal = str(octal)[::-1]  # Inverte o texto para facilitar o cálculo
    for i in range(len(octal)):
        decimal += int(octal[i]) * (8 ** i)
    return decimal

Exemplo: converter o número octal 531 para decimal.

numero_octal = "531"
numero_decimal = octal_para_decimal(numero_octal)
print(f"O número octal {numero_octal} em decimal é {numero_decimal}")

Este exemplo mostra a conversão de 531 (octal) para 345 (decimal).

Você pode testar com outros números octais para ver suas representações decimais.

Hexadecimal para Decimal

A função hexadecimal_para_decimal converte um número hexadecimal em um número decimal, com entrada de um número hexadecimal e saída do número decimal convertido.

  • Define o texto hex_digits que contém os dígitos hexadecimais de 0 a F.
  • Inicializa decimal como 0.
  • Inverte o texto hexadecimal para facilitar o cálculo das potências de 16 e converte o texto para maiúsculas para garantir a consistência.
  • Itera sobre cada dígito do número hexadecimal invertido:
    • Encontra o índice do dígito hexadecimal em hex_digits.
    • Multiplica pela potência de 16 correspondente.
    • Soma o resultado ao valor de decimal.
  • Retorna o valor decimal.
def hexadecimal_para_decimal(hexadecimal):
    hex_digits = "0123456789ABCDEF"
    decimal = 0
    hexadecimal = hexadecimal.upper()[::-1]  # Inverte o texto para facilitar o cálculo e converte para maiúsculas
    for i in range(len(hexadecimal)):
        decimal += hex_digits.index(hexadecimal[i]) * (16 ** i)
    return decimal

Exemplo: converter o número hexadecimal 1A3 para decimal.

numero_hexadecimal = "1A3"
numero_decimal = hexadecimal_para_decimal(numero_hexadecimal)
print(f"O número hexadecimal {numero_hexadecimal} em decimal é {numero_decimal}")

Este exemplo mostra a conversão de 1A3 (hexadecimal) para 419 (decimal).

Você pode testar com outros números hexadecimais para ver suas representações decimais.

Adição binária

A adição binária é similar à adição decimal, mas é realizada com números em base 2 (binária), onde os únicos dígitos são 0 e 1.

Aqui está uma explicação passo a passo de como funciona a adição binária, seguida por um exemplo prático.

Regras Básicas da Adição Binária:

  • 0 + 0 = 0
  • 0 + 1 = 1
  • 1 + 0 = 1
  • 1 + 1 = 0 (com transporte de 1 para a próxima coluna)

Quando dois 1s são somados, o resultado é 0 e um 1 é transportado para a próxima coluna, similar ao que ocorre quando somamos 9 + 1 em decimal, resultando em 0 com um transporte de 1.

Exemplo

Vamos adicionar dois números binários: 1011 e 1101.

  1011
+ 1101
------

Passo a Passo:

Somamos a primeira coluna da direita para esquerda (2^0) do primeiro e segundo operandos (1 e 1) e colocamos o resultado na quarta coluna da soma.

1 + 1 = 10 (escreva 0, transporte 1)

  1011 (primeiro operando)
+ 1101 (segundo operando)
------
     0 (coloque)
    1  (transporte 1)

Somamos a segunda coluna da direita para esquerda (2^1) do primeiro e segundo operandos mais o transporte (1, 0 e 1) e colocamos o resultado na terceira coluna da soma.

  1011 (primeiro operando 1)
+ 1101 (segundo operando 0)
+   1  (transportado 1)
------
    00 (coloque 0)
   1   (transporte 1)

Somamos a terceira coluna da direita para esquerda (2^2) do primeiro e segundo operandos mais o transporte (1, 0 e 1) e colocamos o resultado na segunda coluna da soma.

  1011 (primeiro operando)
+ 1101 (segundo operando)
+  1   (transportado)
------
   000  (coloque 0)
  1     (transporte 1)

Somamos a quarta coluna da direita para esquerda (2^3) do primeiro e segundo operandos mais o transporte (1, 1 e 1) e colocamos o resultado na primeira coluna da soma.

  1011
+ 1101
+ 1    (transportado)
------
  1000 (transporte 1)

Colocamos na quinta coluna da direita para esquerda da soma (2^4) o transporte de 1 da etapa anterior.

   1011
+  1101
+ 1     (transportado)
------
  11000

Portanto, o resultado da adição binária de 1011 e 1101 é 11000.

Recapitulando:

  • A adição binária segue as mesmas regras básicas da adição decimal, mas aplicada à base 2.
  • Quando o resultado de uma soma é 2 (10 em binário), escreve-se 0 e transporta-se 1 para a próxima coluna.
  • Se houver um transporte na coluna mais à esquerda, ele é simplesmente colocado à frente do número resultante.
Algoritmo
def adicao_binaria(a, b):
    max_len = max(len(a), len(b))
    
    # Padroniza as strings binárias para terem o mesmo comprimento
    a = a.zfill(max_len)
    b = b.zfill(max_len)
    
    resultado = ''
    transporte = 0
    
    # Realiza a adição da direita para a esquerda
    for i in range(max_len - 1, -1, -1):
        soma = int(a[i]) + int(b[i]) + transporte
        if soma == 0:
            resultado = '0' + resultado
            transporte = 0
        elif soma == 1:
            resultado = '1' + resultado
            transporte = 0
        elif soma == 2:
            resultado = '0' + resultado
            transporte = 1
        elif soma == 3:
            resultado = '1' + resultado
            transporte = 1
    
    # Se houver um transporte restante, adiciona ao resultado
    if transporte:
        resultado = '1' + resultado
    
    return resultado

Exemplo: adicionar os números 1011 e 1101.

binario1 = '1011'
binario2 = '1101'
resultado = adicao_binaria(binario1, binario2)
print(f"A soma binária de {binario1} e {binario2} é {resultado}")

Este exemplo mostra a adição binária de 1011 e 1101 resultando em 11000.

Você pode testar com outros números binários para praticar a adição binária.

Multiplicação binária

A multiplicação binária é semelhante à multiplicação decimal, mas é realizada com números base 2.

Aqui está uma explicação detalhada do processo, seguida por um exemplo prático.

Regras Básicas da Multiplicação Binária:

  • 0 × 0 = 0
  • 0 × 1 = 0
  • 1 × 0 = 0
  • 1 × 1 = 1

A multiplicação binária é feita através de somas e deslocamentos, assim como na multiplicação decimal, mas é mais simples devido à base 2.

Exemplo

Vamos multiplicar dois números binários: 101 e 11.

Passo a Passo:

Escreva os números um abaixo do outro:

   101
 ×  11

Multiplique cada dígito do número de baixo pelo número de cima, deslocando para a esquerda a cada linha.

Multiplicação do primeiro operando pelo primeiro número binário, da direita para a esquerda, do segundo operando.

   101
 ×   1
   ---
   101 (101 × 1)

Multiplicação do primeiro operando pelo segundo número binário, da direita para a esquerda, do segundo operando.

    101
 ×   1
   ---
   1010 (101 × 1 deslocado uma posição para a esquerda)

Agora somamos os resultados obtidos:

   101   (101 × 1)
+ 1010   (101 × 1, deslocado uma posição para a esquerda)
-----------
  1111

Portanto, o resultado da multiplicação binária de 101 e 11 é 1111.

Recapitulando:

  • Cada dígito do segundo número é multiplicado pelo primeiro número, semelhante à multiplicação decimal.
  • Cada linha resultante é deslocada para a esquerda, de acordo com a posição do dígito no segundo número.
  • Os resultados são então somados para obter o produto final.
Algoritmo

Aqui está um exemplo de como implementar a multiplicação binária em Python:

def binario_para_decimal(binario):
    decimal = 0
    binario = binario[::-1]  # Inverte o texto para facilitar o cálculo
    for i in range(len(binario)):
        decimal += int(binario[i]) * (2 ** i)
    return decimal

def decimal_para_binario(decimal):
    if decimal == 0:
        return "0"
    binario = ""
    while decimal > 0:
        resto = decimal % 2
        binario = str(resto) + binario
        decimal = decimal // 2
    return binario

def multiplicacao_binaria(a, b):
    # Converte binário para decimal
    num1 = binario_para_decimal(a)
    num2 = binario_para_decimal(b)
    
    # Multiplica os números decimais
    produto_decimal = num1 * num2
    
    # Converte o resultado de volta para binário
    produto_binario = decimal_para_binario(produto_decimal)

    return produto_binario

Exemplo: multiplicar os números 101 e 11.

binario1 = '101'
binario2 = '11'
resultado = multiplicacao_binaria(binario1, binario2)
print(f"A multiplicação binária de {binario1} e {binario2} é {resultado}")

Este exemplo mostra a multiplicação binária de 101 e 11 resultando em 1111.

Você pode testar com outros números binários para praticar a multiplicação binária.

Subtração binária

A subtração binária é semelhante à subtração decimal, mas é realizada com números base 2, seguindo regras específicas e usando o conceito de "empréstimo" entre as colunas, similar ao sistema decimal.

Regras Básicas da Subtração Binária:

  • 0 - 0 = 0
  • 1 - 0 = 1
  • 1 - 1 = 0
  • 0 - 1 = 1 (com um empréstimo de 1 da próxima coluna à esquerda)

Empréstimo na Subtração Binária:

Quando subtraímos 1 de 0, precisamos emprestar 1 da próxima coluna à esquerda.

Isso é similar ao que fazemos na subtração decimal quando subtraímos um número maior de um menor.

Em binário, emprestar 1 significa que estamos emprestando 2 em termos decimais.

Exemplo

Vamos subtrair dois números binários: 1101 (13 em decimal) e 101 (5 em decimal).

  1101
- 0101
------

Passo a Passo:

Primeira coluna da direita para a esquerda (2^0): 1 − 1 = 0

  1101
- 0101
------
     0

Segunda coluna da direita para a esquerda (2^1): 0 − 0 = 0

  1101
- 0101
------
    00

Terceira coluna da direita para a esquerda (2^2): 1 − 1 = 0

  1101
- 0101
------
   000

Quarta coluna da direita para a esquerda (2^3): 1 − 0 = 1

  1101
- 0101
------
  1000

Portanto, o resultado da subtração binária de 1101 é 101 é 1000.

Algoritmo
def subtracao_binaria(a, b):
    max_len = max(len(a), len(b))
    
    # Padroniza as strings binárias para terem o mesmo comprimento
    a = a.zfill(max_len)
    b = b.zfill(max_len)
    
    resultado = ''
    emprestimo = 0
    
    # Realiza a subtração da direita para a esquerda
    for i in range(max_len - 1, -1, -1):
        bit_a = int(a[i])
        bit_b = int(b[i]) + emprestimo

        if bit_a < bit_b:
            bit_a += 2
            emprestimo = 1
        else:
            emprestimo = 0
        
        resultado = str(bit_a - bit_b) + resultado
    
    # Remove os zeros à esquerda no resultado
    resultado = resultado.lstrip('0')
    
    if resultado == '':
        resultado = '0'
    
    return resultado

Exemplo: subtrair os números 1101 e 101.

binario1 = '1101'
binario2 = '101'
resultado = subtracao_binaria(binario1, binario2)
print(f"A subtração binária de {binario1} e {binario2} é {resultado}")

Este exemplo mostra a subtração binária de 1101 e 101 resultando em 1000.

Você pode testar com outros números binários para praticar a subtração binária.

Divisão binária

A divisão binária é semelhante à divisão decimal, mas é realizada com números base 2.

Assim como na divisão decimal, ela envolve encontrar quantas vezes um divisor pode caber em um dividendo, com subtrações e "deslocamentos" (shifts) ao longo do processo.

Passos para Divisão Binária usando o número 101001 base 2 (41 base 10) por 11 base 2 (3 base 10).

  • Dividendo: 101001
  • Divisor: 11

Passo a Passo:

Verificamos quantas vezes o divisor (11) cabe na mesma quantidade de números binários à esquerda do dividendo:

 11
(10)1001

O número binário 11 é maior que o número binário 10 (primeiros dois números binários do dividendo), então trazemos mais um número binário.

Agora considere os três primeiros bits (101).

  11
(101)001

11 cabe uma vez em 101 (5 em decimal).

 (10)1001 (quociente '')
: 11 (não cabe)
 (101)001 (traz 1 = próximo número binário à direita)
:  11 (cabe 1 vez, acrescenta 1 ao quociente) 
 -------
   (10)001 (quociente '1' resto 10)
   (100)01 (traz 0 = próximo número binário à direita)
     11 (cabe 1 vez, acrescenta 1 ao quociente)
   ------
     (1)01 (quociente '11' resto 1)
     (10)1 (traz 0 = próximo número binário à direita)
      11 (não cabe, acrescenta 0 ao quociente)
        ---
        (101) (quociente '110', traz 1 = próximo número binário à direita)
          11 (cabe 1 vez, acrescenta 1 ao quociente)
        ------
         (10) (quociente '1101' resto 10)
          11 (não cabe, não há mais números binários a serem divididos)

O resultado é:

  • Quociente: 1101
  • Resto: 10

Portanto, o resultado da divisão binária de 101001 por 11 é um quociente de 1101 (13 em decimal) e um resto de 10 (2 em decimal).

Algoritmo
def divisao_binaria(dividendo, divisor):
    # Converte binário para decimal
    num1 = binario_para_decimal(dividendo)
    num2 = binario_para_decimal(divisor)
    
    # Divide os números decimais
    quociente_decimal = num1 // num2
    resto_decimal = num1 % num2
    
    # Converte os resultados de volta para binário
    quociente_binario = decimal_para_binario(quociente_decimal)
    resto_binario = decimal_para_binario(resto_decimal)
    
    return quociente_binario, resto_binario

def binario_para_decimal(binario):
    decimal = 0
    binario = binario[::-1]  # Inverte o texto para facilitar o cálculo
    for i in range(len(binario)):
        decimal += int(binario[i]) * (2 ** i)
    return decimal

def decimal_para_binario(decimal):
    if decimal == 0:
        return "0"
    binario = ""
    while decimal > 0:
        resto = decimal % 2
        binario = str(resto) + binario
        decimal = decimal // 2
    return binario
Exemplo

Dividir o número binário 101001 pelo número binário 11.

dividendo = '101001'
divisor = '11'
quociente, resto = divisao_binaria(dividendo, divisor)
print(f"A divisão binária de {dividendo} por {divisor} resulta em quociente {quociente} e resto {resto}")

Este exemplo mostra a divisão binária de 101001 (41 base 10) por 11 (3 base 10), resultando em um quociente de 1101 (13 em decimal) e um resto de 10 (2 em decimal).

Você pode testar com outros números binários para praticar a divisão binária.

8.4.3 - Classe MatBin

A classe MatBin opera números binários, convertendo números entre as base binária (base 2) e decimal (base 10), realizando operações binárias aritméticas e de comparação.

Implementação de métodos e atributos da Classe MatBin:

  • Atributo num_bin: número binário.
  • Atributo num_str: número binário convertido em texto.
  • Atributo num_int: número binário convertido em um número inteiro.
  • Método __init__(): método construtor da classe, com um número binário como argumento.
  • Método atribuir_binario(): atribui um número binário aos atributos da classe.
  • Método binario_ok(): verifica se o número binário é valido.
  • Método bin_to_int: converte o número binário em um número inteiro.
  • Método int_to_bin(): converte um número inteiro em um número binário.
  • Métodos Especiais: os Métodos Especiais, também chamados Métodos Mágicos ou Métodos Dunder (de double-underscore), são aqueles herdados da classe object, iniciados e terminados com __, e que são utilizados para sobreposição e polimorfismo de operações matemáticas, lógicas, de atribuição, entre outras, em Python.

No construtor da classe, é passado um argumento que é o número binário.

O método atribuir_binario() é chamado no contrutor e verifica com o método binario_ok() se o número binário é valido.

Se o número binário é válido, o método atribuir_binario() atribui o número binário aos atributos da classe: num_bin, num_str e num_int.

Se o número binário é inválido, uma exceção é levantada.

A função bin_to_int converte o número binário em um número inteiro.

A função int_to_bin converte um número inteiro em um número binário.

Embora na realidade os valores dos números binários sejam armazenados em uma variável do tipo inteiro, este valor contém apenas os dígitos 0 e 1, representando os dígitos binários de um número inteiro convertido de decimal para binário.

Assim, por exemplo, o número binário 101101 na variável do tipo inteiro num_bin representa em decimal o número inteiro 75.

Declaração da Classe MatLab

A classe MatBin completa fica como a seguir:

class MatBin:

    apresentar_calculo = False

    def __init__(self, num_bin):
        self.atribuir_binario(num_bin)

    @classmethod
    def alterar_apresentar_calculo(cls, apresentar_calculo=True):
        cls.apresentar_calculo = apresentar_calculo

    def atribuir_binario(self, num_bin):
        num_str = str(num_bin)
        if self.binario_ok(num_str):
            self.num_bin = num_bin
            self.num_str = num_str
            self.num_int = int(f"0b{num_str}", 2)
        else:
            raise ValueError("Argumento inválido")

    def binario_ok(self, num_str):
        ok = True
        for digito in num_str:
            if not digito in ["0","1"]:
                ok = False
                break
        return ok

    def bin_to_int(self,num):
        if isinstance(num, MatBin):
            return num.num_int
        elif isinstance(num, int):
            num_str = str(num)
            ok = self.binario_ok(num_str)
            if not ok:
                raise ValueError("Argumento inválido")
            return int(f"0b{num_str}",2)
        else:
            raise ValueError("Argumento inválido")

    def int_to_bin(self,num):
        if isinstance(num, int):
            num_str = bin(num).split("b")[1]
            return int(num_str)
        else:
            raise ValueError("Argumento inválido")
   
    def dunder(self,fun,val):
        num_str = bin(int(val)).split("b")[1]
        num_bin = int(num_str)
        res = MatBin(num_str)
        #print(f"entrou em {fun}()")
        #print(f"bin({num_int}) = {num_bin}")
        return res

    def __str__(self): # a.__str__() | str(a)
        return self.num_str

    def __repr__(self): # a.__repr__() | repr(a)
        return f"MatBin({self.num_str})"
  
    def __eq__(self, outro): # a == outro | outro == a
        return self.dunder("__eq__",self.num_int == self.bin_to_int(outro))

    def __ne__(self, outro): # a != outro | outro != a
        return self.dunder("__ne__",self.num_int != self.bin_to_int(outro))
  
    def __lt__(self, outro): # a < outro | outro > a
        return self.dunder("__lt__",self.num_int < self.bin_to_int(outro))

    def __gt__(self, outro): # a > outro | outro < a
        return self.dunder("__gt__",self.num_int > self.bin_to_int(outro))

    def __le__(self, outro): # a <= outro | outro >= a
        return self.dunder("__le__",self.num_int <= self.bin_to_int(outro))

    def __ge__(self, outro): # a >= outro | outro <= a
        return self.dunder("__ge__",self.num_int >= self.bin_to_int(outro))

    def __add__(self, outro): # a + outro
        return self.dunder("__add__",self.num_int + self.bin_to_int(outro))

    def __radd__(self, outro): # outro + a
        return self.dunder("__radd__",self.bin_to_int(outro) + self.num_int)

    def __sub__(self, outro): # a - outro
        return self.dunder("__sub__",self.num_int - self.bin_to_int(outro))

    def __rsub__(self, outro): # outro - a
        return self.dunder("__rsub__",self.bin_to_int(outro) - self.num_int)

    def __mul__(self, outro): # a * outro
        return self.dunder("__mul__",self.num_int * self.bin_to_int(outro))

    def __rmul__(self, outro): # outro * a
        return self.dunder("__rmul__",self.bin_to_int(outro) * self.num_int)

    def __truediv__(self, outro): # a / outro
        return self.dunder("__truediv__",self.num_int / self.bin_to_int(outro))

    def __rtruediv__(self, outro): # outro / a
        return self.dunder("__rtruediv__",self.bin_to_int(outro) / self.num_int)

    def __floordiv__(self, outro): # a // outro
        return self.dunder("__floordiv___",self.num_int // self.bin_to_int(outro))

    def __rfloordiv__(self, outro): # outro // a
        return self.dunder("__rfloordiv__",self.bin_to_int(outro) // self.num_int)

    def __mod__(self, outro):
        return self.dunder("__mod__",self.num_int % self.bin_to_int(outro))

    def __rmod__(self, outro): # outro % a
        return self.dunder("__rmod__",self.bin_to_int(outro) % self.num_int)

    def __pow__(self, outro): # a ** outro
        return self.dunder("__pow__",self.num_int ** self.bin_to_int(outro))

    def __rpow__(self, outro): # outro ** a 
        return self.dunder("__rpow__",self.bin_to_int(outro) ** self.num_int)

    def __lshift__(self, outro): # a << outro
        return self.dunder("__lshift__",self.num_int << self.bin_to_int(outro))

    def __rlshift__(self, outro):
        return self.dunder("__rlshift__",self.bin_to_int(outro) << self.num_int)

    def __rshift__(self, outro): # a >> outro
        return self.dunder("__rshift__",self.num_int >> self.bin_to_int(outro))

    def __rrshift__(self, outro): # outro >> a
        return self.dunder("__rrshift__",self.bin_to_int(outro) >> self.num_int)

    def __and__(self, outro): # a & outro
        return self.dunder("__and__",self.num_int & self.bin_to_int(outro))

    def __rand__(self, outro): # outro & a
        return self.dunder("__rand__",self.bin_to_int(outro) & self.num_int)

    def __or__(self, outro): # a | outro
        return self.dunder("__or__",self.num_int | self.bin_to_int(outro))

    def __ror__(self, outro): # outro | a
        return self.dunder("__ror__",self.bin_to_int(outro) | self.num_int)

    def __xor__(self, outro): # a ^ outro
        return self.dunder("__xor__",self.num_int ^ self.bin_to_int(outro))

    def __rxor__(self, outro): # outro ^ a
        return self.dunder("__rxor__",self.bin_to_int(outro) ^ self.num_int)

    def __neg__(self): # -a
        return self.dunder("__neg__",self.int_to_bin(-self.num_int))

    def __pos__(self): # +a
        return self.dunder("__pos__",self.int_to_bin(self.num_int))

    def __abs__(self): # abs(a)
        return self.dunder("__abs__",self.int_to_bin(abs(self.num_int)))

    def __invert__(self): # ~a
        return self.dunder("_",self.int_to_bin(~self.num_int))
    
    def __round__(self, ndigits=None): # round(a,ndigits)
        return self.dunder("__round__",self.int_to_bin(round(self.num_int,ndigits)))

    def __trunc__(self): # trunc(a)
        return self.dunder("__trunc__",self.int_to_bin(trunc(self.num_int)))

    def __ceil__(self): # ceil(a)
        return self.dunder("__ceil__",self.int_to_bin(ceil(self.num_int)))

    def __floor__(self): # floor(a)
        return self.dunder("__floor__",self.int_to_bin(floor(self.num_int)))

    def __int__(self): # int(a)
        return self.dunder("__int__",self.num_int).num_int

    def __float__(self): # float(a)
        return float(self.dunder("__float__",self.num_int).num_int)

    def __complex__(self): # complex(a)
        return complex(self.dunder("__complex__",self.num_int).num_int)

    def __hex__(self): # hex(a)
        return hex(self.dunder("__hex__",self.num_int).num_int)

    def __oct__(self): # oct(a)
        return oct(self.dunder("__oct__",self.num_int).num_int)

    def __bool__(self): # bool(a)
        return bool(self.dunder("__bool__",self.num_int).num_int)

    def __index__(self): # index(a)
        return self.dunder("__index__",self.num_int).num_int

utilização da classe MatBin

Cria uma variável a do tipo MatBin com o valor 10110 base 2 (22 base 10).

a = MatBin(10110)
print(f"A variável 'a' foi criada com a instrução 'a = MatBin(10110)', e 'a' é do tipo {type(a)}")
print(f"O valor binário da variável 'a' é {a}, e o valor inteiro da variável 'a' é 'a.__int__() = {a.__int__()}' ou 'int(a) = {int(a)}'")

Cria uma variável b do tipo MatBin com o valor 101 base 2 (10 base 10).

b = a + 101
print(f"A variável 'b' foi criada com a instrução 'b = a + 101', e 'b' é do tipo {type(b)}")
print(f"O valor binário da variável 'b' é {b}, e o valor inteiro da variável 'b' é {b.__int__()}")

Cria uma variável c do tipo MatBin com o valor 101 base 2 (10 base 10).

c = 101 + a
print(f"A variável 'c' foi criada com a instrução 'c = 101 + a', e 'c' é do tipo {type(c)}")
print(f"O valor binário da variável 'c' é {c}, e o valor inteiro da variável 'c' é {c.__int__()}")

Exemplo

Declaramos as variáveis binárias a e b.

a = MatBin(10010111)
b = MatBin(11)

Criamos uma função operacao() especializada em avaliar o texto de comando, usando a função eval(), para operações de adição, subtração, multiplicação, divisão etc, e imprimir o resultado.

def operacao(texto_comando, a=a, b=b):
    operador = texto_comando.split(" ")[1] if " " in texto_comando else texto_comando[0]
    resultado = eval(texto_comando)
    if MatBin.apresentar_calculo:
        print()
        if " " in texto_comando:
            tammax = max(len(a.num_str),len(b.num_str),len(str(resultado)))
            print(f"{a.num_str.zfill(tammax)}")
            print(f"{b.num_str.zfill(tammax)} {operador}")
            print("-" * tammax)
        else:
            tammax = max(len(a.num_str),len(str(resultado)))
            print(f"{a.num_str.zfill(tammax)} {operador}")
            print("-" * tammax)
        print(resultado.num_str.zfill(tammax))
        print()
    print(f"{texto_comando} == {resultado} ({resultado.__int__()})")

Realizamos uma série de operações usando as variáveis a e b.

def imprimir_operacoes_binarias(a,b):
    print(f"a = {a} base 2 ({a.__int__()} base 10)")
    print(f"b = {b} base 2 ({b.__int__()} base 10)")
    operacao("a + b")
    operacao("a - b")
    operacao("a * b")
    operacao("a / b")
    operacao("a // b")
    operacao("a % b")
    operacao("a ** b")
    operacao("a << b")
    operacao("a >> b")
    operacao("a & b")
    operacao("a | b")
    operacao("a ^ b")
    operacao("a == b")
    operacao("a != b")
    operacao("a < b")
    operacao("a <= b")
    operacao("a > b")
    operacao("a >= b")
    operacao("~a")

Imprimimos as funções binárias sem apresentar os cálculos, já que o atributo de classe apresentar_calculo tem valor False.

imprimir_operacoes_binarias(a,b)

Agora inverteremos o valor do atributo de classe apresentar_calculo para True e imprimiremos as operações.

MatBin.apresentar_calculo = True
imprimir_operacoes_binarias(a,b)

Exemplo de métodos estáticos de classe

Exemplo de variáveis estáticas criando uma classe Mat:

class Mat:
    @staticmethod
    def soma(a, b):
        return a + b

    @staticmethod
    def subtracao(a, b):
        return a - b

    @staticmethod
    def multiplicacao(a, b):
        return a * b

    @staticmethod
    def divisao(a, b):
        return a / b

Exemplos:

a = 10
b = 3
print(f"a = {a}")
print(f"b = {b}")
print()
print(f"Mat.soma(a, b) = {Mat.soma(a, b)}")
print(f"Mat.subtracao(a, b) = {Mat.subtracao(a, b)}")
print(f"Mat.multiplicacao(a, b) = {Mat.multiplicacao(a, b)}")
print(f"Mat.divisao(a, b) = {Mat.divisao(a, b)}")
Arduino
Coautor
Betobyte
Autor
Autores
||| Áreas ||| Estatística ||| Python ||| Projetos ||| Dicas & Truques ||| Quantum ||| Python Básico || Python para Iniciantes || Python Básico || Matplotlib || Numpy || Seaborn || Pandas || Django || Estatística para Cientistas de Dados || Python com ML Básico || Python com ML Básico || Aulas | Introdução (sintaxe, comentários, variáveis, tipos) | Instalação (instalação, execução) | Sintaxe e semântica (sintaxe, semântica, variáveis, tipos de dados, blocos e recuos, comentários) | Tipos (números, strings, booleanos, datas, listas, tuplas, conjuntos, dicionarios, conversão) | Operadores (aritméticos, atribuição, comparação, lógicos, identidade, associação, bitwise) | Blocos (se-senão, laços, funções, classes, iteradores, escopo) | Funções (Funções nativas do Python) | Classes (Classes e Objetos no Python) | Entrada/Saída (teclado, tela, arquivo) | Módulos (pip, instalação, importação) |