Introdução
Ao escrever classes, é comum tentar encapsular detalhes internos. Esta seção introduz alguns idioms de programação Python para isso, incluindo variáveis privadas e propriedades.
This tutorial is from open-source community. Access the source code
Ao escrever classes, é comum tentar encapsular detalhes internos. Esta seção introduz alguns idioms de programação Python para isso, incluindo variáveis privadas e propriedades.
Um dos papéis primários de uma classe é encapsular dados e detalhes internos de implementação de um objeto. No entanto, uma classe também define uma interface pública que o mundo exterior deve usar para manipular o objeto. Essa distinção entre detalhes de implementação e a interface pública é importante.
Em Python, quase tudo sobre classes e objetos é aberto.
Isso é um problema quando você está tentando isolar detalhes da implementação interna.
Python se baseia em convenções de programação para indicar o uso pretendido de algo. Essas convenções são baseadas em nomenclatura. Existe uma atitude geral de que cabe ao programador observar as regras, em vez de a linguagem impô-las.
Qualquer nome de atributo com um _ inicial é considerado privado.
class Person(object):
def __init__(self, name):
self._name = 0
Como mencionado anteriormente, isso é apenas um estilo de programação. Você ainda pode acessá-lo e alterá-lo.
>>> p = Person('Guido')
>>> p._name
'Guido'
>>> p._name = 'Dave'
>>>
Como regra geral, qualquer nome com um _ inicial é considerado uma implementação interna, seja uma variável, uma função ou um nome de módulo. Se você se encontrar usando esses nomes diretamente, provavelmente está fazendo algo errado. Procure por funcionalidades de nível superior.
Considere a seguinte classe.
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
Uma característica surpreendente é que você pode definir os atributos para qualquer valor:
>>> s = Stock('IBM', 50, 91.1)
>>> s.shares = 100
>>> s.shares = "hundred"
>>> s.shares = [1, 0, 0]
>>>
Você pode olhar para isso e pensar que precisa de algumas verificações extras.
s.shares = '50' ## Raise a TypeError, this is a string
Como você faria isso?
Uma abordagem: introduzir métodos acessores (accessor methods).
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.set_shares(shares)
self.price = price
## Function that layers the "get" operation
def get_shares(self):
return self._shares
## Function that layers the "set" operation
def set_shares(self, value):
if not isinstance(value, int):
raise TypeError('Expected an int')
self._shares = value
É uma pena que isso quebre todo o nosso código existente. s.shares = 50 torna-se s.set_shares(50)
Existe uma abordagem alternativa ao padrão anterior.
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
@property
def shares(self):
return self._shares
@shares.setter
def shares(self, value):
if not isinstance(value, int):
raise TypeError('Expected int')
self._shares = value
O acesso normal ao atributo agora aciona os métodos getter e setter sob @property e @shares.setter.
>>> s = Stock('IBM', 50, 91.1)
>>> s.shares ## Triggers @property
50
>>> s.shares = 75 ## Triggers @shares.setter
>>>
Com este padrão, não há alterações necessárias no código-fonte. O novo setter também é chamado quando há uma atribuição dentro da classe, inclusive dentro do método __init__().
class Stock:
def __init__(self, name, shares, price):
...
## This assignment calls the setter below
self.shares = shares
...
...
@shares.setter
def shares(self, value):
if not isinstance(value, int):
raise TypeError('Expected int')
self._shares = value
Frequentemente, há uma confusão entre uma propriedade e o uso de nomes privados. Embora uma propriedade use internamente um nome privado como _shares, o restante da classe (não a propriedade) pode continuar a usar um nome como shares.
As propriedades também são úteis para atributos de dados computados.
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
@property
def cost(self):
return self.shares * self.price
...
Isso permite que você retire os parênteses extras, escondendo o fato de que é realmente um método:
>>> s = Stock('GOOG', 100, 490.1)
>>> s.shares ## Instance variable
100
>>> s.cost ## Computed Value
49010.0
>>>
O último exemplo mostra como colocar uma interface mais uniforme em um objeto. Se você não fizer isso, um objeto pode ser confuso de usar:
>>> s = Stock('GOOG', 100, 490.1)
>>> a = s.cost() ## Method
49010.0
>>> b = s.shares ## Data attribute
100
>>>
Por que os () são necessários para o custo, mas não para as ações (shares)? Uma propriedade pode corrigir isso.
A sintaxe @ é conhecida como "decoração" (decoration). Ela especifica um modificador que é aplicado à definição da função que segue imediatamente.
...
@property
def cost(self):
return self.shares * self.price
Mais detalhes são fornecidos na Seção 7.
__slots__Você pode restringir o conjunto de nomes de atributos.
class Stock:
__slots__ = ('name','_shares','price')
def __init__(self, name, shares, price):
self.name = name
...
Isso levantará um erro para outros atributos.
>>> s.price = 385.15
>>> s.prices = 410.2
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AttributeError: 'Stock' object has no attribute 'prices'
Embora isso evite erros e restrinja o uso de objetos, ele é realmente usado para desempenho e faz com que o Python use a memória de forma mais eficiente.
Não exagere com atributos privados, propriedades, slots, etc. Eles servem a um propósito específico e você pode vê-los ao ler outro código Python. No entanto, eles não são necessários para a maioria da codificação do dia a dia.
Propriedades são uma maneira útil de adicionar "atributos computados" a um objeto. Em stock.py, você criou um objeto Stock. Observe que em seu objeto há uma ligeira inconsistência na forma como diferentes tipos de dados são extraídos:
>>> from stock import Stock
>>> s = Stock('GOOG', 100, 490.1)
>>> s.shares
100
>>> s.price
490.1
>>> s.cost()
49010.0
>>>
Especificamente, observe como você precisa adicionar os parênteses extras () a cost porque é um método.
Você pode se livrar dos parênteses extras () em cost() se o transformar em uma propriedade. Pegue sua classe Stock e modifique-a para que o cálculo do custo funcione assim:
>>> ================================ RESTART ================================
>>> from stock import Stock
>>> s = Stock('GOOG', 100, 490.1)
>>> s.cost
49010.0
>>>
Tente chamar s.cost() como uma função e observe que ela não funciona agora que cost foi definida como uma propriedade.
>>> s.cost()
... fails ...
>>>
Fazer essa alteração provavelmente quebrará seu programa pcost.py anterior. Você pode precisar voltar e se livrar dos () no método cost().
Modifique o atributo shares para que o valor seja armazenado em um atributo privado e que um par de funções de propriedade seja usado para garantir que ele seja sempre definido como um valor inteiro. Aqui está um exemplo do comportamento esperado:
>>> ================================ RESTART ================================
>>> from stock import Stock
>>> s = Stock('GOOG',100,490.10)
>>> s.shares = 50
>>> s.shares = 'a lot'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: expected an integer
>>>
Modifique a classe Stock para que ela tenha um atributo __slots__. Em seguida, verifique se novos atributos não podem ser adicionados:
>>> ================================ RESTART ================================
>>> from stock import Stock
>>> s = Stock('GOOG', 100, 490.10)
>>> s.name
'GOOG'
>>> s.blah = 42
... see what happens ...
>>>
Quando você usa __slots__, Python usa uma representação interna mais eficiente de objetos. O que acontece se você tentar inspecionar o dicionário subjacente de s acima?
>>> s.__dict__
... see what happens ...
>>>
Deve-se notar que __slots__ é mais comumente usado como uma otimização em classes que servem como estruturas de dados. Usar slots fará com que esses programas usem muito menos memória e executem um pouco mais rápido. No entanto, você provavelmente deve evitar __slots__ na maioria das outras classes.
Parabéns! Você concluiu o laboratório de Classes e Encapsulamento. Você pode praticar mais laboratórios no LabEx para aprimorar suas habilidades.