Мне интересно, как лучше всего получить доступ к родительской переменной из вложенного подкласса, в настоящее время я использую декоратор.

Это единственный / лучший способ ???

Я не хочу иметь прямой доступ к родительской переменной (например, ComponentModel.origin (см. Ниже)), поскольку для этого потребуется больше кода в файле конфигурации, поэтому мне интересно, могу ли я назначить родительскую переменную в класс, от которого наследуется рассматриваемый подкласс?

Тривиальный пример моего текущего решения:

# defined in a big library somewhere:
class LibrarySerialiser(object):
    pass

# defined in my module:
class ModelBase:
    pass

class SerialiserBase(LibrarySerialiser):
    def __init__(self, *args, **kwargs):
        # could i some how get hold of origin here without the decorator?
        print self.origin
        super(SerialiserBase, self).__init__(*args, **kwargs)

def setsubclasses(cls):
    cls.Serialiser.origin = cls.origin
    return cls

# written by "the user" for the particular application as the
# configuration of the module above:

@setsubclasses
class ComponentModel(ModelBase):
    origin = 'supermarket'

    class Serialiser(SerialiserBase):
        pass


ser = ComponentModel.Serialiser()

Это явно тривиальный пример, в котором отсутствует вся реальная логика, поэтому многие классы кажутся недействительными, но действительно необходимы.

4
SColvin 14 Сен 2013 в 15:18

1 ответ

Лучший ответ

К вашему сведению, принятая терминология, используемая при вложении классов, как вы это сделали, - это внутренний / внешний, а не родительский / дочерний или суперкласс / подкласс. Отношения родитель / потомок или супер / подчиненный относятся к наследованию. Это сбивает с толку имя вашего декоратора, setsubclasses, поскольку в нем нет подклассов!

Необычная вещь, которую вы здесь делаете, - это использование класса в качестве пространства имен без его создания. Обычно вы инстанцируете свой ComponentModel, и в это время просто предоставить вашему внутреннему классу Serialiser копию атрибута из его внешнего класса. Например.:

class ModelBase(object):
    def __init__(self):
        self.Serialiser.origin = self.origin

# ... then

cm  = ComponentModel()
ser = cm.Serialiser()

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

class ModelBase(object):
    def __init__(self, *args, **kwargs):
        serialiser = self.Serialiser(self, *args, **kwargs)

class SerialiserBase(LibrarySerialiser):
    def __init__(self, outer, *args, **kwargs):
        self.outer = outer
        print self.outer.origin
        super(SerialiserBase, self).__init__(*args, **kwargs)

# ...

cm  = ComponentModel()
ser = cm.serialiser

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

class PropagateOuter(type):
    def __init__(cls, name, bases, dct):
        type.__init__(cls, name, bases, dct)
        if "Serialiser" in dct:
            cls.Serialiser.outer = cls

class ModelBase(object):
    __metaclass__ = PropagateOuter

# Python 3 version of the above
# class ModelBase(metaclass=PropagateOuter):
#     pass

class SerialiserBase(LibrarySerialiser):
    def __init__(self, *args, **kwargs):
        print self.outer.origin
        super(SerialiserBase, self).__init__(*args, **kwargs)

class ComponentModel(ModelBase):
    origin = 'supermarket'

    class Serialiser(SerialiserBase):
        pass

ser = ComponentModel.Serialiser()

Это не делает того, что не делает ваш декоратор, но пользователь получает его автоматически через наследование, вместо того, чтобы указывать его вручную. Дзен Python говорит, что «явное лучше, чем неявное», так что помидор, помидор.

Вы даже можете написать метакласс, чтобы он анализировал внешний класс и помещал ссылку на этот класс во каждый внутренний класс независимо от их имени.

Между прочим, одна из ловушек того, как вы это делаете, заключается в том, что все ваши классы моделей должны быть подклассами SerialiserBase. Если пользователю вашего класса просто нужен сериализатор по умолчанию, он не может просто написать Serialiser = SerialiserBase в своем определении класса, он должен написать class Serialiser(SerialiserBase): pass. Это потому, что существует только один SerialiserBase, и он, очевидно, не может содержать ссылку на несколько внешних классов. Конечно, вы можете написать свой метакласс, чтобы справиться с этим (например, путем автоматического создания подкласса указанного сериализатора, если он уже имеет атрибут outer).

3
kindall 14 Сен 2013 в 19:40
Большое спасибо за ответ, очень полезно. Я думаю, что ваше первое решение соответствует всем требованиям, оно ясное (как я не подумал об этом, не могу представить), и оно просто в файле конфигурации.
 – 
SColvin
14 Сен 2013 в 19:59