我正致力于为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
答案 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类实例:
图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`.