Existe uma vantagem em definir uma classe dentro de outra classe em Python?

106

O que estou falando aqui são classes aninhadas. Basicamente, tenho duas classes que estou modelando. Uma classe DownloadManager e uma classe DownloadThread. O conceito OOP óbvio aqui é composição. No entanto, composição não significa necessariamente aninhamento, certo?

Eu tenho um código parecido com este:

class DownloadThread:
    def foo(self):
        pass

class DownloadManager():
    def __init__(self):
        dwld_threads = []
    def create_new_thread():
        dwld_threads.append(DownloadThread())

Mas agora estou me perguntando se há uma situação em que o aninhamento seria melhor. Algo como:

class DownloadManager():
    class DownloadThread:
        def foo(self):
            pass
    def __init__(self):
        dwld_threads = []
    def create_new_thread():
        dwld_threads.append(DownloadManager.DownloadThread())
fuentesjr
fonte

Respostas:

121

Você pode querer fazer isso quando a classe "interna" for única, que nunca será usada fora da definição da classe externa. Por exemplo, para usar uma metaclasse, às vezes é útil fazer

class Foo(object):
    class __metaclass__(type):
        .... 

em vez de definir uma metaclasse separadamente, se você a usar apenas uma vez.

A única outra vez que usei classes aninhadas como essa, usei a classe externa apenas como um namespace para agrupar um monte de classes intimamente relacionadas:

class Group(object):
    class cls1(object):
       ...

    class cls2(object):
       ...

Em seguida, de outro módulo, você pode importar Grupo e referir-se a eles como Group.cls1, Group.cls2 etc. No entanto, pode-se argumentar que você pode realizar exatamente o mesmo (talvez de uma forma menos confusa) usando um módulo.

dF.
fonte
13
Eu - eu - argumentaria que, no segundo caso, você - definitivamente - seria melhor servido usando um módulo ou pacote, que são projetados para serem namespaces.
Matthew Trevor,
5
Esta é uma resposta fantástica. Simples, direto ao ponto, e menciona o método alternativo de uso de módulos. +1
CornSmith
5
Apenas para registro, para aqueles que teimosamente se recusam ou não podem organizar seu código em um pacote hierárquico e módulos apropriados, usar classes como um namespace pode às vezes ser melhor do que não usar nada.
Acumenus
19

Não conheço Python, mas sua pergunta parece muito geral. Ignore-me se for específico para Python.

O aninhamento de classes tem tudo a ver com escopo. Se você acha que uma classe só fará sentido no contexto de outra, então a primeira provavelmente é uma boa candidata para se tornar uma classe aninhada.

É um padrão comum tornar as classes auxiliares como classes privadas aninhadas.

André Chalella
fonte
9
Apenas para constar, o conceito de privateclasses não se aplica tanto ao Python, mas um escopo de uso implícito ainda se aplica definitivamente.
Acumenus
7

Há outro uso para classe aninhada, quando se deseja construir classes herdadas cujas funcionalidades aprimoradas são encapsuladas em uma classe aninhada específica.

Veja este exemplo:

class foo:

  class bar:
    ...  # functionalities of a specific sub-feature of foo

  def __init__(self):
    self.a = self.bar()
    ...

  ...  # other features of foo


class foo2(foo):

  class bar(foo.bar):
    ... # enhanced functionalities for this specific feature

  def __init__(self):
    foo.__init__(self)

Observe que no construtor de foo, a linha self.a = self.bar()construirá um foo.barquando o objeto que está sendo construído for realmente um fooobjeto e um foo2.barobjeto quando o objeto que estiver sendo construído for na verdade um foo2objeto.

Se a classe barfosse definida fora da classe foo, bem como sua versão herdada (que seria chamada bar2por exemplo), então definir a nova classe foo2seria muito mais doloroso, porque o construtor de foo2precisaria ter sua primeira linha substituída por self.a = bar2(), o que implica reescrever todo o construtor.

Thomas
fonte
6

Não há realmente nenhum benefício em fazer isso, exceto se você estiver lidando com metaclasses.

a classe: suíte realmente não é o que você pensa que é. É um escopo estranho e faz coisas estranhas. Realmente nem chega a ser uma aula! É apenas uma forma de coletar algumas variáveis ​​- o nome da classe, as bases, um pequeno dicionário de atributos e uma metaclasse.

O nome, o dicionário e as bases são todos passados ​​para a função que é a metaclasse, e então é atribuída à variável 'nome' no escopo onde a classe: suíte estava.

O que você pode ganhar mexendo com metaclasses e, de fato, aninhando classes dentro de suas classes padrão de estoque, é mais difícil de ler o código, mais difícil de entender o código e erros estranhos que são terrivelmente difíceis de entender sem estar intimamente familiarizado com o porquê da 'classe' escopo é totalmente diferente de qualquer outro escopo python.

Jerub
fonte
3
Discordo: aninhar classes de exceção é muito útil, porque os usuários de sua classe podem verificar suas exceções personalizadas sem ter que fazer importações complicadas. Por exemploexcept myInstanceObj.CustomError, e:
RobM
2
@Jerub, por que isso é ruim?
Robert Siemer
5

Você pode usar uma classe como gerador de classe. Como (em alguns códigos improvisados ​​:)

class gen(object):
    class base_1(object): pass
    ...
    class base_n(object): pass

    def __init__(self, ...):
        ...
    def mk_cls(self, ..., type):
        '''makes a class based on the type passed in, the current state of
           the class, and the other inputs to the method'''

Eu sinto que quando você precisar dessa funcionalidade, ela ficará muito clara para você. Se você não precisa fazer algo semelhante, provavelmente não é um bom caso de uso.

tim.tadh
fonte
1

Não, composição não significa aninhamento. Faria sentido ter uma classe aninhada se você quiser ocultá-la mais no namespace da classe externa.

De qualquer forma, não vejo nenhuma utilidade prática para aninhamento no seu caso. Isso tornaria o código mais difícil de ler (entender) e também aumentaria o recuo, o que tornaria as linhas mais curtas e mais sujeitas a divisão.

Cristian Ciupitu
fonte