Devo usar uma aula ou dicionário?

99

Eu tenho uma classe que contém apenas campos e nenhum método, como este:

class Request(object):

    def __init__(self, environ):
        self.environ = environ
        self.request_method = environ.get('REQUEST_METHOD', None)
        self.url_scheme = environ.get('wsgi.url_scheme', None)
        self.request_uri = wsgiref.util.request_uri(environ)
        self.path = environ.get('PATH_INFO', None)
        # ...

Isso poderia ser facilmente traduzido para um dict. A classe é mais flexível para adições futuras e pode ser mais rápida __slots__. Então, haveria uma vantagem em usar um dicionário em vez disso? Um dict seria mais rápido do que uma aula? E mais rápido do que uma classe com slots?

deamon
fonte
2
Eu sempre uso dicionários para manter os dados, e isso parece um caso de uso para mim. em alguns casos, derivar uma classe de dictpode fazer sentido, para. grande vantagem: ao depurar, basta dizer print(request)e você verá prontamente todas as informações de estado. com a abordagem mais clássica, você terá que escrever seus __str__métodos personalizados , o que é uma droga se você tiver que fazer isso o tempo todo.
fluxo
Eles são intercambiáveis stackoverflow.com/questions/1305532/…
Oleksiy
Se a aula faz todo o sentido e é clara para os outros, por que não? Além disso, se você definir muitas classes com interfaces comuns, por que não? Mas Python não oferece suporte a conceitos orientados a objetos poderosos como C ++.
MasterControlProgram
3
@Ralf que OOP não suporta o python?
qwr

Respostas:

32

Por que você faria este dicionário? Qual é a vantagem? O que acontece se você quiser adicionar algum código posteriormente? Para onde iria seu __init__código?

As aulas são para agrupar dados relacionados (e geralmente código).

Os dicionários são para armazenar relacionamentos de valor-chave, onde geralmente as chaves são todas do mesmo tipo e todos os valores também são de um tipo. Ocasionalmente, eles podem ser úteis para agrupar dados quando os nomes de chave / atributo não são todos conhecidos antecipadamente, mas geralmente isso é um sinal de que algo está errado com seu projeto.

Mantenha esta uma aula.

adw
fonte
Eu criaria uma espécie de método de fábrica criando um dicionário em vez do __init__método da classe . Mas você está certo: eu separaria as coisas que pertencem uma à outra.
deamon
88
não poderia discordar mais de você: dicionários, conjuntos, listas e tuplas estão todos lá para agrupar dados relacionados. de forma alguma existe a suposição de que os valores de um dicionário devam ou devam ser do mesmo tipo de dados, muito pelo contrário. em muitas listas e conjuntos, os valores terão o mesmo tipo, mas isso ocorre principalmente porque é o que gostamos de enumerar juntos. Na verdade, acho que o uso generalizado de classes para manter dados é um abuso de oop; quando você pensa sobre problemas de serialização, pode ver facilmente por quê.
fluxo
4
É programação orientada a OBJETOS e não orientada a classes por um motivo: lidamos com objetos. Um objeto é caracterizado por 2 (3) propriedades: 1. estado (membros) 2. comportamento (métodos) e 3. instância pode ser descrita em várias palavras. Portanto, as classes são para agrupar estado e comportamento.
friendzis
14
Eu marquei isso porque você sempre deve usar a estrutura de dados mais simples, sempre que possível. Nesse caso, um dicionário é suficiente para a finalidade pretendida. A questão where would your __init__ code go?é preocupante. Isso poderia persuadir um desenvolvedor menos experiente de que apenas as classes devem ser usadas, uma vez que um método init não é usado em um dicionário. Absurdo.
Lloyd Moore
1
@Ralf Uma classe Foo é meramente um tipo, como int e string. Você armazena valor em um tipo inteiro ou variável foo do tipo inteiro? Diferença semântica sutil, mas importante. Em línguas como a C, essa distinção não é muito relevante fora dos círculos acadêmicos. Embora a maioria das linguagens OO tenha suporte a variáveis ​​de classe, o que torna a distinção entre classe e objeto / instância extremamente relevante - você armazena dados em classe (compartilhados por todas as instâncias) ou em um objeto específico? Não seja mordido
friendzis
44

