É uma prática recomendada pré-inicializar atributos em uma classe ou adicioná-los ao longo do caminho?

12

Sinto muito se essa é uma pergunta absolutamente sofisticada, mas estou curiosa para saber quais são as melhores práticas e não consigo encontrar uma boa resposta no Google.

Em Python, eu costumo usar uma classe vazia como um contêiner de estrutura de dados com capacidade super grande (como um arquivo JSON) e adicionar atributos ao longo do caminho:

class DataObj:
    "Catch-all data object"
    def __init__(self):
        pass

def processData(inputs):
    data = DataObj()
    data.a = 1
    data.b = "sym"
    data.c = [2,5,2,1]

Isso me dá uma tremenda quantidade de flexibilidade, porque o objeto contêiner pode essencialmente armazenar qualquer coisa. Portanto, se novos requisitos surgirem, vou adicioná-lo como outro atributo ao objeto DataObj (que passo em meu código).

No entanto, recentemente me impressionou (por programadores de FP) que essa é uma prática terrível, porque torna muito difícil a leitura do código. É preciso passar por todo o código para descobrir quais atributos o DataObj realmente possui.

Pergunta : Como posso reescrever isso para maior facilidade de manutenção sem sacrificar a flexibilidade?

Há alguma idéia da programação funcional que eu possa adotar?

Estou procurando as melhores práticas por aí.

Nota : uma idéia é pré-inicializar a classe com todos os atributos que se espera encontrar, por exemplo

class DataObj:
    "Catch-all data object"
    def __init__(self):
        data.a = 0
        data.b = ""
        data.c = []

def processData(inputs):
    data = DataObj()
    data.a = 1
    data.b = "sym"
    data.c = [2,5,2,1]

Isso é realmente uma boa ideia? E se eu não souber quais são meus atributos a priori?

Gilead
fonte
Suas estruturas de dados são tão mutáveis ​​que você parece estar preocupado com a manutenção delas. No seu tempo livre abundante ™, tente ler este artigo sobre modelos de dados imutáveis . Isso pode mudar completamente a maneira como você pensa sobre os dados.
9000
@ 9000 Um artigo como esse convence os que já estão convencidos. Para mim, parecia mais um tutorial do que um motivo (a lista de porquês não é realmente convincente, a menos que você sinta que tem essas necessidades específicas). Para mim, isso não convence alguém que atualiza faturas no VB de que ter que fazer constantemente novas cópias de seu objeto de fatura faz sentido (adicionar pagamento, novo objeto de fatura; adicionar peça, novo objeto de fatura).
Paul

Respostas:

10

Como posso reescrever isso para maior facilidade de manutenção sem sacrificar a flexibilidade?

Você não A flexibilidade é precisamente o que causa o problema. Se qualquer código em qualquer lugar pode alterar os atributos de um objeto, a manutenção já está em pedaços. Idealmente, toda classe tem um conjunto de atributos que são definidos como pedra depois __init__e o mesmo para cada instância. Nem sempre é possível ou sensato, mas deve ser o caso sempre que você não tiver realmente boas razões para evitá-lo.

uma idéia é pré-inicializar a classe com todos os atributos que se espera encontrar

Isso não é uma boa idéia. Claro, o atributo está lá, mas pode ter um valor falso, ou mesmo válido, que encobre o código que não atribui o valor (ou um erro de ortografia). AttributeErroré assustador, mas obter resultados errados é pior. Os valores padrão em geral são bons, mas para escolher um padrão adequado (e decidir o que é necessário), é necessário saber para que o objeto é usado.

E se eu não souber quais são meus atributos a priori?

Então você está ferrado em qualquer caso e deve usar um dict ou lista em vez de nomes de atributos codificados. Mas suponho que você quis dizer "... no momento em que escrevo a classe do contêiner". Em seguida, a resposta é: "Você pode editar arquivos em passo de bloqueio, duh". Precisa de um novo atributo? Adicione um atributo de frigging à classe de contêiner. Há mais código usando essa classe e ele não precisa desse atributo? Considere dividir as coisas em duas classes separadas (use mixins para permanecer SECO), portanto, faça isso opcional se fizer sentido.

Se você tem medo de escrever aulas de contêineres repetitivas: aplique metaprogramação criteriosamente ou use-o collections.namedtuplese não precisar alterar os membros após a criação (seus amigos do FP ficariam satisfeitos).


fonte
7

Você sempre pode usar a aula de Alex Martelli . No seu caso:

class DataObj:
    "Catch-all data object"
    def __init__(self, **kwds):
        self.__dict__.update(kwds)

def processData(inputs):
    data = DataObj(a=1, b="sym", c=[2,5,2,1])

Dessa forma, pelo menos fica claro para o leitor que os dados são apenas um armazenamento de dados estúpidos e é possível ver imediatamente quais valores são armazenados sob o nome, pois tudo acontece em uma linha.

E sim, fazer as coisas dessa maneira é de fato uma boa ideia, às vezes.

pillmuncher
fonte
1

Eu provavelmente usaria a segunda abordagem, possivelmente usando Nonepara indicar dados inválidos. É verdade que é difícil ler / manter se você adicionar atributos posteriormente. No entanto, mais informações sobre o objetivo dessa classe / objeto forneceriam informações sobre por que a primeira idéia é um projeto ruim: onde você teria uma classe completamente vazia sem métodos ou dados padrão? Por que você não sabe quais atributos a classe possui?

É possível que processDataisso seja melhor como método ( process_datapara seguir as convenções de nomenclatura de python), pois ele atua na classe. Dado o exemplo, parece que poderia ser melhor como uma estrutura de dados (onde a dictpode ser suficiente).

Dado um exemplo real, você pode levar a questão para o CodeReview , onde eles podem ajudar a refatorar o código.

Casey Kuball
fonte