Python name muting

109

Em outras linguagens, uma diretriz geral que ajuda a produzir um código melhor é sempre deixar tudo o mais oculto possível. Em caso de dúvida se uma variável deve ser privada ou protegida, é melhor optar por privada.

O mesmo vale para Python? Devo usar dois sublinhados à esquerda em tudo a princípio, e apenas torná-los menos ocultos (apenas um sublinhado) conforme preciso deles?

Se a convenção é usar apenas um sublinhado, também gostaria de saber a razão.

Aqui está um comentário que deixei na resposta de JBernardo . Isso explica por que fiz essa pergunta e também por que gostaria de saber por que o Python é diferente das outras linguagens:

Venho de línguas que treinam você para pensar que tudo deve ser tão público quanto necessário e nada mais. O raciocínio é que isso reduzirá as dependências e tornará o código mais seguro para alterações. A maneira Python de fazer as coisas ao contrário - começando do público e indo para o oculto - é estranha para mim.

Paul Manta
fonte

Respostas:

182

Na dúvida, deixe "público" - quero dizer, não adicione nada para obscurecer o nome do seu atributo. Se você tem uma aula com algum valor interno, não se preocupe com isso. Em vez de escrever:

class Stack(object):

    def __init__(self):
        self.__storage = [] # Too uptight

    def push(self, value):
        self.__storage.append(value)

escreva isso por padrão:

class Stack(object):

    def __init__(self):
        self.storage = [] # No mangling

    def push(self, value):
        self.storage.append(value)

Esta é certamente uma forma controversa de fazer as coisas. Os novatos em Python simplesmente odeiam e até mesmo alguns caras mais velhos do Python desprezam esse padrão - mas é o padrão de qualquer maneira, então eu realmente recomendo que você o siga, mesmo se você se sentir desconfortável.

Se você realmente deseja enviar a mensagem "Não posso tocar nisso!" para seus usuários, a maneira usual é preceder a variável com um sublinhado. Isso é apenas uma convenção, mas as pessoas entendem e tomam cuidado redobrado ao lidar com essas coisas:

class Stack(object):

    def __init__(self):
        self._storage = [] # This is ok but pythonistas use it to be relaxed about it

    def push(self, value):
        self._storage.append(value)

Isso também pode ser útil para evitar conflito entre nomes de propriedades e nomes de atributos:

 class Person(object):
     def __init__(self, name, age):
         self.name = name
         self._age = age if age >= 0 else 0

     @property
     def age(self):
         return self._age

     @age.setter
     def age(self, age):
         if age >= 0:
             self._age = age
         else:
             self._age  = 0

E o sublinhado duplo? Bem, a magia de sublinhado duplo é usada principalmente para evitar sobrecarga acidental de métodos e conflitos de nome com atributos das superclasses . Pode ser muito útil se você escrever uma classe que deve ser estendida muitas vezes.

Se quiser usá-lo para outros fins, você pode, mas não é comum nem recomendado.

EDIT : Por que isso acontece? Bem, o estilo Python usual não enfatiza tornar as coisas privadas - pelo contrário! Existem muitas razões para isso - a maioria delas controversas ... Vamos ver algumas delas.

Python tem propriedades

A maioria das linguagens OO hoje usa a abordagem oposta: o que não deve ser usado não deve ser visível, portanto, os atributos devem ser privados. Teoricamente, isso resultaria em classes mais gerenciáveis ​​e menos acopladas, porque ninguém mudaria os valores dentro dos objetos de maneira imprudente.

No entanto, não é tão simples. Por exemplo, as classes Java têm muitos atributos e getters que apenas obtêm os valores e setters que apenas definem os valores. Você precisa, digamos, de sete linhas de código para declarar um único atributo - o que um programador Python diria que é desnecessariamente complexo. Além disso, na prática, você apenas escreve todo esse código para obter um campo público, já que pode alterar seu valor usando getters e setters.

Então, por que seguir essa política privada por padrão? Basta tornar seus atributos públicos por padrão. Claro, isso é problemático em Java, porque se você decidir adicionar alguma validação ao seu atributo, seria necessário alterar todos

person.age = age;

em seu código para, digamos,

person.setAge(age);

setAge() ser:

public void setAge(int age) {
    if (age >= 0) {
        this.age = age;
    } else {
        this.age = 0;
    }
}