Use um dicionário, a menos que você precise do mecanismo extra de uma classe. Você também pode usar um namedtuplepara uma abordagem híbrida:

>>> from collections import namedtuple
>>> request = namedtuple("Request", "environ request_method url_scheme")
>>> request
<class '__main__.Request'>
>>> request.environ = "foo"
>>> request.environ
'foo'

As diferenças de desempenho aqui serão mínimas, embora eu ficaria surpreso se o dicionário não fosse mais rápido.

Katriel
fonte
15
"As diferenças de desempenho aqui serão mínimas , embora eu ficasse surpreso se o dicionário não fosse significativamente mais rápido." Isso não computa. :)
mipadi
1
@mipadi: isso é verdade. Agora corrigido: p
Katriel
O dict é 1,5 vezes mais rápido que o namedtuple e duas vezes mais rápido que uma classe sem slots. Verifique minha postagem sobre esta resposta.
alexpinho98
@ alexpinho98: Tentei muito encontrar o 'post' a que você se refere, mas não consigo encontrar! você pode fornecer o URL. Obrigado!
Dan Oblinger
@DanOblinger Presumo que ele queira dizer sua resposta abaixo.
Adam Lewis
37

Uma classe em python é um dict por baixo. Você obtém alguma sobrecarga com o comportamento da classe, mas não será capaz de notá-lo sem um criador de perfil. Nesse caso, acredito que você se beneficia com a aula porque:

  • Toda a sua lógica vive em uma única função
  • É fácil de atualizar e permanece encapsulado
  • Se você mudar alguma coisa depois, pode facilmente manter a interface igual
Bruce Armstrong
fonte
Toda a sua lógica não vive em uma única função. Uma classe é uma tupla de estado compartilhado e geralmente um ou mais métodos. Se você alterar uma classe, não terá nenhuma garantia sobre sua interface.
Lloyd Moore
25

Eu acho que o uso de cada um é muito subjetivo para eu entrar nisso, então vou me limitar aos números.

Eu comparei o tempo que leva para criar e alterar uma variável em um dicionário, uma classe new_style e uma classe new_style com slots.

Aqui está o código que usei para testá-lo (é um pouco confuso, mas faz o trabalho).

import timeit

class Foo(object):

    def __init__(self):

        self.foo1 = 'test'
        self.foo2 = 'test'
        self.foo3 = 'test'

def create_dict():

    foo_dict = {}
    foo_dict['foo1'] = 'test'
    foo_dict['foo2'] = 'test'
    foo_dict['foo3'] = 'test'

    return foo_dict

class Bar(object):
    __slots__ = ['foo1', 'foo2', 'foo3']

    def __init__(self):

        self.foo1 = 'test'
        self.foo2 = 'test'
        self.foo3 = 'test'

tmit = timeit.timeit

print 'Creating...\n'
print 'Dict: ' + str(tmit('create_dict()', 'from __main__ import create_dict'))
print 'Class: ' + str(tmit('Foo()', 'from __main__ import Foo'))
print 'Class with slots: ' + str(tmit('Bar()', 'from __main__ import Bar'))

print '\nChanging a variable...\n'

print 'Dict: ' + str((tmit('create_dict()[\'foo3\'] = "Changed"', 'from __main__ import create_dict') - tmit('create_dict()', 'from __main__ import create_dict')))
print 'Class: ' + str((tmit('Foo().foo3 = "Changed"', 'from __main__ import Foo') - tmit('Foo()', 'from __main__ import Foo')))
print 'Class with slots: ' + str((tmit('Bar().foo3 = "Changed"', 'from __main__ import Bar') - tmit('Bar()', 'from __main__ import Bar')))

E aqui está a saída ...

Criando ...

Dict: 0.817466186345
Class: 1.60829183597
Class_with_slots: 1.28776730003

