在子类中实现类变量

时间:2014-02-26 15:47:27

标签: python subclass class-variables

我正致力于为App Engine扩展Python webapp2 Web框架,以引入一些缺少的功能(为了使创建应用程序更快更容易)。

这里的一个要求是每个子类需要来拥有一些特定的静态类变量。如果在我使用它们时缺少它们​​或者有更好的方法,那么实现这一目标的最佳方法是简单地抛出异常吗?

示例(非真实代码):

子类:

class Bar(Foo):
  page_name = 'New Page'
需要存在

page_name才能在此处理:

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

6 个答案:

答案 0 :(得分:7)

如果您尝试使用不存在的属性,Python将抛出异常。这是一个非常合理的方法,因为错误消息将清楚表明属性需要存在。通常的做法是尽可能在基类中为这些属性提供合理的默认值。如果需要属性或方法,抽象基类是很好的,但是它们不能用于数据属性,并且在实例化类之前它们不会引发错误。

如果您希望尽快失败,则元类可以阻止用户甚至在不包含属性的情况下定义类。关于元类的好处是它是可继承的,所以如果你在基类上定义它,它会自动用在派生它的任何类上。

这是一个元类;实际上,这是一个元类 factory ,可以让您轻松传入您想要的属性名称。

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

答案 1 :(得分:6)

Abstract Base Classes允许声明属性abstract,这将强制所有实现类具有该属性。我只提供完整性的这个例子,许多pythonistas认为你提出的解决方案更加pythonic。

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

答案 2 :(得分:4)

在描述我的解决方案之前,让我向您介绍如何创建Python类实例:

Instance creation in Python

图1:Python实例创建[1]

鉴于以上描述,您可以看到Python类实例实际上是由Metaclass创建的。我们可以看到,当调用者创建我们类的实例时,首先调用__call__魔术方法,然后调用该类的__new____init__,然后{ {1}}将对象实例返回给调用者。

尽管如此,我们可以简单地尝试检查__cal__创建的实例是否实际定义了那些&#34; required&#34;属性。

<强>元类

__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__方法,该方法将检查是否已定义所需的属性。如果没有定义所需的属性,我们应该简单地抛出一个错误。

<强>超类

Python 2

check_required_attributes()

Python 3

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.')

这里我们定义实际的超类。三件事:

  • 应该使用我们的元类。
  • 应将所需属性定义为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.') ,请参阅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.

答案 3 :(得分:1)

一般来说,在Python中,人们普遍认为,处理这种情况的最佳方法,正如你的正确建议,是用try-except块包装这个类变量的任何操作。

答案 4 :(得分:0)

这有效。甚至将无法定义子类,更不用说实例化了。

class Foo:

    page_name = None
    author = None

    def __init_subclass__(cls, **kwargs):
        for required in ('page_name', 'author',):
            if not getattr(cls, required):
                raise TypeError(f"Can't instantiate abstract class {cls.__name__} without {required} attribute defined")
        return super().__init_subclass__(**kwargs)


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

答案 5 :(得分:0)

我爱this answer。 一次过的最佳方法。与元类相比,其他读者所受的恐惧要小得多。

但是,如果您希望将元类作为通用工具插入很多地方,则元类非常有用。我借鉴了其他一些答案,但还添加了bases检查,以便您可以在mixin中使用它,并且mixin本身不会触发它。可以添加类似的支票以跳过ABC。

def RequiredAttributes(*required_attrs):
    class RequiredAttributesMeta(type):
        def __init__(cls, name, bases, attrs):
            if not bases:
                return  # No bases implies mixin. Mixins aren't the final class, so they're exempt.
            if missing_attrs := [attr for attr in required_attrs if not hasattr(cls, attr)]:
                raise AttributeError(f"{name!r} requires attributes: {missing_attrs}")
    return RequiredAttributesMeta

然后像这样使用:

class LicenseAccessMixin(metaclass=RequiredAttributes('access_control')):
    ...  # define methods that safely refer to `self.access_control`.