Então, em Java (e outras linguagens), o padrão é usar getters e setters de qualquer maneira, porque eles podem ser chatos de escrever, mas podem poupar muito tempo se você se encontrar na situação que descrevi.

No entanto, você não precisa fazer isso em Python, já que Python tem propriedades. Se você tem esta classe:

 class Person(object):
     def __init__(self, name, age):
         self.name = name
         self.age = age

e então você decide validar idades, você não precisa alterar as person.age = agepartes do seu código. Basta adicionar uma propriedade (conforme mostrado abaixo)

 class Person(object):
     def __init__(self, name, age):
         self.name = name
         self._age = age if age >= 0 else 0

     @property
     def age(self):
         return self._age

     @age.setter
     def age(self, age):
         if age >= 0:
             self._age = age
         else:
             self._age  = 0

Se você pode fazer isso e ainda usar person.age = age, por que adicionaria campos privados e getters e setters?

(Além disso, consulte Python não é Java e este artigo sobre os malefícios do uso de getters e setters .).

Tudo é visível de qualquer maneira - e tentar esconder apenas complica o seu trabalho

Mesmo em linguagens onde existem atributos privados, você pode acessá-los através de algum tipo de biblioteca de reflexão / introspecção. E as pessoas fazem muito isso, em frameworks e para solucionar necessidades urgentes. O problema é que as bibliotecas de introspecção são apenas uma maneira difícil de fazer o que você poderia fazer com atributos públicos.

Como Python é uma linguagem muito dinâmica, é contraproducente adicionar esse fardo às suas classes.

O problema não está sendo possível ver - está sendo necessário ver

Para um Pythonista, encapsulamento não é a incapacidade de ver o interior das classes, mas a possibilidade de evitar olhar para ele. O que quero dizer é que o encapsulamento é a propriedade de um componente que permite que ele seja usado sem que o usuário se preocupe com os detalhes internos. Se você pode usar um componente sem se preocupar com sua implementação, então ele é encapsulado (na opinião de um programador Python).

Agora, se você escreveu sua classe de tal forma que pode usá-la sem ter que pensar nos detalhes de implementação, não há problema se você quiser olhar dentro da classe por algum motivo. A questão é: sua API deve ser boa e o resto são detalhes.

O guido disse que sim

Bem, isso não é polêmico: ele disse isso, na verdade . (Procure por "quimono aberto".)

Isso é cultura

Sim, existem alguns motivos, mas nenhum motivo crítico. Este é principalmente um aspecto cultural da programação em Python. Francamente, poderia ser o contrário também - mas não é. Além disso, você poderia facilmente perguntar o contrário: por que algumas linguagens usam atributos privados por padrão? Pelo mesmo motivo principal da prática do Python: porque é a cultura dessas linguagens, e cada escolha tem vantagens e desvantagens.

Como já existe essa cultura, é recomendável segui-la. Caso contrário, você ficará irritado com os programadores Python dizendo para remover o __do seu código quando fizer uma pergunta no Stack Overflow :)

Brandizzi
fonte
1. O encapsulamento é para proteger invariantes de classe. Não esconder detalhes desnecessários do mundo externo porque seria um aborrecimento. 2. "A questão é: sua API deve ser boa e o resto são detalhes." Isso é verdade. E os atributos públicos fazem parte da sua API. Além disso, às vezes os setters públicos são apropriados (em relação às invariantes de sua classe) e às vezes não. Uma API que possui setters públicos que não deveriam ser públicos (risco de violação de invariantes) é uma API ruim. Isso significa que você tem que pensar sobre a visibilidade de cada configurador de qualquer maneira e ter um 'padrão' significa menos.
Júpiter
21

Primeiro - Qual é o nome mutilado?

A mutilação de nome é invocada quando você está em uma definição de classe e usa __any_nameou __any_name_, isto é, dois (ou mais) sublinhados iniciais e no máximo um sublinhado final.

class Demo:
    __any_name = "__any_name"
    __any_other_name_ = "__any_other_name_"

E agora:

>>> [n for n in dir(Demo) if 'any' in n]
['_Demo__any_name', '_Demo__any_other_name_']
>>> Demo._Demo__any_name
'__any_name'
>>> Demo._Demo__any_other_name_
'__any_other_name_'

Na dúvida, fazer o quê?

