Pular para o conteúdo principal

Encapsulamento em Python: Um guia abrangente

Aprenda os fundamentos da implementação de encapsulamento na programação orientada a objetos em Python.
Actualizado 12 de dez. de 2024  · 11 min de leitura

O encapsulamento é um princípio fundamental orientado a objetos em Python. Ele protege suas classes contra alterações ou exclusões acidentais e promove a reutilização e a manutenção do código. Considere esta definição de classe simples:

class Smartphone:
   def __init__(self, brand, os):
       self.brand = brand
       self.os = os

iphone = Smartphone("Apple", "iOS 17")

Muitos programadores Python definem classes como essa. No entanto, isso está longe de ser as práticas recomendadas que os profissionais de Python seguem. O problema com essa classe fica evidente quando você tenta modificar seus dados:

iphone.os = "Android"
print(iphone.os)
Android

Imagine um iPhone rodando no Android - que ultraje! Claramente, precisamos definir alguns limites em nossa classe para que os usuários não possam alterar seus atributos para o que quiserem.

Se eles decidirem alterá-las, as alterações deverão ser feitas de acordo com nossos termos, seguindo nossas regras. Mas ainda queremos que os atributos .os ou .brand permaneçam os mesmos na superfície.

Todas essas coisas podem ser promovidas usando o encapsulamento e, neste tutorial, aprenderemos tudo sobre isso. Vamos começar!

Como o encapsulamento é obtido em Python?

O Python oferece suporte ao encapsulamento por meio de convenções e práticas de programação, em vez de modificadores de acesso impostos. Você sabe, o princípio fundamental por trás de grande parte do design do código Python é "Somos todos adultos aqui". Só podemos implementar o encapsulamento como uma mera convenção e esperar que outros desenvolvedores de Python confiem e respeitem nosso código.

Em outras linguagens OOP, como Java e C++, o encapsulamento é estritamente imposto com modificadores de acesso, como public, private ou protected, mas o Python não tem isso:

Recurso Python Java C++
Modificadores de acesso Nenhum modificador imposto; usa convenções de nomenclatura (_protected__private) Aplicado com publicprotectedprivate keywords Aplicado com publicprotectedprivate keywords
Métodos getter/setter Opcional, geralmente usado com @property decorador para acesso controlado Prática comum, normalmente implementada como métodos Prática comum, normalmente implementada como métodos
Acesso aos dados Acessível por meio de convenções de nomenclatura; depende da disciplina do desenvolvedor Controlado por modificadores de acesso; aplicado pelo compilador Controlado por modificadores de acesso; aplicado pelo compilador
Philosophy "Somos todos adultos aqui" - baseia-se em convenções e confiança Aplicação rigorosa do controle de acesso Aplicação rigorosa do controle de acesso

Portanto, a maioria, se não todas, as técnicas de encapsulamento que estou prestes a mostrar a você são convenções do Python. Eles podem ser facilmente quebrados se você decidir. Mas acredito que você as respeita e as segue em seus próprios projetos de desenvolvimento.

Modificadores de acesso em Python

Digamos que você tenha esta classe simples:

class Tree:
   def __init__(self, height):
       self.height = height

pine = Tree(20)
print(pine.height)
20

Ele tem um único atributo de altura que podemos imprimir. O problema é que também podemos alterá-lo para o que quisermos:

pine.height = 50
pine.height
50
pine.height = "Grandma"
pine.height
'Grandma'

Então, como informamos aos usuários que a alteração da altura está fora dos limites? Bem, podemos transformá-lo em um membro protegido adicionando um único sublinhado antes dele:

class Tree:
   def __init__(self, height):
       self._height = height

pine = Tree(20)
pine._height
20

Agora, as pessoas que estão cientes dessa convenção saberão que só podem acessar o atributo e que não estamos incentivando-as a usá-lo e modificá-lo. Mas se eles quiserem, podem modificá-lo, sim.

Então, como podemos evitar isso também? Usando outra convenção, você pode transformar o atributo em um membro privado adicionando dois sublinhados anteriores:

class Tree:
   def __init__(self, height):
       self.__height = height


pine = Tree(20)
pine.__height
AttributeError: 'Tree' object has no attribute '__height'

Agora, o Python emitirá um erro se alguém tentar acessar o atributo, e muito menos modificá-lo. Você pode usar o atributo para modificar o atributo.

