Pergunta

Suponha que você tenha um método python que obtém um tipo como parâmetro; é possível determinar se o tipo de dado é uma classe aninhada?
Por exemplo. neste exemplo:

def show_type_info(t):
    print t.__name__
    # print outer class name (if any) ...

class SomeClass:
    pass

class OuterClass:
    class InnerClass:
        pass

show_type_info(SomeClass)
show_type_info(OuterClass.InnerClass)

Gostaria que a chamada para show_type_info(OuterClass.InnerClass) para mostrar também que InnerClass é definida dentro OuterClass.

Foi útil?

Solução

AFAIK, dada uma classe e nenhuma outra informação, você não pode dizer se é ou não é uma classe aninhada. No entanto, ver aqui para como você pode usar um decorador para determinar isso.

O problema é que uma classe aninhada é simplesmente uma classe normal que é um atributo de sua classe externa. Outras soluções que você pode esperar para trabalhar, provavelmente, não vai - inspect.getmro, por exemplo, só lhe dá classes de base, não classes de exteriores.

Além disso, classes aninhadas raramente são necessários. Eu fortemente reconsiderar se isso é uma boa abordagem em cada caso particular onde você se sentir tentado a usar um.

Outras dicas

Um ofertas classe interna não particular especial apresenta em Python. É apenas uma propriedade do objeto de classe, não é diferente de um número inteiro ou a propriedade string. Seu exemplo OuterClass / InnerClass pode ser reescrita exatamente como:

class OuterClass(): pass
class InnerClass(): pass
OuterClass.InnerClass= InnerClass

InnerClass não pode saber se foi declarada dentro de outra classe, porque isso é apenas uma ligação variável simples. A magia que faz com que os métodos vinculados saber sobre seu proprietário ‘self’ não se aplica aqui.

A magia decorador innerclass no link John postou é uma abordagem interessante, mas eu não iria usá-lo como está. Ele não armazena em cache as classes que ele cria para cada objeto externo, de modo a obter um novo InnerClass cada vez que você chamar outerinstance.InnerClass:

>>> o= OuterClass()
>>> i= o.InnerClass()
>>> isinstance(i, o.InnerClass)
False # huh?
>>> o.InnerClass is o.InnerClass
False # oh, whoops...

Além disso, a forma como ele tenta replicar o comportamento Java de fazer variáveis ??de classe exteriores disponíveis na classe interna com getattr / setattr é muito duvidoso, e desnecessário realmente (uma vez que o caminho mais Pythonic seria chamar i .__ exterior __. Attr explicitamente ).

Realmente uma classe aninhada não é diferente de qualquer outra classe - que só acontece a ser definido em outro lugar que o namespace de nível superior (dentro de outra classe em vez). Se modificar a descrição de "nested" para "não-top-level", então você pode ser capaz de chegar perto o suficiente para o que você precisa.

por exemplo:

import inspect

def not_toplevel(cls):
    m = inspect.getmodule(cls)
    return not (getattr(m, cls.__name__, []) is cls)

Este trabalho vontade para casos comuns, mas não pode fazer o que quiser em situações onde as aulas são renomeados ou de outra forma manipulados após definição. Por exemplo:

class C:             # not_toplevel(C) = False
    class B: pass    # not_toplevel(C.B) = True

B=C.B                # not_toplevel(B) = True

D=C                  # D is defined at the top, but...
del C                # not_toplevel(D) = True

def getclass():      # not_toplevel(getclass()) = True
    class C: pass

Obrigado a todos por suas respostas.
Eu encontrei esta solução possível usando metaclasses; Eu tenho feito isso mais para obstination de necessidade real, e é feito de uma forma que não será aplicável a python 3.
Quero compartilhar esta solução de qualquer maneira, então eu estou postando-lo aqui.

#!/usr/bin/env python
class ScopeInfo(type): # stores scope information
    __outers={} # outer classes
    def __init__(cls, name, bases, dict):
        super(ScopeInfo, cls).__init__(name, bases, dict)
        ScopeInfo.__outers[cls] = None
        for v in dict.values(): # iterate objects in the class's dictionary
            for t in ScopeInfo.__outers:
                if (v == t): # is the object an already registered type?
                    ScopeInfo.__outers[t] = cls
                    break;
    def FullyQualifiedName(cls):
        c = ScopeInfo.__outers[cls]
        if c is None:
            return "%s::%s" % (cls.__module__,cls.__name__)
        else:
            return "%s.%s" % (c.FullyQualifiedName(),cls.__name__)

__metaclass__ = ScopeInfo

class Outer:
    class Inner:
        class EvenMoreInner:
            pass

print Outer.FullyQualifiedName()
print Outer.Inner.FullyQualifiedName()
print Outer.Inner.EvenMoreInner.FullyQualifiedName()
X = Outer.Inner
del Outer.Inner
print X.FullyQualifiedName()

Se você não configurá-lo mesmo, eu não acredito que haja qualquer forma de determinar se a classe está aninhada. Como qualquer maneira uma classe Python não pode ser usado como um espaço de nomes (ou pelo menos não facilmente), eu diria que a melhor coisa a fazer é simplesmente usar arquivos diferentes.

A partir do Python 3.3, há um novo atributo __qualname__, que fornece não só o nome da classe, mas também os nomes das classes exteriores:

Em sua amostra isso resultaria em:

assert SomeClass.__qualname__ == 'SomeClass'
assert OuterClass.InnerClass.__qualname__ == 'OuterClass.InnerClass'

Se __qualname__ de uma classe não contém um '' é uma classe externa!

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top