O uso ostensivo é evitar que os subclasses usem um atributo que a classe usa.

Um valor potencial é evitar conflitos de nomes com subclasses que desejam substituir o comportamento, de modo que a funcionalidade da classe pai continue funcionando conforme o esperado. No entanto, o exemplo na documentação do Python não é substituível por Liskov, e nenhum exemplo vem à mente onde eu achei isso útil.

As desvantagens são que ele aumenta a carga cognitiva para ler e entender uma base de código, especialmente durante a depuração, onde você vê o nome de sublinhado duplo na origem e um nome mutilado no depurador.

Minha abordagem pessoal é evitá-lo intencionalmente. Eu trabalho em uma base de código muito grande. Os raros usos dela se destacam como uma ferida no polegar e não parecem justificados.

Você precisa estar ciente disso para saber quando vir.

PEP 8

PEP 8 , o guia de estilo da biblioteca padrão do Python, atualmente diz (resumido):

Existe alguma controvérsia sobre o uso de __names.

Se a sua classe se destina a ser uma subclasse e você tem atributos que não deseja que as subclasses usem, considere nomeá-los com sublinhados iniciais duplos e sem sublinhados finais.

  1. Observe que apenas o nome da classe simples é usado no nome mutilado, portanto, se uma subclasse escolher o mesmo nome de classe e o mesmo nome de atributo, você ainda pode obter colisões de nomes.

  2. A mutilação de nomes pode fazer certos usos, como depuração e __getattr__(), menos convenientes. No entanto, o algoritmo de mutilação de nomes é bem documentado e fácil de executar manualmente.

  3. Nem todo mundo gosta de mutilar nomes. Tente equilibrar a necessidade de evitar conflitos de nomes acidentais com o uso potencial por chamadores avançados.

Como funciona?

Se você preceder dois sublinhados (sem terminar dois sublinhados) em uma definição de classe, o nome será mutilado e um sublinhado seguido pelo nome da classe será acrescentado ao objeto:

>>> class Foo(object):
...     __foobar = None
...     _foobaz = None
...     __fooquux__ = None
... 
>>> [name for name in dir(Foo) if 'foo' in name]
['_Foo__foobar', '__fooquux__', '_foobaz']

Observe que os nomes só serão mutilados quando a definição da classe for analisada:

>>> Foo.__test = None
>>> Foo.__test
>>> Foo._Foo__test
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'Foo' has no attribute '_Foo__test'

Além disso, aqueles que são novos em Python às vezes têm problemas para entender o que está acontecendo quando não conseguem acessar manualmente um nome que vêem definido em uma definição de classe. Esta não é uma razão forte contra isso, mas é algo a se considerar se você tiver um público que aprende.

Um sublinhado?

Se a convenção é usar apenas um sublinhado, também gostaria de saber a razão.

Quando minha intenção é que os usuários mantenham suas mãos longe de um atributo, eu tendo a usar apenas o sublinhado, mas isso é porque no meu modelo mental, os subclasses teriam acesso ao nome (o que eles sempre têm, pois podem facilmente identificar o nome mutilado de qualquer maneira).

Se eu estivesse revisando o código que usa o __prefixo, perguntaria por que eles estão invocando a mutilação de nomes, e se eles não poderiam fazer tão bem com um único sublinhado, tendo em mente que se os subclasses escolherem os mesmos nomes para a classe e atributo de classe, haverá uma colisão de nomes, apesar disso.

Aaron Hall
fonte
15

Eu não diria que a prática produz um código melhor. Os modificadores de visibilidade apenas o distraem da tarefa em questão e, como um efeito colateral, forçam sua interface a ser usada conforme pretendido. De modo geral, impor visibilidade evita que os programadores bagunçam as coisas se não tiverem lido a documentação corretamente.

Uma solução muito melhor é a rota que o Python incentiva: suas classes e variáveis ​​devem ser bem documentadas e seu comportamento, claro. A fonte deve estar disponível. Esta é uma maneira muito mais extensível e confiável de escrever código.

Minha estratégia em Python é esta:

  1. Apenas escreva a maldita coisa, não faça suposições sobre como seus dados devem ser protegidos. Isso pressupõe que você escreva para criar as interfaces ideais para seus problemas.
  2. Use um sublinhado inicial para coisas que provavelmente não serão usadas externamente e não fazem parte da interface normal de "código do cliente".
  3. Use o sublinhado duplo apenas para coisas que são puramente convenientes dentro da classe, ou causarão danos consideráveis ​​se expostas acidentalmente.

