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)
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.
抽象基类允许声明一个属性抽象,这将强制所有实现类都具有该属性。我仅出于完整性的目的提供此示例,许多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类实例:
图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 = Nonecheck_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直线。
这有效。甚至会阻止子类被定义,更不用说实例化了。
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)