Tudo bem ter várias classes no mesmo arquivo em Python?

18

Estou voltando recentemente ao mundo Python após anos de Java e PHP. Embora a linguagem em si seja bastante simples, estou lutando com alguns problemas 'menores' que não consigo entender - e para os quais não consegui encontrar respostas nos inúmeros documentos e tutoriais que li até aqui .

Para o praticante experiente de Python, essa pergunta pode parecer boba, mas eu realmente quero uma resposta para que eu possa ir mais longe com a linguagem:

Em Java e PHP ( embora não seja estritamente necessário ), é esperado que você escreva cada um classem seu próprio arquivo, com o nome do arquivo classcomo o de uma prática recomendada.

Mas em Python, ou pelo menos nos tutoriais que eu verificados, é ok para ter múltiplas classes no mesmo arquivo.

Essa regra se aplica à produção, código pronto para implantação ou é feita apenas por uma questão de brevidade no código apenas educativo?

Olivier Malki
fonte

Respostas:

13

Tudo bem ter várias classes no mesmo arquivo em Python?

Sim. Tanto de uma perspectiva filosófica quanto prática.

No Python, os módulos são um espaço para nome que existe uma vez na memória.

Digamos que tivemos a seguinte estrutura de diretórios hipotéticos, com uma classe definida por arquivo:

                    Defines
 abc/
 |-- callable.py    Callable
 |-- container.py   Container
 |-- hashable.py    Hashable
 |-- iterable.py    Iterable
 |-- iterator.py    Iterator
 |-- sized.py       Sized
 ... 19 more

Todas essas classes estão disponíveis no collectionsmódulo e (existem, de fato, 25 no total) definidas no módulo de biblioteca padrão em_collections_abc.py

Existem algumas questões aqui que, acredito, tornam a _collections_abc.pysuperior à estrutura de diretório hipotético alternativa.

  • Esses arquivos são classificados em ordem alfabética. Você pode classificá-los de outras maneiras, mas não conheço um recurso que classifique arquivos por dependências semânticas. A origem do módulo _collections_abc é organizada por dependência.
  • Em casos não patológicos, os módulos e as definições de classe são singletons, ocorrendo uma vez na memória. Haveria um mapeamento bijetivo de módulos para classes - tornando os módulos redundantes.
  • O crescente número de arquivos torna menos conveniente a leitura casual das classes (a menos que você tenha um IDE que simplifique) - tornando-o menos acessível para pessoas sem ferramentas.

Você é impedido de dividir grupos de classes em módulos diferentes quando achar desejável do ponto de vista organizacional e do namespace?

Não.

Do Zen de Python , que reflete a filosofia e os princípios sob os quais ele cresceu e evoluiu:

Os espaços para nome são uma ótima idéia - vamos fazer mais!

Mas tenhamos em mente que também diz:

Flat é melhor que aninhado.

Python é incrivelmente limpo e fácil de ler. Incentiva você a lê-lo. Colocar cada classe separada em um arquivo separado desencoraja a leitura. Isso vai contra a filosofia central do Python. Veja a estrutura da Biblioteca Padrão , a grande maioria dos módulos são módulos de arquivo único, não pacotes. Eu diria a você que o código Python idiomático é escrito no mesmo estilo que a lib padrão do CPython.

Aqui está o código real do módulo da classe base abstrata . Eu gosto de usá-lo como referência para a denotação de vários tipos abstratos na linguagem.

Você diria que cada uma dessas classes deve exigir um arquivo separado?

class Hashable:
    __metaclass__ = ABCMeta

    @abstractmethod
    def __hash__(self):
        return 0

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Hashable:
            try:
                for B in C.__mro__:
                    if "__hash__" in B.__dict__:
                        if B.__dict__["__hash__"]:
                            return True
                        break
            except AttributeError:
                # Old-style class
                if getattr(C, "__hash__", None):
                    return True
        return NotImplemented


class Iterable:
    __metaclass__ = ABCMeta

    @abstractmethod
    def __iter__(self):
        while False:
            yield None

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Iterable:
            if _hasattr(C, "__iter__"):
                return True
        return NotImplemented

Iterable.register(str)


class Iterator(Iterable):

    @abstractmethod
    def next(self):
        'Return the next item from the iterator. When exhausted, raise StopIteration'
        raise StopIteration

    def __iter__(self):
        return self

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Iterator:
            if _hasattr(C, "next") and _hasattr(C, "__iter__"):
                return True
        return NotImplemented


class Sized:
    __metaclass__ = ABCMeta

    @abstractmethod
    def __len__(self):
        return 0

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Sized:
            if _hasattr(C, "__len__"):
                return True
        return NotImplemented


class Container:
    __metaclass__ = ABCMeta

    @abstractmethod
    def __contains__(self, x):
        return False

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Container:
            if _hasattr(C, "__contains__"):
                return True
        return NotImplemented


class Callable:
    __metaclass__ = ABCMeta

    @abstractmethod
    def __call__(self, *args, **kwds):
        return False

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Callable:
            if _hasattr(C, "__call__"):
                return True
        return NotImplemented

Então, cada um deve ter seu próprio arquivo?