Acima de tudo, deve ficar claro o que tudo faz. Documente se outra pessoa for usá-lo. Documente se quiser que seja útil daqui a um ano.

Como uma observação lateral, você realmente deveria usar protected nessas outras linguagens: você nunca sabe que sua classe pode ser herdada mais tarde e para que ela pode ser usada. É melhor proteger apenas as variáveis ​​que você tem certeza que não podem ou não devem ser usadas por código estrangeiro.

Matt Joiner
fonte
9

Você não deve começar com dados privados e torná-los públicos conforme necessário. Em vez disso, você deve começar descobrindo a interface do seu objeto. Ou seja, você deve começar descobrindo o que o mundo vê (as coisas públicas) e então descobrir quais coisas privadas são necessárias para que isso aconteça.

Outras linguagens tornam difícil tornar privado o que antes era público. Ou seja, vou quebrar muitos códigos se tornar minha variável privada ou protegida. Mas com propriedades em python, esse não é o caso. Em vez disso, posso manter a mesma interface, mesmo reorganizando os dados internos.

A diferença entre _ e __ é que o python realmente faz uma tentativa de impor o último. Claro, não é realmente difícil, mas torna-se difícil. Tendo _ apenas informando a outros programadores qual é a intenção, eles estão livres para ignorar por sua conta e risco. Mas ignorar essa regra às vezes é útil. Os exemplos incluem depuração, hacks temporários e trabalho com código de terceiros que não foi criado para ser usado da maneira como você o usa.

Winston Ewert
fonte
6

Já existem muitas respostas boas para isso, mas vou oferecer outra. Isso também é parcialmente uma resposta às pessoas que continuam dizendo que o sublinhado duplo não é privado (realmente é).

Se você olhar para Java / C #, ambos possuem private / protected / public. Todos esses são construções em tempo de compilação . Eles são aplicados apenas no momento da compilação. Se você fosse usar reflexão em Java / C #, poderia acessar facilmente o método privado.

Agora, toda vez que você chama uma função em Python, está inerentemente usando reflexão. Esses pedaços de código são os mesmos em Python.

lst = []
lst.append(1)
getattr(lst, 'append')(1)

A sintaxe do "ponto" é apenas um açúcar sintático para a última parte do código. Principalmente porque usar getattr já é feio com apenas uma chamada de função. Só fica pior a partir daí.

Então, com isso, não pode haver uma versão Java / C # de private, já que Python não compila o código. Java e C # não podem verificar se uma função é privada ou pública em tempo de execução, pois essa informação se foi (e ele não tem conhecimento de onde a função está sendo chamada).

Agora, com essa informação, o nome mutilado do sublinhado duplo faz mais sentido para alcançar "privacidade". Agora, quando uma função é chamada a partir da instância 'self' e percebe que ela começa com '__', ela apenas executa o nome mutilado ali. É apenas mais açúcar sintático. Esse açúcar sintático permite o equivalente a 'privado' em uma linguagem que só usa reflexão para acesso de membro de dados.

Isenção de responsabilidade: nunca ouvi ninguém do desenvolvimento Python dizer algo assim. A verdadeira razão para a falta de "privado" é cultural, mas você também perceberá que a maioria das linguagens de script / interpretadas não tem privado. Um privado estritamente aplicável não é prático em nada, exceto em tempo de compilação.

Jonathan Sternberg
fonte
4

Primeiro: por que você deseja ocultar seus dados? Por que isso é tão importante?

Na maioria das vezes você realmente não quer fazer isso, mas o faz porque os outros estão fazendo.

Se você realmente realmente não quer que as pessoas usem algo, adicione um sublinhado na frente disso. É isso ... Pythonistas sabem que coisas com um sublinhado não têm garantia de funcionar todas as vezes e podem mudar sem você saber.

É assim que vivemos e estamos bem com isso.

Usar dois sublinhados tornará sua classe tão ruim para subclasse que mesmo você não vai querer trabalhar dessa maneira.

