在子类中实现类变量

A_P*_*ine 10 python subclass class-variables

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

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

示例(不是真实代码):

子类:

class Bar(Foo):
  page_name = 'New Page'
Run Code Online (Sandbox Code Playgroud)

需要存在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
Run Code Online (Sandbox Code Playgroud)

kin*_*all 8

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
Run Code Online (Sandbox Code Playgroud)

现在使用这个元类实际定义一个基类有点棘手.你必须定义属性来定义类,它是元类的整个点,但是如果属性是在基类上定义的,它们也是在从它派生的任何类上定义的,从而破坏了目的.那么我们要做的就是定义它们(使用虚拟值)然后从类中删除它们.

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

del Base.a, Base.b, Base.c
Run Code Online (Sandbox Code Playgroud)

现在,如果您尝试定义子类,但不定义属性:

class Child(Base):
    pass
Run Code Online (Sandbox Code Playgroud)

你得到:

AttributeError: class 'Child' requires attributes 'a', 'b', 'c'
Run Code Online (Sandbox Code Playgroud)

NB我没有使用Google App Engine的经验,所以它可能已经使用了元类.在这种情况下,您希望您RequiredAttributesMeta从该元类派生,而不是type.


mba*_*rov 6

抽象基类允许声明一个属性抽象,这将强制所有实现类都具有该属性。我仅出于完整性的目的提供此示例,许多pythonista人士认为您提出的解决方案更具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
Run Code Online (Sandbox Code Playgroud)

尝试实例化第一个实现类:

print Implementation1()
Out[6]: <__main__.Implementation1 at 0x105c41d90>
Run Code Online (Sandbox Code Playgroud)

试图实例化第二个实现类:

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
Run Code Online (Sandbox Code Playgroud)

  • 自 Python 3.3 起,“abc.abstractproperty”已被弃用。将“property”与“abc.abstractmethod”一起使用,这样现在就可以使用“@property @abstractmethod”装饰器了。 (3认同)

Raf*_*ael 6

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

用Python创建实例

图1:Python实例创建[1]

鉴于以上描述,您可以看到在Python类实例中实际上是由元类创建的。正如我们所看到的,当主叫用户创造我们的类的实例,首先__call__魔术方法被调用这又是调用__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
Run Code Online (Sandbox Code Playgroud)

如您所见,__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.')
Run Code Online (Sandbox Code Playgroud)

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.')
Run Code Online (Sandbox Code Playgroud)

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

  • 应该利用我们的元类。
  • 应该定义必需的属性,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
Run Code Online (Sandbox Code Playgroud)

输出量

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.
Run Code Online (Sandbox Code Playgroud)

如您所见,自定义所需属性以来,第一个实例成功创建,第二个实例提出了一条NotImplementedError直线。


eri*_*ich 6

这有效。甚至会阻止子类被定义,更不用说实例化了。

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'
Run Code Online (Sandbox Code Playgroud)