Por que os métodos 'privados' do Python não são realmente privados?

658

Python nos dá a capacidade de criar métodos 'privados' e variáveis dentro de uma classe, antecedendo sublinhados duplos para o nome, assim: __myPrivateMethod(). Como, então, alguém pode explicar isso?

>>> class MyClass:
...     def myPublicMethod(self):
...             print 'public method'
...     def __myPrivateMethod(self):
...             print 'this is private!!'
... 
>>> obj = MyClass()
>>> obj.myPublicMethod()
public method
>>> obj.__myPrivateMethod()
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: MyClass instance has no attribute '__myPrivateMethod'
>>> dir(obj)
['_MyClass__myPrivateMethod', '__doc__', '__module__', 'myPublicMethod']
>>> obj._MyClass__myPrivateMethod()
this is private!!

Qual é o problema ?!

Vou explicar isso um pouco para quem não entendeu direito.

>>> class MyClass:
...     def myPublicMethod(self):
...             print 'public method'
...     def __myPrivateMethod(self):
...             print 'this is private!!'
... 
>>> obj = MyClass()

O que eu fiz lá é criar uma classe com um método público e um método privado e instancia-lo.

Em seguida, chamo seu método público.

>>> obj.myPublicMethod()
public method

Em seguida, tento chamar seu método privado.

>>> obj.__myPrivateMethod()
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: MyClass instance has no attribute '__myPrivateMethod'

Tudo parece bom aqui; não podemos chamá-lo. É, de fato, "privado". Bem, na verdade não é. A execução de dir () no objeto revela um novo método mágico que o python cria magicamente para todos os seus métodos 'particulares'.

>>> dir(obj)
['_MyClass__myPrivateMethod', '__doc__', '__module__', 'myPublicMethod']

O nome desse novo método é sempre um sublinhado, seguido pelo nome da classe, seguido pelo nome do método.

>>> obj._MyClass__myPrivateMethod()
this is private!!

Tanta coisa para encapsulamento, não é?

De qualquer forma, sempre ouvi dizer que o Python não suporta encapsulamento, então por que tentar? O que da?

willurd
fonte
18
O mesmo vale para Java ou C # se você usar reflexão (que é de alguma forma o que você está fazendo lá).
0x434D53 22/02
4
Ele foi criado para fins de Teste de Unidade, para que você possa usar esse "hack" para testar os métodos privados de sua classe de fora.
waas1919
16
Testar métodos privados não é um antipadrão? Métodos privados serão usados ​​em algum método público, com certeza, caso contrário, eles não serão utilizados para sempre. E a maneira correta de testar métodos particulares (com base no meu aprendizado até agora no ThoughtWorks) é que você escreva testes apenas para métodos públicos que abranjam todos os casos. Se isso funcionar bem, você não precisará testar métodos privados de fora.
Vishnu Narang
3
@VishnuNarang: Sim, é o que costuma ser ensinado. Mas, como sempre, uma abordagem quase "religiosa" de " sempre faça isso, nunca faça isso" é a única coisa que "nunca" é bom. Se os testes de unidade são "apenas" usados ​​para testes de regressão ou API pública, você não precisa testar em particular. Porém, se você desenvolver desenvolvimento orientado a teste de unidade, há boas razões para testar métodos privat durante o desenvolvimento (por exemplo, quando é difícil zombar de certos parâmetros incomuns / extremos por meio da interface pública). Alguns ambientes de teste de idiomas / unidade não permitem fazer isso, o que IMHO não é bom.
Marco Freudenberger
5
@MarcoFreudenberger Entendo o seu ponto. Tenho experiência em desenvolvimento orientado a testes de unidade. Muitas vezes, quando fica difícil zombar de parâmetros, na maioria das vezes é resolvido alterando e melhorando o design. Ainda estou para me deparar com um cenário em que o design é perfeito e o teste de unidade ainda é extremamente difícil para evitar o teste de métodos particulares. Vou cuidar de tais casos. Obrigado. Eu gostaria que você pudesse compartilhar um cenário da sua cabeça para me ajudar a entender.
Vishnu Narang

Respostas:

592

A codificação de nomes é usada para garantir que as subclasses não substituam acidentalmente os métodos e atributos particulares de suas superclasses. Ele não foi projetado para impedir o acesso deliberado de fora.