JBernardo
fonte
2
Você omitiu o motivo pelo qual o sublinhado duplo é ruim para a subclasse ... isso melhoraria sua resposta.
Matt Joiner,
2
Dado que os sublinhados duplos são, na verdade, apenas para evitar colisões de nomes com subclasses (como uma forma de dizer "tire as mãos" dos subclasses), não vejo como a mutilação de nomes cria um problema.
Aaron Hall
4

A resposta escolhida explica bem como as propriedades eliminam a necessidade de atributos privados , mas eu também acrescentaria que as funções no nível do módulo eliminam a necessidade de métodos privados .

Se você transformar um método em uma função no nível do módulo, você remove a oportunidade das subclasses de substituí-lo. Mover algumas funcionalidades para o nível de módulo é mais Pythônico do que tentar ocultar métodos com alteração de nome.

Tanner_Wauchope
fonte
3

O seguinte snippet de código explicará todos os casos diferentes:

  • dois sublinhados principais (__a)
  • sublinhado inicial único (_a)
  • sem sublinhado (a)

    class Test:
    
    def __init__(self):
        self.__a = 'test1'
        self._a = 'test2'
        self.a = 'test3'
    
    def change_value(self,value):
        self.__a = value
        return self.__a

imprimir todos os atributos válidos do objeto de teste

testObj1 = Test()
valid_attributes = dir(testObj1)
print valid_attributes

['_Test__a', '__doc__', '__init__', '__module__', '_a', 'a', 
'change_value']

Aqui, você pode ver que o nome de __a foi alterado para _Test__a para evitar que essa variável seja substituída por qualquer uma das subclasses. Este conceito é conhecido como "Name Mangling" em python. Você pode acessar isso desta forma:

testObj2 = Test()
print testObj2._Test__a

test1

Da mesma forma, no caso de _a, a variável serve apenas para avisar ao desenvolvedor que ela deve ser usada como variável interna daquela classe, o interpretador python não fará nada mesmo que você acesse, mas não é uma boa prática.

testObj3 = Test()
print testObj3._a

test2

uma variável pode ser acessada de qualquer lugar, é como uma variável de classe pública.

testObj4 = Test()
print testObj4.a

test3

Espero que a resposta tenha ajudado você :)

Nitish Chauhan
fonte
2

À primeira vista, deve ser o mesmo que para outras linguagens (em "outros", quero dizer Java ou C ++), mas não é.

Em Java, você tornou privadas todas as variáveis ​​que não deveriam ser acessíveis externamente. Ao mesmo tempo, em Python, você não pode conseguir isso, pois não há "privacidade" (como diz um dos princípios do Python - "Somos todos adultos"). Então sublinhado duplo significa apenas "Pessoal, não use este campo diretamente". O mesmo significado tem sublinhado único, que ao mesmo tempo não causa dor de cabeça quando você tem que herdar de uma classe considerada (apenas um exemplo de possível problema causado por sublinhado duplo).

Portanto, eu recomendo que você use um único sublinhado por padrão para membros "privados".

Roman Bodnarchuk
fonte
Use sublinhado duplo para "privado" e sublinhado único para "protegido". Normalmente, as pessoas usam apenas um sublinhado único para tudo (o sublinhado duplo ajudará a reforçar a privacidade, que geralmente é contra o estilo Python).
Jonathan Sternberg,
1
Mas isso não torna dois sublinhados semelhantes a privado e um sublinhado semelhante a protegido? Por que não começar de "privado"?
Paul Manta,
@Paul Não, não importa. Não existe privado no Python e você não deve tentar alcançá-lo.
Roman Bodnarchuk,
@Roman Conceitualmente falando ... Observe as aspas em torno de 'privado'.
Paul Manta,
1

"Em caso de dúvida se uma variável deve ser privada ou protegida, é melhor ir com privada." - sim, o mesmo vale para Python.

Algumas respostas aqui falam sobre 'convenções', mas não fornecem os links para essas convenções. O guia oficial para Python, PEP 8 afirma explicitamente:

Em caso de dúvida, escolha não público; é mais fácil torná-lo público depois do que tornar um atributo público não público.

A distinção entre público e privado e alteração de nomes em Python foram consideradas em outras respostas. Do mesmo link,

Não usamos o termo "privado" aqui, uma vez que nenhum atributo é realmente privado no Python (sem uma quantidade geralmente desnecessária de trabalho).

Yaroslav Nikitenko
fonte