Aqui está uma pergunta de design puro e específica para Python:
class MyClass(object):
...
def get_my_attr(self):
...
def set_my_attr(self, value):
...
e
class MyClass(object):
...
@property
def my_attr(self):
...
@my_attr.setter
def my_attr(self, value):
...
Python nos permite fazê-lo de qualquer maneira. Se você projetasse um programa Python, qual abordagem você usaria e por quê?
fonte
No Python, você não usa getters, setters ou propriedades apenas para se divertir. Você primeiro usa atributos e, posteriormente, somente se necessário, eventualmente migra para uma propriedade sem precisar alterar o código usando suas classes.
De fato, há muito código com a extensão .py que usa getters e setters e herança e classes inúteis em todos os lugares, por exemplo, como uma tupla simples faria, mas é código de pessoas que escrevem em C ++ ou Java usando Python.
Esse não é o código Python.
fonte
O uso de propriedades permite iniciar com acessos normais a atributos e, em seguida, fazer backup deles com getters e setters posteriormente, conforme necessário .
fonte
A resposta curta é: as propriedades vencem as mãos . Sempre.
Às vezes há a necessidade de caçadores e caçadores, mas mesmo assim eu os "esconderia" para o mundo exterior. Há muitas maneiras de fazer isso em Python (
getattr
,setattr
,__getattribute__
, etc ..., mas um muito conciso e limpo é:Aqui está um breve artigo que apresenta o tópico de getters e setters no Python.
fonte
property
. ( Parece uma tarefa simples, mas chama uma função.) Portanto, "pitônico" é essencialmente um termo sem sentido, exceto pela definição tautológica: "convenções pitônicas são coisas que definimos como pitônicas".property
recurso ameace tornar quase inútil a idéia de axiomas pitônicos. Então, tudo o que resta é uma lista de verificação.self
objeto , setters explícitos podem ser úteis. Por exemplo,user.email = "..."
não parece que isso possa gerar uma exceção, pois apenas define um atributo, enquantouser.set_email("...")
deixa claro que pode haver efeitos colaterais, como exceções.[ TL; DR? Você pode pular para o final para obter um exemplo de código .]
Na verdade, prefiro usar um idioma diferente, que é um pouco complicado para ser usado como um exemplo, mas é bom se você tiver um caso de uso mais complexo.
Um pouco de fundo primeiro.
As propriedades são úteis, pois nos permitem lidar com a configuração e a obtenção de valores de maneira programática, mas ainda permitem que os atributos sejam acessados como atributos. Podemos transformar 'recebe' em 'cálculos' (essencialmente) e podemos transformar 'conjuntos' em 'eventos'. Então, digamos que temos a seguinte classe, que eu codifiquei com getters e setters do tipo Java.
Você pode estar se perguntando por que eu não liguei
defaultX
edefaultY
no__init__
método do objeto . O motivo é que, para o nosso caso, quero assumir que ossomeDefaultComputation
métodos retornam valores que variam ao longo do tempo, digamos um carimbo de data e hora, e sempre quex
(ouy
) não estiver definido (onde, para os fins deste exemplo, "não definido" significa "definido" para Nenhum ") Quero o valor do cálculo padrão dex
's (ouy
' s).Portanto, isso é ruim por várias razões descritas acima. Vou reescrevê-lo usando as propriedades:
O que ganhamos? Adquirimos a capacidade de nos referir a esses atributos como atributos, embora, nos bastidores, acabemos executando métodos.
Obviamente, o verdadeiro poder das propriedades é que geralmente queremos que esses métodos façam algo além de apenas obter e definir valores (caso contrário, não faz sentido usar propriedades). Eu fiz isso no meu exemplo getter. Basicamente, estamos executando um corpo de função para selecionar um padrão sempre que o valor não estiver definido. Este é um padrão muito comum.
Mas o que estamos perdendo e o que não podemos fazer?
O principal incômodo, na minha opinião, é que, se você definir um getter (como fazemos aqui), também precisará definir um setter. [1] Esse ruído extra atrapalha o código.
Outro incômodo é que ainda temos para inicializar o
x
ey
valores__init__
. (Bem, é claro que poderíamos adicioná-los usando,setattr()
mas isso é mais código extra.)Terceiro, diferente do exemplo do Java, os getters não podem aceitar outros parâmetros. Agora já posso ouvi-lo dizendo, bem, se está aceitando parâmetros, não é fácil! Em um sentido oficial, isso é verdade. Mas, no sentido prático, não há razão para não podermos parametrizar um atributo nomeado - como
x
- e definir seu valor para alguns parâmetros específicos.Seria bom se pudéssemos fazer algo como:
por exemplo. O mais próximo que podemos chegar é substituir a tarefa para implicar em uma semântica especial:
e, é claro, garanta que nosso criador saiba como extrair os três primeiros valores como chave de um dicionário e defina seu valor como um número ou algo assim.
Mas mesmo se fizéssemos isso, ainda não poderíamos suportá-lo com propriedades porque não há como obter o valor, porque não podemos passar parâmetros para o getter. Então tivemos que devolver tudo, introduzindo uma assimetria.
O getter / setter no estilo Java nos permite lidar com isso, mas voltamos a precisar de getter / setters.
Na minha opinião, o que realmente queremos é algo que capture os seguintes requisitos:
Os usuários definem apenas um método para um determinado atributo e podem indicar se o atributo é somente leitura ou leitura / gravação. As propriedades falham neste teste se o atributo for gravável.
Não é necessário que o usuário defina uma variável extra subjacente à função; portanto, não precisamos do código
__init__
nemsetattr
no código. A variável existe apenas pelo fato de termos criado esse atributo de novo estilo.Qualquer código padrão para o atributo é executado no próprio corpo do método.
Podemos definir o atributo como um atributo e referenciá-lo como um atributo.
Nós podemos parametrizar o atributo.
Em termos de código, queremos uma maneira de escrever:
e ser capaz de:
e assim por diante.
Também queremos uma maneira de fazer isso para o caso especial de um atributo parametrizável, mas ainda permitimos que o caso de atribuição padrão funcione. Você verá como lidei com isso abaixo.
Agora ao ponto (yay! O ponto!). A solução que eu vim para isso é a seguinte.
Criamos um novo objeto para substituir a noção de propriedade. O objetivo do objeto é armazenar o valor de uma variável definida, mas também mantém um identificador no código que sabe como calcular um padrão. Seu trabalho é armazenar o conjunto
value
ou executar omethod
se esse valor não estiver definido.Vamos chamá-lo de
UberProperty
.Suponho que
method
aqui seja um método de classe,value
seja o valor deUberProperty
, e eu adicioneiisSet
porqueNone
pode ser um valor real e isso nos permite uma maneira limpa de declarar que realmente não há "nenhum valor". Outra maneira é um sentinela de algum tipo.Isso basicamente nos dá um objeto que pode fazer o que queremos, mas como realmente o colocamos em nossa classe? Bem, as propriedades usam decoradores; por que não podemos? Vamos ver como pode parecer (daqui em diante vou usar apenas um 'atributo'
x
).Isso ainda não funciona, é claro. Temos que implementar
uberProperty
e garantir que ele lide com os conjuntos e conjuntos.Vamos começar com gets.
Minha primeira tentativa foi simplesmente criar um novo objeto UberProperty e devolvê-lo:
Eu rapidamente descobri, é claro, que isso não funciona: o Python nunca vincula a chamada ao objeto e eu preciso do objeto para chamar a função. Mesmo criar o decorador na classe não funciona, pois, embora agora tenhamos a classe, ainda não temos um objeto para trabalhar.
Então, precisamos fazer mais aqui. Sabemos que um método só precisa ser representado uma vez, então vamos continuar e manter nosso decorador, mas modifique
UberProperty
para armazenar apenas amethod
referência:Também não é exigível, então, no momento, nada está funcionando.
Como completamos a imagem? Bem, com o que terminamos quando criamos a classe de exemplo usando nosso novo decorador:
nos dois casos, recebemos de volta o
UberProperty
que, obviamente, não é passível de chamada, portanto, isso não é de muita utilidade.O que precisamos é de uma maneira de vincular dinamicamente a
UberProperty
instância criada pelo decorador após a classe ter sido criada para um objeto da classe antes que esse objeto tenha sido retornado ao usuário para uso. Hum, sim, é uma__init__
ligação, cara.Vamos escrever o que queremos que nosso resultado seja primeiro. Como vinculamos um
UberProperty
a uma instância, uma coisa óbvia a ser retornada seria um BoundUberProperty. É aqui que manteremos o estado dox
atributo.Agora nós a representação; como obtê-los em um objeto? Existem algumas abordagens, mas a mais fácil de explicar apenas usa o
__init__
método para fazer esse mapeamento. No momento em que__init__
é chamado de nossos decoradores ter executado, então só precisa olhar através do objeto de__dict__
e atualizar quaisquer atributos onde o valor do atributo é do tipoUberProperty
.Agora, as uber-properties são legais e provavelmente queremos usá-las muito, por isso faz sentido criar apenas uma classe base que faça isso para todas as subclasses. Eu acho que você sabe como a classe base será chamada.
Adicionamos isso, alteramos nosso exemplo para herdar de
UberObject
e ...Após modificar
x
para ser:Podemos executar um teste simples:
E obtemos a saída que queríamos:
(Nossa, estou trabalhando até tarde.)
Note que eu usei
getValue
,setValue
eclearValue
aqui. Isso ocorre porque ainda não vinculei os meios para que eles sejam retornados automaticamente.Mas acho que este é um bom lugar para parar por enquanto, porque estou cansado. Você também pode ver que a funcionalidade principal que queríamos está em vigor; o resto é vitrine. Importante vestir a janela de usabilidade, mas isso pode esperar até que eu tenha uma alteração para atualizar a postagem.
Terminarei o exemplo na próxima postagem abordando estas coisas:
Precisamos garantir que o UberObject
__init__
seja sempre chamado por subclasses.Precisamos garantir que lidemos com o caso comum em que alguém 'aliases' uma função para outra coisa, como:
Precisamos
e.x
retornare.x.getValue()
por padrão.e.x.getValue()
. (Fazer este é óbvio, se você ainda não o tiver consertado.)Precisamos apoiar a configuração
e.x directly
, como eme.x = <newvalue>
. Também podemos fazer isso na classe pai, mas precisaremos atualizar nosso__init__
código para lidar com isso.Por fim, adicionaremos atributos parametrizados. Deve ser bastante óbvio como faremos isso também.
Aqui está o código que existe até agora:
[1] Eu posso ficar para trás se esse ainda é o caso.
fonte
return self.x or self.defaultX()
este é um código perigoso. O que acontece quandoself.x == 0
?__getitem__
método. Seria estranho, pois você teria um python completamente fora do padrão.Eu acho que ambos têm o seu lugar. Um problema com o uso
@property
é que é difícil estender o comportamento de getters ou setters em subclasses usando mecanismos de classe padrão. O problema é que as funções getter / setter reais estão ocultas na propriedade.Você pode realmente se apossar das funções, por exemplo, com
você pode acessar as funções getter e setter como
C.p.fget
eC.p.fset
, mas não pode usar facilmente os recursos normais de herança de método (por exemplo, super) para estendê-los. Após algumas investigações sobre os meandros do super, você pode realmente usar o super desta maneira:Usar super () é, no entanto, bastante desajeitado, pois a propriedade precisa ser redefinida e você deve usar o mecanismo super (cls, cls) um pouco contra-intuitivo para obter uma cópia não acoplada de p.
fonte
Usar propriedades é para mim mais intuitivo e se encaixa melhor na maioria dos códigos.
Comparando
vs.
é óbvio para mim que é mais fácil de ler. Também as propriedades permitem variáveis privadas muito mais fáceis.
fonte
Eu preferiria usar nenhum dos dois na maioria dos casos. O problema com as propriedades é que elas tornam a classe menos transparente. Especialmente, esse é um problema se você criar uma exceção de um levantador. Por exemplo, se você tiver uma propriedade Account.email:
o usuário da classe não espera que atribuir um valor à propriedade possa causar uma exceção:
Como resultado, a exceção pode não ser tratada e se propagar muito alto na cadeia de chamadas para ser tratada adequadamente ou resultar em um retorno de retorno muito inútil sendo apresentado ao usuário do programa (o que é muito comum no mundo de python e java )
Também evitaria o uso de getters e setters:
Em vez de propriedades e getters / setters, prefiro fazer a lógica complexa em locais bem definidos, como em um método de validação:
ou um método semelhante Account.save.
Observe que não estou tentando dizer que não há casos em que as propriedades são úteis, apenas que você pode estar melhor se puder tornar suas aulas simples e transparentes o suficiente para que você não precise delas.
fonte
validate()
método em uma classe. Uma propriedade é simplesmente usada quando você tem uma lógica complexa por trás de umaobj.x = y
atribuição simples , e depende da lógica.Eu sinto que as propriedades são sobre permitir que você obtenha a sobrecarga de escrever getters e setters somente quando você realmente precisa deles.
A cultura de programação em Java aconselha vivamente que nunca dê acesso a propriedades e, em vez disso, passe por getters e setters, e somente aqueles que são realmente necessários. É um pouco detalhado escrever sempre esses trechos óbvios de código e observe que 70% das vezes eles nunca são substituídos por uma lógica não trivial.
No Python, as pessoas realmente cuidam desse tipo de sobrecarga, para que você possa adotar a seguinte prática:
@property
para implementá-los sem alterar a sintaxe do restante do seu código.fonte
Estou surpreso que ninguém tenha mencionado que propriedades são métodos vinculados de uma classe de descritor, Adam Donohue e NeilenMarais têm exatamente essa ideia em seus posts - que getters e setters são funções e podem ser usados para:
Isso apresenta uma maneira inteligente de ocultar os detalhes da implementação e a quantidade de códigos, como expressão regular, digitar lançamentos, tentar ... exceto blocos, asserções ou valores computados.
Em geral, fazer CRUD em um objeto pode ser bastante comum, mas considere o exemplo de dados que serão persistidos em um banco de dados relacional. Os ORMs podem ocultar detalhes de implementação de vernáculos SQL específicos nos métodos vinculados a fget, fset, fdel definidos em uma classe de propriedade que gerenciará as terríveis escadas se .. elif .. else que são tão feias no código OO - expondo as simples e as elegante
self.variable = something
e obviar os detalhes para o desenvolvedor usando o ORM.Se alguém considera as propriedades apenas como um vestígio sombrio de uma linguagem Bondage and Discipline (isto é, Java), elas estão perdendo o objetivo dos descritores.
fonte
Em projetos complexos, prefiro usar propriedades somente leitura (ou getters) com função explícita de setter:
Em projetos de longa duração, a depuração e refatoração leva mais tempo do que a escrita do próprio código. Existem várias desvantagens no uso
@property.setter
que tornam a depuração ainda mais difícil:1) python permite criar novos atributos para um objeto existente. Isso dificulta o rastreamento de uma impressão incorreta a seguir:
Se o seu objeto for um algoritmo complicado, você passará algum tempo tentando descobrir por que ele não converge (observe um 't' extra na linha acima)
2) o setter às vezes pode evoluir para um método complicado e lento (por exemplo, acessar um banco de dados). Seria muito difícil para outro desenvolvedor descobrir por que a função a seguir é muito lenta. Ele pode gastar muito tempo no
do_something()
método de criação de perfil , enquantomy_object.my_attr = 4.
na verdade é a causa do abrandamento:fonte
Ambos,
@property
getters e setters tradicionais, têm suas vantagens. Depende do seu caso de uso.Vantagens de
@property
Você não precisa alterar a interface enquanto altera a implementação do acesso a dados. Quando seu projeto é pequeno, você provavelmente deseja usar o acesso direto a atributos para acessar um membro da classe. Por exemplo, digamos que você tenha um objeto
foo
do tipoFoo
, que tenha um membronum
. Então você pode simplesmente obter esse membronum = foo.num
. À medida que o seu projeto cresce, você pode sentir que precisa haver algumas verificações ou depurações no acesso ao atributo simples. Então você pode fazer isso com um@property
dentro da classe. A interface de acesso a dados permanece a mesma para que não seja necessário modificar o código do cliente.Citado em PEP-8 :
Usando
@property
para acesso a dados em Python é considerado Pythonic :Ele pode fortalecer sua auto-identificação como programador Python (não Java).
Pode ajudar sua entrevista de emprego se o entrevistador considerar que os getters e setters no estilo Java são anti-padrões .
Vantagens dos getters e setters tradicionais
Os getters e setters tradicionais permitem um acesso a dados mais complicado do que o simples acesso a atributos. Por exemplo, quando você está configurando um membro da classe, às vezes você precisa de um sinalizador indicando onde deseja forçar essa operação, mesmo que algo não pareça perfeito. Embora não seja óbvio como aumentar o acesso direto a um membro
foo.num = num
, você pode aumentar facilmente seu setter tradicional com umforce
parâmetro adicional :Os getters e setters tradicionais tornam explícito que o acesso de um membro da classe é feito através de um método. Isso significa:
O que você obtém como resultado pode não ser o mesmo que é exatamente armazenado nessa classe.
Mesmo que o acesso pareça um acesso simples a atributos, o desempenho pode variar bastante disso.
A menos que os usuários de sua classe esperem uma
@property
esconder atrás de cada declaração de acesso a atributos, tornar essas coisas explícitas pode ajudar a minimizar as surpresas dos usuários de sua classe.Como mencionado por @NeilenMarais e neste post , estender getters e setters tradicionais em subclasses é mais fácil do que estender propriedades.
Os getters e setters tradicionais são amplamente utilizados há muito tempo em diferentes idiomas. Se você tem pessoas de diferentes origens em sua equipe, elas parecem mais familiares do que
@property
. Além disso, conforme seu projeto cresce, se você precisar migrar do Python para outro idioma que não tenha@property
, o uso de getters e setters tradicionais tornaria a migração mais suave.Ressalvas
Nem
@property
os getters e setters tradicionais tornam o membro da classe privado, mesmo que você use sublinhado duplo antes do nome:fonte
Aqui estão alguns trechos de "Python eficaz: 90 maneiras específicas de escrever melhor Python" (livro surpreendente. Eu recomendo).
fonte