Применение переменных класса в подклассе

Я работаю над расширением Python webapp2 web framework для App Engine, чтобы внести некоторые недостающие функции (чтобы сделать создание приложений немного быстрее и проще).

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

пример (не реальный код):

подкласс:

class Bar(Foo):
  page_name = 'New Page'

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

page_names = process_pages(list_of_pages)

def process_pages(list_of_pages)
  page_names = []

  for page in list_of_pages:
    page_names.append(page.page_name)

  return page_names

4 ответов


Абстрактные Базовые Классы разрешить объявление абстрактного свойства, которое заставит все реализующие классы иметь свойство. Я только предоставляю этот пример для полноты, многие питонисты думают, что ваше предлагаемое решение более питоническое.

import abc

class Base(object):
    __metaclass__ = abc.ABCMeta

    @abc.abstractproperty
    def value(self):
        return 'Should never get here'


class Implementation1(Base):

    @property
    def value(self):
        return 'concrete property'


class Implementation2(Base):
    pass # doesn't have the required property

попытка создать экземпляр первого реализующего класса:

print Implementation1()
Out[6]: <__main__.Implementation1 at 0x105c41d90>

попытка создать экземпляр второго реализующего класса:

print Implementation2()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-bbaeae6b17a6> in <module>()
----> 1 Implementation2()

TypeError: Can't instantiate abstract class Implementation2 with abstract methods value

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

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

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

def RequiredAttributes(*required_attrs):

    class RequiredAttributesMeta(type):
        def __init__(cls, name, bases, attrs):
            missing_attrs = ["'%s'" % attr for attr in required_attrs 
                             if not hasattr(cls, attr)]
            if missing_attrs:
                raise AttributeError("class '%s' requires attribute%s %s" %
                                     (name, "s" * (len(missing_attrs) > 1), 
                                      ", ".join(missing_attrs)))
    return RequiredAttributesMeta

теперь на самом деле определить базовый класс с помощью этого метакласса немного сложно. Вы должны определить атрибуты, чтобы определить класс, который является всей точкой метакласса, но если атрибуты определены на базовом классе, они также определены на любом производном от него классе, побеждая цель. Поэтому мы определим их (используя фиктивное значение), а затем удалим их из класса.

class Base(object):
    __metaclass__ = RequiredAttributes("a", "b" ,"c")
    a = b = c = 0

del Base.a, Base.b, Base.c

теперь, если вы попытаетесь определить подкласс, но не определяйте атрибуты:

class Child(Base):
    pass

вы получаете:

AttributeError: class 'Child' requires attributes 'a', 'b', 'c'

N. B. У меня нет опыта работы с Google App Engine, поэтому возможно, что он уже использует метакласс. В этом случае вы хотите свой RequiredAttributesMeta извлечь из этого метакласса, а не type.


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

Instance creation in Python

Рисунок 1: создание экземпляра Python [1]

учитывая приведенное выше описание, вы можете видеть, что в Python экземпляры классов фактически создаются Метаклассом. Как мы видим, когда вызывающий объект создает экземпляр нашего класса, сначала __call__ вызывается метод magic, который в свою очередь вызывает __new__ и __init__ класса, а потом __cal__ возвращает экземпляр объекта обратно.

со всем сказанным, мы можем просто попытаться сделать нашу проверку, если экземпляр, созданный __init__ фактически определяет эти" обязательные " атрибуты.

метакласс

class ForceRequiredAttributeDefinitionMeta(type):
    def __call__(cls, *args, **kwargs):
        class_object = type.__call__(cls, *args, **kwargs)
        class_object.check_required_attributes()
        return class_object

как вы можете видеть в __call__ то, что мы делаем, это создать объект класса, а затем вызвать его check_required_attributes() метод, который проверит, были ли необходимые атрибуты определенный. Там, если необходимые атрибуты не определены, мы должны просто выбросить ошибку.

суперкласса

Python 2

class ForceRequiredAttributeDefinition(object):
    __metaclass__ = ForceRequiredAttributeDefinitionMeta
    starting_day_of_week = None

    def check_required_attributes(self):
        if self.starting_day_of_week is None:
            raise NotImplementedError('Subclass must define self.starting_day_of_week attribute. \n This attribute should define the first day of the week.')

Python 3

class ForceRequiredAttributeDefinition(metaclass=ForceRequiredAttributeDefinitionMeta):
    starting_day_of_week = None

    def check_required_attributes(self):
        if self.starting_day_of_week is None:
            raise NotImplementedError('Subclass must define self.starting_day_of_week attribute. \n This attribute should define the first day of the week.')

здесь мы определяем фактический суперкласса. Три вещи:--19-->

  • следует использовать наш метакласс.
  • следует определить необходимые атрибуты как None посмотреть starting_day_of_week = None
  • должны использовать тег check_required_attributes метод, который проверяет, являются ли обязательные атрибуты None и если они хотят бросить NotImplementedError с разумным сообщением об ошибке для пользователя.

пример рабочего и нерабочего подкласс

class ConcereteValidExample(ForceRequiredAttributeDefinition):
    def __init__(self):
        self.starting_day_of_week = "Monday"


class ConcereteInvalidExample(ForceRequiredAttributeDefinition):
    def __init__(self):
        # This will throw an error because self.starting_day_of_week is not defined.
        pass

выход

Traceback (most recent call last):
  File "test.py", line 50, in <module>
    ConcereteInvalidExample()  # This will throw an NotImplementedError straightaway
  File "test.py", line 18, in __call__
    obj.check_required_attributes()
  File "test.py", line 36, in check_required_attributes
    raise NotImplementedError('Subclass must define self.starting_day_of_week attribute. \n This attribute should define the first day of the week.')
NotImplementedError: Subclass must define self.starting_day_of_week attribute.
 This attribute should define the first day of the week.

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


вообще говоря, в Python широко принято, что лучший способ справиться с такого рода сценарием, как вы правильно предположили,-это обернуть любую операцию, требующую этой переменной класса, с помощью блока try-except.