Por exemplo:

>>> class Foo(object):
...     def __init__(self):
...         self.__baz = 42
...     def foo(self):
...         print self.__baz
...     
>>> class Bar(Foo):
...     def __init__(self):
...         super(Bar, self).__init__()
...         self.__baz = 21
...     def bar(self):
...         print self.__baz
...
>>> x = Bar()
>>> x.foo()
42
>>> x.bar()
21
>>> print x.__dict__
{'_Bar__baz': 21, '_Foo__baz': 42}

Obviamente, ele será quebrado se duas classes diferentes tiverem o mesmo nome.

Alya
fonte
12
docs.python.org/2/tutorial/classes.html . Seção: 9.6 sobre Variáveis ​​privadas e referências de classe local.
Gjáin
72
Para aqueles de nós com preguiça de rolar / pesquisar: Seção 9.6 link direto
cod3monk3y 20/02/2014
3
Você deve colocar um único sublinhado para especificar que a variável deve ser considerada privada. Novamente, isso não impede que alguém realmente acesse isso.
igon
14
Guido respondeu a esta pergunta - "o principal motivo para tornar (quase) tudo o que foi descoberto foi a depuração: ao depurar, você muitas vezes precisa romper as abstrações" - adicionei como comentário porque é tarde demais - respostas demais.
Peter M. - significa Monica
1
Se você seguir o critério "impedir acesso deliberado", a maioria dos idiomas OOP não suporta membros verdadeiramente privados. Por exemplo, em C ++, você tem acesso bruto à memória e, em código confiável em C #, pode usar reflexão particular.
CodesInChaos
207

Exemplo de função privada

import re
import inspect

class MyClass :

    def __init__(self) :
        pass

    def private_function ( self ) :
        try :
            function_call = inspect.stack()[1][4][0].strip()

            # See if the function_call has "self." in the begining
            matched = re.match( '^self\.', function_call )
            if not matched :
                print 'This is Private Function, Go Away'
                return
        except :
            print 'This is Private Function, Go Away'
            return

        # This is the real Function, only accessible inside class #
        print 'Hey, Welcome in to function'

    def public_function ( self ) :
        # i can call private function from inside the class
        self.private_function()

### End ###
uma corrida
fonte
12
self = MyClass() self.private_function(). : D Claro que não funciona nas aulas, mas você só tem que definir uma função personalizada: def foo(self): self.private_function()
Casey Kuball
161
Apenas no caso não era clara: nunca mais fazer isso no código real;)
Sudo Bash
10
@ThorSummoner Ou apenas function_call.startswith('self.').
precisa saber é o seguinte
13
inspect.stack()[1][4][0].strip()<- quais são os números mágicos 1, 4 e 0?
akhy
5
Isso pode ser derrotado com muita facilidade, self = MyClass(); self.private_function()e falha quando chamado usando x = self.private_function()dentro de um método.
Will
171

Quando eu vim de Java para Python, odiei isso. Isso me assustou até a morte.

Hoje, essa pode ser a única coisa que eu mais amo no Python.

Adoro estar em uma plataforma, na qual as pessoas confiam umas nas outras e não sentem que precisam criar muros impenetráveis ​​em torno de seu código. Em linguagens fortemente encapsuladas, se uma API tiver um bug e você tiver descoberto o que está errado, você ainda poderá não conseguir solucionar o problema, porque o método necessário é privado. Em Python, a atitude é: "com certeza". Se você acha que entende a situação, talvez até a tenha lido, tudo o que podemos dizer é "boa sorte!".

Lembre-se de que o encapsulamento não está nem um pouco relacionado à "segurança" ou a manter as crianças fora do gramado. É apenas outro padrão que deve ser usado para facilitar a compreensão de uma base de código.

