Применение переменных класса в подклассе
Я работаю над расширением 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:
Рисунок 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.