Alterando uma variável ...

Dict: 0.0735140918748
Class: 0.111714198313
Class_with_slots: 0.10618612142

Então, se você está apenas armazenando variáveis, você precisa de velocidade e não vai exigir que você faça muitos cálculos, eu recomendo usar um dicionário (você pode apenas fazer uma função que se pareça com um método). Mas, se você realmente precisa de classes, lembre-se - sempre use __ slots __ .

Nota:

Testei a 'Classe' com as classes new_style e old_style. Acontece que as classes old_style são mais rápidas de criar, mas mais lentas de modificar (não muito, mas significativo se você estiver criando muitas classes em um loop fechado (dica: você está fazendo isso errado)).

Além disso, os tempos para criar e alterar variáveis ​​podem ser diferentes no seu computador, pois o meu é antigo e lento. Certifique-se de testar você mesmo para ver os resultados 'reais'.

Editar:

Posteriormente, testei o namedtuple: não posso modificá-lo, mas para criar os 10.000 exemplos (ou algo parecido) demorou 1,4 segundos, então o dicionário é realmente o mais rápido.

Se eu alterar a função dict para incluir as chaves e valores e retornar o dict em vez da variável que contém o dict quando eu crio, isso me dá 0,65 em vez de 0,8 segundos.

class Foo(dict):
    pass

Criar é como uma classe com slots e alterar a variável é o mais lento (0,17 segundos), portanto , não use essas classes . vá para um dict (velocidade) ou para a classe derivada do objeto ('doce de sintaxe')

alexpinho98
fonte
Gostaria de ver os números de uma subclasse de dict(sem métodos substituídos, acho?). Ele tem o mesmo desempenho de uma classe de novo estilo que foi escrita do zero?
Benjamin Hodgson
12

Eu concordo com @adw. Eu nunca representaria um "objeto" (no sentido OO) com um dicionário. Os dicionários agregam pares de nome / valor. As classes representam objetos. Eu vi um código onde os objetos são representados com dicionários e não está claro qual é a forma real da coisa. O que acontece quando certos nomes / valores não estão lá? O que restringe o cliente de colocar qualquer coisa dentro. Ou de tentar retirar qualquer coisa. A forma da coisa sempre deve ser claramente definida.

Ao usar Python, é importante construir com disciplina, pois a linguagem permite que o autor dê um tiro no próprio pé de várias maneiras.

Jaydel
fonte
9
As respostas no SO são classificadas por votos e as respostas com a mesma contagem de votos são ordenadas aleatoriamente. Portanto, esclareça quem você quer dizer com "o último pôster".
Mike DeSimone,
4
E como você sabe que algo que tem apenas campos e nenhuma funcionalidade é um "objeto" no sentido OO?
Muhammad Alkarouri,
1
Meu teste decisivo é "a estrutura desses dados está fixa?". Se sim, use um objeto, caso contrário, um dict. Partir disso só vai criar confusão.
weberc2
Você tem que analisar o contexto do problema que está resolvendo. Os objetos devem ser relativamente óbvios, uma vez que você esteja familiarizado com a paisagem. Pessoalmente, acho que devolver um dicionário deve ser seu último recurso, a menos que o que você está retornando DEVERÁ ser um mapeamento de pares nome / valor. Já vi muitos códigos desleixados em que tudo que entra e sai dos métodos é apenas um dicionário. É preguiçoso. Eu amo linguagens digitadas dinamicamente, mas também gosto que meu código seja claro e lógico. Colocar tudo em um dicionário pode ser uma conveniência que esconde o significado.
Jaydel
5

Eu recomendaria uma aula, pois são todos os tipos de informações envolvidas em uma solicitação. Se alguém usasse um dicionário, esperaria que os dados armazenados fossem muito mais semelhantes em natureza. Uma orientação que eu mesmo tendo a seguir é que, se quiser fazer um loop por todo o conjunto de pares chave-> valor e fazer algo, uso um dicionário. Caso contrário, os dados aparentemente têm muito mais estrutura do que um mapeamento básico de chave-> valor, o que significa que uma classe provavelmente seria uma alternativa melhor.

