python: определить, является ли класс вложенным - PullRequest
6 голосов
/ 12 марта 2009

Предположим, у вас есть метод python, который получает тип в качестве параметра; Можно ли определить, является ли данный тип вложенным классом?
Например. в этом примере:

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)

Я бы хотел, чтобы вызов show_type_info(OuterClass.InnerClass) также показал, что InnerClass определен внутри OuterClass.

Ответы [ 6 ]

13 голосов
/ 12 марта 2009

AFAIK, учитывая класс и никакой другой информации, вы не можете сказать, является ли это вложенным классом. Однако см. Здесь , чтобы узнать, как использовать декоратор для определения этого.

Проблема в том, что вложенный класс - это просто обычный класс, который является атрибутом своего внешнего класса. Другие решения, которые вы можете ожидать, вероятно, не сработают - например, inspect.getmro дает только базовые классы, а не внешние классы.

Кроме того, вложенные классы нужны редко. Я бы решительно пересмотрел, является ли это хорошим подходом в каждом конкретном случае, когда вы испытываете желание использовать его.

5 голосов
/ 12 марта 2009

Внутренний класс не предлагает особых особенностей в Python. Это только свойство объекта класса, ничем не отличающееся от целочисленного или строкового свойства. Ваш пример OuterClass / InnerClass может быть переписан точно так:

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

InnerClass не может знать, было ли оно объявлено внутри другого класса, потому что это просто простая привязка переменной. Магия, благодаря которой связанные методы узнают об их собственном «я», здесь не применима.

Магия внутреннего декоратора в ссылке, которую Джон опубликовал, - интересный подход, но я бы не стал использовать его как есть. Он не кэширует классы, которые он создает для каждого внешнего объекта, поэтому вы получаете новый InnerClass каждый раз, когда вызываете externalinstance.InnerClass:

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

Также способ, которым он пытается воспроизвести поведение Java, делая переменные внешнего класса доступными во внутреннем классе с помощью getattr / setattr, очень хитрый и действительно ненужный (поскольку более Pythonic способ будет вызывать i .__ external __. Attr явно ).

4 голосов
/ 12 марта 2009

Действительно вложенный класс ничем не отличается от любого другого класса - он просто определен где-то еще, чем пространство имен верхнего уровня (вместо этого внутри другого класса). Если мы изменим описание с «вложенного» на «не верхнего уровня», то вы сможете приблизиться к тому, что вам нужно.

например:

import inspect

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

Это будет работать в обычных случаях, но может не работать так, как вы хотите, в ситуациях, когда классы переименовываются или иным образом манипулируются после определения. Например:

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
1 голос
/ 12 марта 2009

Спасибо всем за ваши ответы.
Я нашел это возможное решение с помощью метаклассов; Я сделал это больше для упрямства, чем для реальной необходимости, и это сделано так, что это не будет применимо к Python 3.
Я хочу поделиться этим решением в любом случае, поэтому я публикую его здесь.

#!/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()
0 голосов
/ 22 апреля 2018

Начиная с Python 3.3, появился новый атрибут __qualname__, который предоставляет не только имя класса, но и имена внешних классов:

В вашем примере это приведет к:

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

Если __qualname__ класса не содержит «.» это внешний класс!

0 голосов
/ 12 марта 2009

Если вы не установите его самостоятельно, я не верю, что существует какой-либо способ определить, является ли класс вложенным. Так как в любом случае класс Python нельзя использовать в качестве пространства имен (или, по крайней мере, нелегко), я бы сказал, что лучше всего просто использовать разные файлы.

...