Thomas Ahle
fonte
36
@CamJackson Javascript é o seu exemplo? A única linguagem amplamente usada que tem herança baseada em protótipos e uma linguagem que favorece a programação funcional? Eu acho que JS é muito mais difícil de aprender do que a maioria dos outros idiomas, uma vez que são necessários vários passos ortogonais do OOP tradicional. Não que isso impede que os idiotas de escrever JS, eles simplesmente não sabem disso;)
K.Steff
23
As APIs são realmente um bom exemplo de por que o encapsulamento é importante e quando os métodos privados seriam preferidos. Um método destinado a ser privado pode desaparecer, alterar a assinatura ou o pior de todos os comportamentos de alteração - tudo sem aviso prévio - em qualquer nova versão subsequente. Seu membro inteligente e adulto da equipe realmente se lembrará de que acessará um método destinado a ser privado daqui a um ano, quando você atualizar? Ela ainda estará trabalhando lá?
einnocent
2
Eu discordo do argumento. No código de produção, eu provavelmente nunca usaria uma API que possui um bug que me faz alterar membros públicos para fazê-la "funcionar". Uma API deve funcionar. Caso contrário, eu arquivaria um relatório de erro ou faria a mesma API. Eu não gosto da filosofia e eu não gosto muito de Python, embora sua sintaxe torna divertido para escrever scripts menores em ...
Yngve sneen Lindal
4
Java possui Method.setAccessible e Field.setAccessible. Também assustador?
Tony
15
A imposição em Java e C ++ não ocorre porque o Java desconfia do usuário enquanto o Python o faz. Isso porque o compilador e / ou vm podem fazer várias suposições ao lidar com o modo como lidam com seus negócios, se souberem essas informações, por exemplo, o C ++ pode pular uma camada inteira de indireção usando chamadas C antigas comuns em vez de chamadas virtuais, e que é importante quando você trabalha com itens de alto desempenho ou alta precisão. Python, por sua natureza, não pode realmente fazer bom uso das informações sem comprometer seu dinamismo. Ambas as línguas são destinadas a coisas diferentes, então, nem são "errado"
Shayne
144

Em http://www.faqs.org/docs/diveintopython/fileinfo_private.html

A rigor, os métodos privados são acessíveis fora da classe, mas não são facilmente acessíveis. Nada no Python é verdadeiramente privado; internamente, os nomes dos métodos e atributos privados são mutilados e não mutilados em tempo real para torná-los inacessíveis por seus nomes. Você pode acessar o método __parse da classe MP3FileInfo com o nome _MP3FileInfo__parse. Reconheça que isso é interessante e prometa nunca fazê-lo em código real. Os métodos privados são privados por um motivo, mas, como muitas outras coisas em Python, sua privacidade é, em última análise, uma questão de convenção, não de força.

xsl
fonte
202
ou como Guido van Rossum colocou: "somos todos adultos".
35
-1: isso está errado. Os sublinhados duplos nunca devem ser usados ​​como privados em primeiro lugar. A resposta de Alya abaixo informa a real intenção da sintaxe desconcertante de nomes. A convenção real é um único sublinhado.
Nosklo 15/10/09
2
Tente apenas com um sublinhado e você verá o resultado obtido. @nosklo
Billal Begueradj
93

A frase comumente usada é "somos todos adultos concordantes aqui". Anexando um único sublinhado (não exponha) ou o sublinhado duplo (ocultar), você está dizendo ao usuário da sua classe que pretende que o membro seja 'privado' de alguma forma. No entanto, você está confiando que todos os demais devem se comportar com responsabilidade e respeitar isso, a menos que eles tenham um motivo convincente para não fazê-lo (por exemplo, depuradores de código, conclusão de código).

Se você realmente deve ter algo privado, pode implementá-lo em uma extensão (por exemplo, em C para CPython). Na maioria dos casos, no entanto, você simplesmente aprende a maneira pitônica de fazer as coisas.

Tony Meyer
fonte
então existe algum tipo de protocolo de invólucro que devo usar para acessar uma variável protegida?
intuited
3
Não existem variáveis ​​"protegidas", assim como não são "particulares". Se você deseja acessar um atributo que começa com um sublinhado, basta fazê-lo (mas observe que o autor desencoraja isso). Se você precisar acessar um atributo que comece com um sublinhado duplo, poderá fazer o nome se mutilar, mas quase certamente não deseja fazer isso.
Tony Meyer
33

Não é como se você absolutamente não pudesse contornar a privacidade dos membros em qualquer idioma (aritmética de ponteiros em C ++, Reflexões em .NET / Java).

O ponto é que você receberá um erro se tentar chamar o método privado por acidente. Mas se você quer dar um tiro no pé, vá em frente e faça-o.

Edit: Você não tenta proteger suas coisas por encapsulamento OO, não é?