Portanto, fique com a classe.

Estigma
fonte
2
Eu discordo totalmente. Não há razão para restringir o uso de dicionários a itens para iterar. Eles servem para manter um mapeamento.
Katriel
1
Leia um pouco mais de perto, por favor. Eu disse que pode querer fazer um loop , não vai . Para mim, usar um dicionário implica que há uma grande semelhança funcional entre as chaves e os valores. Por exemplo, nomes com idades. Usar uma classe significaria que as várias 'chaves' têm valores com significados muito diferentes. Por exemplo, pegue uma classe PErson. Aqui, 'nome' seria uma string e 'amigos' poderia ser uma lista, ou um dicionário, ou algum outro objeto adequado. Você não faria um loop sobre todos esses atributos como parte do uso normal desta classe.
Estigma
1
Eu acho que a distinção entre classes e dicionários é indistinta em Python pelo fato de que os primeiros são implementados usando o último ('slots' não obstante). Eu sei que isso me confundiu um pouco quando eu estava aprendendo a língua pela primeira vez (junto com o fato de que classes eram objetos e, portanto, instâncias de algumas metaclasses misteriosas).
martineau
4

Se tudo o que você deseja alcançar é um doce de sintaxe em obj.bla = 5vez de obj['bla'] = 5, especialmente se tiver que repetir muito isso, talvez queira usar alguma classe de contêiner simples como na sugestão de martineaus. No entanto, o código lá é bastante inchado e desnecessariamente lento. Você pode mantê-lo simples assim:

class AttrDict(dict):
    """ Syntax candy """
    __getattr__ = dict.__getitem__
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__

Outro motivo para mudar para namedtuples ou uma classe __slots__pode ser o uso de memória. Os dictos exigem muito mais memória do que os tipos de lista, portanto, esse pode ser um ponto a se pensar.

De qualquer forma, no seu caso específico, não parece haver nenhuma motivação para abandonar sua implementação atual. Você não parece manter milhões desses objetos, portanto, nenhum tipo derivado de lista é necessário. E, na verdade, contém alguma lógica funcional dentro do __init__, então você também não deve continuar AttrDict.

Michael
fonte
types.SimpleNamespace(disponível desde Python 3.3) é uma alternativa ao AttrDict customizado.
Cristian Ciupitu
4

Pode ser possível ter seu bolo e comê-lo também. Em outras palavras, você pode criar algo que forneça a funcionalidade de uma classe e de uma instância de dicionário. Veja a receita Dɪᴄᴛɪᴏɴᴀʀʏ ᴡɪᴛʜ ᴀᴛᴛʀɪʙᴜᴛᴇ-sᴛʏʟᴇ ᴀᴄᴄᴇss do ActiveState e comentários sobre as maneiras de fazer isso.

Se você decidir usar uma classe regular em vez de uma subclasse, descobri que a receita Tʜᴇ sɪᴍᴘʟᴇ ʙᴜᴛ ʜᴀɴᴅʏ "ᴄᴏʟʟᴇᴄᴛᴏʀ ᴏғ ᴀ ʙᴜɴᴄʜ ᴏғ ɴᴀᴍᴇᴅ sᴛᴜғғ" ᴄʟᴀss (de Alex Martelli) é muito flexível e útil para o tipo de coisa que parece que você está fazendo (ou seja, criar um agregador de informações relativamente simples). Por ser uma classe, você pode facilmente estender sua funcionalidade ainda mais adicionando métodos.

Por último, deve-se observar que os nomes dos membros da classe devem ser identificadores legais do Python, mas as chaves de dicionário não - portanto, um dicionário forneceria maior liberdade a esse respeito porque as chaves podem ser qualquer coisa hashble (mesmo algo que não seja uma string).

Atualizar

Uma classe object(que não tem uma __dict__) subclasse chamada SimpleNamespace(que tem uma) foi adicionada ao typesmódulo Python 3.3 e é mais uma alternativa.

martineau
fonte