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:
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}")
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 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.
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 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:
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)
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"))
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.
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:
Aqui estão alguns métodos dunder comuns e seus propósitos.
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:
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:
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:
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:
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:
Explicação dos métodos:
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:
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:
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:
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:
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:
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:
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.
O atributo __match_args__ é usado para personalizar argumentos posicionais na correspondência de padrões de classe.
Métodos dunder para implementar as funções internas buffer() e releasebuffer():
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.
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.
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)
Os Métodos Especiais de Representação (MER) são utilizados na identificação e representação classes e objetos.
São:
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))
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:
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")
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:
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.
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]
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:
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")
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:
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.
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)")
Os Métodos Especiais de Conversão (MEV) permitem que os objetos sejam convertidos de forma conveniente para outros tipos.
São:
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)")
Os Métodos Especiais de Atributos (MEB) permitem que os objetos sejam atribuidos de forma conveniente.
São:
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
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')}")
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.
Decoradores de métodos aplicados a instâncias alteram o comportamento de chamadas de métodos.
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
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}")
O decorador @
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)
O decorador @
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
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)
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.
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)
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)
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.
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.
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.
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.
Divisão Repetida:
Iteração:
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).
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:
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.
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.
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.
O número 345 em base decimal pode ser expresso como:
$345 = 3 \times 10^2 + 4 \times 10^1 + 5 \times 10^0$
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.
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$
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.
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$
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.
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$
A conversão entre diferentes bases é um processo essencial em computação. Aqui estão alguns métodos comuns:
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.
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))
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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:
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.
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:
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.
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:
A multiplicação binária é feita através de somas e deslocamentos, assim como na multiplicação decimal, mas é mais simples devido à base 2.
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:
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.
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:
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.
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.
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.
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).
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 é:
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).
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
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.
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:
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.
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
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__()}")
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 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)}")