python: determinar se uma classe é aninhada
-
11-07-2019 - |
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.
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!