Mas você percebeu o que acabamos de fazer? Escondemos dos usuários as únicas informações relacionadas aos nossos objetos. Nossa classe acabou de se tornar inútil porque não tem atributos públicos.

Então, como podemos expor a altura da árvore aos usuários e ainda controlar como eles são acessados e modificados? Por exemplo, queremos que a altura das árvores esteja dentro de um intervalo específico e tenha apenas valores inteiros. Como podemos aplicar isso?

Neste ponto, talvez você tenha um amigo que usa Java e sugira o uso de métodos getter e setter. Então, vamos tentar isso primeiro:

class Tree:
   def __init__(self, height):
       self.__height = height

   def get_height(self):
       return self.__height

   def set_height(self, new_height):
       if not isinstance(new_height, int):
           raise TypeError("Tree height must be an integer")
       if 0 < new_height <= 40:
           self.__height = new_height
       else:
           raise ValueError("Invalid height for a pine tree")


pine = Tree(20)
pine.get_height()
20

Dessa forma, você cria um atributo privado __height, mas permite que os usuários o acessem e modifiquem de forma controlada usando os métodos get_height e set_height.

pine.set_height(25)

pine.get_height()
25

Antes de definir um novo valor, o site set_height garante que a nova altura esteja dentro de um determinado intervalo e seja numérica.

pine.set_height("Password")
TypeError: Tree height must be an integer

Mas esses métodos parecem exagerados para uma operação simples. Além disso, é feio escrever um código como esse:

# Increase height by 5
pine.set_height(pine.get_height() + 5)

Não seria mais bonito e legível se pudéssemos escrever esse código?

pine.height += 5

e ainda assim aplicar o tipo de dados e o intervalo corretos para a altura? A resposta é sim e aprenderemos como fazer isso na próxima seção.

Usando o decorador @property em classes Python

Apresentamos uma nova técnica: criar propriedades para atributos:

class Tree:
   def __init__(self, height):
       # First, create a private or protected attribute
       self.__height = height

   @property
   def height(self):
       return self.__height

pine = Tree(17)
pine.height
17

Queremos que os usuários acessem um atributo oculto chamado __height como se fosse um atributo normal chamado height. Para isso, definimos um método chamado height que retorna self.__height e o decoramos com @property.

Agora, podemos chamar height e acessar o atributo privado:

pine.height
17

Mas a melhor parte é que os usuários não podem modificá-lo:

pine.height = 15
AttributeError: can't set attribute 'height'

Portanto, adicionamos outro método chamado height(self, new_height) que é envolvido por um decorador height.setter. Dentro desse método, implementamos a lógica que impõe o tipo de dados e o intervalo desejados para a altura:

class Tree:
   def __init__(self, height):
       self.__height = height

   @property
   def height(self):
       return self.__height

   @height.setter
   def height(self, new_height):
       if not isinstance(new_height, int):
           raise TypeError("Tree height must be an integer")
       if 0 < new_height <= 40:
           self.__height = new_height
       else:
           raise ValueError("Invalid height for a pine tree")

Agora, quando um usuário tenta modificar o atributo height, @height.setter é chamado, garantindo assim que o valor correto seja passado:

pine = Tree(10)

pine.height = 33  # Calling @height.setter
pine.height = 45  # An error is raised
ValueError: Invalid height for a pine tree

Também podemos personalizar a forma como o atributo height é acessado por meio da anotação de ponto com @height.getter:

class Tree:
   def __init__(self, height):
       self.__height = height

   @property
   def height(self):
       return self.__height

   @height.getter
   def height(self):
       # You can return a custom version of height
       return f"This tree is {self.__height} meters"


pine = Tree(33)

pine.height
'This tree is 33 meters'

Embora tenhamos criado pine com uma altura inteira, podemos modificar seu valor com @height.getter.

Esses são exemplos de como podemos promover o encapsulamento em uma classe Python. Lembre-se de que o encapsulamento ainda é uma convenção porque ainda podemos quebrar o membro privado interno __height:

pine._Tree__height = "Gotcha!"

pine.height
'This tree is Gotcha! meters'

Tudo nas classes Python é público, assim como os métodos privados. Não se trata de uma falha de design, mas de um exemplo da abordagem "Somos todos adultos aqui".

Aqui está uma visão geral dos modificadores de acesso Python que acabamos de analisar:

Modificador/Convenção Descrição Exemplo
Público Nível de acesso padrão; atributos e métodos são acessíveis de fora da classe self.attribute
Protegido Indicado por um único sublinhado; uma convenção para indicar acesso parcialmente restrito self._attribute
Privado Indicado por sublinhados duplos; a adulteração do nome fornece restrição de acesso limitado self.__attribute
Propriedades Fornece acesso controlado a atributos privados usando o @property decorator @property def attribute(self):
Propriedades somente leitura Não @attribute.setterpermite acesso somente leitura a um atributo @property def attribute(self):

Práticas recomendadas ao implementar o encapsulamento

Há várias práticas recomendadas que você pode seguir para garantir que seu código se alinhe bem ao código escrito por outros OOPistas experientes:

  1. Crie atributos ou métodos protegidos ou privados se eles forem usados somente por você. Os membros protegidos ou privados são excluídos da documentação e indicam aos outros que podem ser alterados por você, o desenvolvedor, sem aviso prévio, desencorajando-os a usá-los.
  2. Você nem sempre precisa criar propriedades para cada atributo de classe. Para classes grandes com muitos atributos, escrever os métodos attr.getter e attr.setter pode se tornar uma dor de cabeça.
  3. Considere a possibilidade de emitir um aviso sempre que um usuário acessar um membro protegido (_).
  4. Use membros privados com moderação, pois eles podem tornar o código ilegível para quem não está familiarizado com a convenção.
  5. Priorize a clareza em vez da obscuridade. Como o objetivo do encapsulamento é melhorar a manutenção do código e a proteção dos dados, não oculte completamente os detalhes importantes da implementação da sua classe.
  6. Se você quiser criar propriedades somente de leitura, não implemente o método @attr.setter. Os usuários poderão acessar a propriedade, mas não modificá-la.
  7. Lembre-se sempre de que o encapsulamento é uma convenção, não um aspecto obrigatório da sintaxe do Python.
  8. Para classes simples, considere o uso de dataclasses que permitem que você habilite o encapsulamento de classes com uma única linha de código. No entanto, as classes de dados são para classes mais simples com atributos e métodos previsíveis. Para saber mais, confira este tutorial sobre dataclasses.

Conclusão e recursos adicionais

Neste tutorial, aprendemos um dos principais pilares da programação orientada a objetos em Python: o encapsulamento.

O encapsulamento permite que você defina o acesso controlado aos dados armazenados nos objetos da sua classe. Isso permite que você escreva um código limpo, legível e eficiente e evite alterações ou exclusões acidentais dos dados da classe.

Aqui estão mais alguns recursos relacionados para você aprimorar seu conhecimento sobre OOP:

Aprenda Python do zero

Domine o Python para a ciência de dados e adquira habilidades que estão em alta.
Comece a Aprender De Graça

Perguntas frequentes

Como o encapsulamento difere da abstração em Python?

O encapsulamento consiste em agrupar dados e métodos que operam nos dados em uma unidade, como uma classe, e restringir o acesso a alguns componentes. A abstração, por outro lado, consiste em ocultar a realidade complexa e expor apenas as partes essenciais. Enquanto o encapsulamento se concentra em restringir o acesso, a abstração se concentra em simplificar as interações.

O encapsulamento pode ser usado na programação funcional ou é exclusivo da OOP?

O encapsulamento é um conceito intimamente ligado à programação orientada a objetos (OOP) e não é normalmente usado na programação funcional. A programação funcional enfatiza a imutabilidade e as funções sem estado, ao contrário da OOP, que se concentra em objetos e no encapsulamento de dados dentro desses objetos.

Quais são algumas armadilhas comuns que você deve evitar ao implementar o encapsulamento em Python?

As armadilhas comuns incluem o uso excessivo de variáveis privadas, o que pode dificultar a manutenção do código, e o não fornecimento de métodos de acesso adequados (getters/setters), o que pode levar a um código frágil. Além disso, os desenvolvedores podem esquecer que o encapsulamento do Python é feito por convenção e, portanto, pode ser contornado.

Como o encapsulamento pode melhorar a segurança do código?

O encapsulamento pode melhorar a segurança do código ao restringir o acesso ao estado interno de um objeto e impedir modificações não autorizadas. Ao controlar como os dados são acessados e modificados, os desenvolvedores podem garantir que os dados permaneçam válidos e consistentes.

Como o uso do decorador @property afeta o desempenho em Python?

O uso do decorador@property pode introduzir uma pequena sobrecarga devido às chamadas de método extras, mas, na maioria dos casos, esse impacto é insignificante. Os benefícios de uma melhor legibilidade e manutenção do código geralmente superam qualquer custo menor de desempenho.