Maximilian
fonte
2
De modo nenhum. Estou simplesmente afirmando que é estranho dar ao desenvolvedor uma maneira fácil e, na minha opinião, uma maneira mágica de acessar propriedades 'privadas'.
willurd 16/09/08
2
Sim, eu apenas tentei ilustrar o ponto. Tornar privado apenas diz "você não deve acessar isso diretamente", fazendo o compilador reclamar. Mas alguém quer realmente fazer o que pode. Mas sim, é mais fácil no Python do que na maioria dos outros idiomas.
Maximilian
7
Em Java, você realmente pode proteger coisas via encapsulamento, mas isso exige que você seja inteligente e execute o código não confiável em um SecurityManager, e tenha muito cuidado. Até a Oracle erra algumas vezes.
Antimony
12

A class.__stuffconvenção de nomenclatura permite que o programador saiba que ele não deve acessar __stuffde fora. O nome desconcertante torna improvável que alguém o faça por acidente.

É verdade que você ainda pode solucionar isso, é ainda mais fácil do que em outras linguagens (que a BTW também permite), mas nenhum programador Python faria isso se ele se importasse com o encapsulamento.

Nickolay
fonte
12

Um comportamento semelhante existe quando os nomes dos atributos do módulo começam com um único sublinhado (por exemplo, _foo).

Os atributos do módulo nomeados como tal não serão copiados para um módulo de importação ao usar o from*método, por exemplo:

from bar import *

No entanto, esta é uma convenção e não uma restrição de idioma. Esses não são atributos particulares; eles podem ser referenciados e manipulados por qualquer importador. Alguns argumentam que, por causa disso, o Python não pode implementar o verdadeiro encapsulamento.

Ross
fonte
12

É apenas uma dessas opções de design de idioma. Em algum nível, eles são justificados. Eles fazem com que você precise ir muito longe do seu caminho para tentar chamar o método, e se você realmente precisa muito disso, deve ter um bom motivo!

Ganchos de depuração e testes vêm à mente como possíveis aplicativos, usados ​​com responsabilidade, é claro.

ctcherry
fonte
4

Com o Python 3.4, este é o comportamento:

>>> class Foo:
        def __init__(self):
                pass
        def __privateMethod(self):
                return 3
        def invoke(self):
                return self.__privateMethod()


>>> help(Foo)
Help on class Foo in module __main__:

class Foo(builtins.object)
 |  Methods defined here:
 |
 |  __init__(self)
 |
 |  invoke(self)
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |
 |  __dict__
 |      dictionary for instance variables (if defined)
 |
 |  __weakref__
 |      list of weak references to the object (if defined)

 >>> f = Foo()
 >>> f.invoke()
 3
 >>> f.__privateMethod()
 Traceback (most recent call last):
   File "<pyshell#47>", line 1, in <module>
     f.__privateMethod()
 AttributeError: 'Foo' object has no attribute '__privateMethod'

https://docs.python.org/3/tutorial/classes.html#tut-private

Observe que as regras de confundimento são projetadas principalmente para evitar acidentes; ainda é possível acessar ou modificar uma variável considerada privada. Isso pode até ser útil em circunstâncias especiais, como no depurador.

Mesmo se a pergunta for antiga, espero que meu snippet possa ser útil.

Alberto
fonte
2

A preocupação mais importante sobre métodos e atributos privados é dizer aos desenvolvedores para não chamá-lo fora da classe e isso é encapsulamento. pode-se entender mal a segurança do encapsulamento. quando alguém usa deliberadamente uma sintaxe como essa (abaixo) mencionada, não deseja encapsulamento.

obj._MyClass__myPrivateMethod()

Eu migrei do C # e no começo era estranho para mim também, mas depois de um tempo cheguei à ideia de que apenas a maneira como os designers de código Python pensam sobre OOP é diferente.

Afshin Amiri
fonte
1

Por que os métodos 'privados' do Python não são realmente privados?

Pelo que entendi, eles não podem ser privados. Como a privacidade pode ser aplicada?

A resposta óbvia é que "membros privados só podem ser acessados ​​por meio self", mas isso não funcionaria - selfnão é especial no Python, nada mais é que um nome comumente usado para o primeiro parâmetro de uma função.

user200783
fonte