Espero que não.

Esses arquivos não são apenas códigos - são documentação sobre a semântica do Python.

São talvez 10 a 20 linhas em média. Por que eu deveria ter que ir para um arquivo completamente separado para ver outras 10 linhas de código? Isso seria altamente impraticável. Além disso, haveria importações de clichê quase idênticas em cada arquivo, adicionando mais linhas de código redundantes.

Eu acho bastante útil saber que existe um único módulo em que posso encontrar todas essas classes básicas abstratas, em vez de precisar examinar uma lista de módulos. Vê-los em contexto um com o outro me permite entendê-los melhor. Quando vejo que um iterador é um iterável, posso revisar rapidamente o que é um iterável olhando para cima.

Às vezes, acabo tendo algumas aulas muito curtas. Eles permanecem no arquivo, mesmo que precisem crescer com o tempo. Às vezes, módulos maduros têm mais de 1000 linhas de código. Mas o ctrl-f é fácil, e alguns IDE facilitam a visualização dos contornos do arquivo - portanto, independentemente do tamanho do arquivo, você pode acessar rapidamente qualquer objeto ou método que esteja procurando.

Conclusão

Minha direção, no contexto do Python, é preferir manter definições de classe relacionadas e semanticamente semelhantes no mesmo arquivo. Se o arquivo crescer tanto que se torne pesado, considere uma reorganização.

Aaron Hall
fonte
1
Bem, enquanto eu entendo, graças ao código que você enviou que não há problema em ter várias classes no mesmo arquivo, não consigo achar o argumento muito convincente. Por exemplo, era muito comum no PHP ter um arquivo inteiro com apenas um código semelhante a este:class SomeException extends \Exception {}
Olivier Malki
3
Comunidades diferentes têm diferentes padrões de codificação. O pessoal do Java olha para o python e diz "por que ele permite várias classes por arquivo !?". O pessoal do Python olha para o Java e diz "por que ele exige que cada classe tenha seu próprio arquivo !?". É melhor seguir o estilo da comunidade em que você está trabalhando.
Gort the Robot
Também estou confuso aqui. Aparentemente, eu não entendi algumas coisas sobre Python com a minha resposta. Mas alguém aplicaria "flat is better than anested" para adicionar o maior número possível de métodos a uma classe? Em geral, eu pensaria que os princípios de coesão e SRP ainda se aplicam a um módulo que favorece módulos que fornecem classes que se relacionam intimamente entre si em funcionalidade (embora talvez muito bem mais de uma classe, uma vez que um módulo modela um conceito de pacote mais grosseiro do que uma única classe ), especialmente porque qualquer variável de escopo do módulo (embora esperemos que evitada em geral) aumentaria de escopo.
1
O Zen do Python é uma lista de princípios que estão em tensão um com o outro. Pode-se responder de acordo com o seu argumento: "Esparso é melhor que denso". - que segue imediatamente: "Flat é melhor que aninhado". As linhas individuais de O Zen de Python podem facilmente ser mal utilizadas e levadas ao extremo, mas, no conjunto, podem ajudar na arte de codificar e encontrar um terreno comum onde pessoas razoáveis ​​possam discordar. Eu não acho que as pessoas considerariam meus exemplos de código densos, mas o que você descreve me parece muito denso.
Aaron Hall
Obrigado Jeffrey Albertson / Comic Book Guy. :) A maioria dos usuários do Python não deve usar os métodos especiais (sublinhado duplo), mas permite que um designer / arquiteto principal se envolva em metaprogramação para fazer uso personalizado de operadores, comparadores, notação de subscrito, contextos e outros características da linguagem. Desde que não violem o princípio da menor surpresa, acho que a relação dano / valor é infinitesimal.
Aaron Hall
4

Ao estruturar seu aplicativo em Python, você precisa pensar em termos de pacotes e módulos.

Módulos são sobre os arquivos que você está falando. É bom ter várias classes dentro do mesmo módulo. O objetivo é que todas as classes dentro do mesmo módulo atendam ao mesmo objetivo / lógica. Se o módulo demorar demais, pense em subdividi-lo redesenhando sua lógica.

Não se esqueça de ler de tempos em tempos sobre o Índice de propostas de aprimoramento do Python .


fonte
2

A resposta real para isso é geral e não depende da linguagem usada: o que deve estar em um arquivo não depende principalmente de quantas classes ele define. Depende da conexão lógica e da complexidade. Período.

Portanto, se você tiver algumas classes muito pequenas e altamente interconectadas, elas deverão ser agrupadas no mesmo arquivo. Você deve dividir uma classe se ela não estiver firmemente conectada a outra classe ou se for muito complexo para ser incluído em outra classe.

Dito isto, a regra de uma classe por arquivo geralmente é uma boa heurística. No entanto, existem exceções importantes: Uma classe auxiliar pequena que é realmente apenas os detalhes de implementação de sua única classe de usuário geralmente deve ser agrupada no arquivo dessa classe de usuário. Da mesma forma, se você tem três classes vector2, vector3e vector4, não há razão pouco provável que implementá-los em arquivos separados.

cmaster - restabelece monica
fonte