É possível impor o encapsulamento estritamente em Python como em Java ou C++?

O Python não impõe o encapsulamento estritamente como o Java ou o C++. Sua filosofia é baseada em convenções e no entendimento de que os desenvolvedores respeitarão o uso pretendido de membros privados e protegidos. No entanto, o uso de convenções de nomenclatura, como sublinhados, pode desencorajar o uso indevido.

Quais são algumas aplicações reais do encapsulamento em projetos Python?

O encapsulamento é usado para proteger dados confidenciais em aplicativos como software bancário, em que os detalhes da conta ficam ocultos. Ele também é usado em bibliotecas e estruturas para ocultar detalhes complexos de implementação, permitindo que os desenvolvedores interajam com interfaces simples.

Como posso testar o código encapsulado de forma eficaz em Python?

O teste de código encapsulado envolve escrever testes de unidade para a interface pública de uma classe. Ao testar os métodos e as propriedades expostos ao usuário, você garante que as alterações de estado interno sejam gerenciadas corretamente e que a lógica de encapsulamento seja mantida.

Você pode aplicar o encapsulamento aos módulos Python ou ele se limita às classes?

Embora o encapsulamento esteja associado principalmente a classes, o conceito pode ser estendido aos módulos usando funções ou variáveis com prefixo de sublinhado para sinalizar o uso privado em um módulo.

Como o encapsulamento se relaciona com o polimorfismo em Python?

O encapsulamento e o polimorfismo são princípios da POO, mas têm finalidades diferentes. O encapsulamento restringe o acesso a determinadas partes de um objeto, enquanto o polimorfismo permite que os objetos sejam tratados como instâncias de sua classe principal, facilitando a expansão e a manutenção do código. Eles podem ser usados em conjunto para criar estruturas de código flexíveis e seguras.


Bex Tuychiev's photo
Author
Bex Tuychiev
LinkedIn

Sou um criador de conteúdo de ciência de dados com mais de 2 anos de experiência e um dos maiores seguidores no Medium. Gosto de escrever artigos detalhados sobre IA e ML com um estilo um pouco sarcástico, porque você precisa fazer algo para torná-los um pouco menos monótonos. Produzi mais de 130 artigos e um curso DataCamp, e estou preparando outro. Meu conteúdo foi visto por mais de 5 milhões de pessoas, das quais 20 mil se tornaram seguidores no Medium e no LinkedIn. 

Temas

Continue sua jornada com Python hoje mesmo!

curso

Object-Oriented Programming in Python

4 hr
87.2K
Dive in and learn how to create classes and leverage inheritance and polymorphism to reuse and optimize code.
Ver DetalhesRight Arrow
Iniciar Curso
Ver maisRight Arrow
Relacionado
Data Skills

blog

6 práticas recomendadas de Python para um código melhor

Descubra as práticas recomendadas de codificação Python para escrever os melhores scripts Python da categoria.
Javier Canales Luna's photo

Javier Canales Luna

13 min

tutorial

Programação orientada a objetos em Python (OOP): Tutorial

Aborde os fundamentos da programação orientada a objetos (OOP) em Python: explore classes, objetos, métodos de instância, atributos e muito mais!
Théo Vanderheyden's photo

Théo Vanderheyden

12 min

tutorial

Desenvolvimento de back-end em Python: Um guia completo para iniciantes

Este guia completo ensina a você os fundamentos do desenvolvimento de back-end em Python. Aprenda conceitos básicos, estruturas e práticas recomendadas para você começar a criar aplicativos da Web.
Oluseye Jeremiah's photo

Oluseye Jeremiah

26 min

tutorial

Dados JSON em Python

Trabalhando com JSON em Python: Um guia passo a passo para iniciantes
Moez Ali's photo

Moez Ali

6 min

tutorial

Tutorial de funções Python

Um tutorial sobre funções em Python que aborda como escrever funções, como chamá-las e muito mais!
Karlijn Willems's photo

Karlijn Willems

14 min

tutorial

Tutorial de Python

Em Python, tudo é objeto. Números, cadeias de caracteres (strings), DataFrames, e até mesmo funções são objetos. Especificamente, qualquer coisa que você usa no Python tem uma classe, um modelo associado por trás.
DataCamp Team's photo

DataCamp Team

3 min

See MoreSee More