不可实例化的超类
Posted
技术标签:
【中文标题】不可实例化的超类【英文标题】:Uninstantiable superclass 【发布时间】:2011-09-09 12:58:03 【问题描述】:所以,我正在编写一个用于连接外部帐户提供程序(Twitter、Facebook 等)的模块,并且我有一个自身无用的超类,但包含需要由子类调用以保持身份验证的通用方法令牌,获取身份验证令牌并取消对提供者的授权。我的问题是,有没有办法让它无法实例化,或者我应该遵循成年人同意的规则,让任何使用它的人犯他们认为合适的错误?除了文档字符串之外,还有什么好方法可以表示某人不应该单独使用这个超类?
【问题讨论】:
真的不能只在python中声明类抽象吗? @bjarkef:你看过 AFoglia 的回答了吗? 【参考方案1】:我附议Sven Marnach's edit:我认为您应该遵循“同意的成年人”规则,并在文档字符串中提到该类不应该被实例化。
您问题中的关键短语是“我有一个超类它本身是无用的。”实例化时不会调用 cthulhu;它不会在程序的其他地方导致某种灾难性的、难以调试的故障;这只是浪费时间。我认为,这不值得削弱班级。
【讨论】:
如果我必须选择一个“接受的答案”,我会选择这个! 我喜欢所有其他答案,但它们似乎太不合情理了。我认为@senderle 是对的。我会将信息放在文档字符串中,希望人们花时间阅读它。【参考方案2】:class NoInstantiation: # "class NoInstantiation(object):" in Python 2.2+ or whatever
def __new__(cls):
"This class is not meant to be instantiated, so __new__ returns None."
return None
这不会阻止人们根据需要覆盖该功能,但它应该是防止意外实例化的相当不错的方法。如果有人不小心调用了它,那么他们不能说你没有警告他们。
编辑:如果您真的想表现得很好,您还可以在__new__()
中向 stderr 打印警告,甚至抛出异常。
编辑 2:如果您走异常路线,您可能希望在超类的 __init__()
方法中引发异常,因为用户可能会在其子类中覆盖 __init__()
。
编辑 3:也可以选择将 __new__
或 __init__
设置为等于 None
,尽管由此产生的错误信息不会很丰富。
【讨论】:
1.这在 Python 2.x 中不起作用。__new__()
只为新式类调用,所以你必须从 object
派生。 2. 你应该提出一个错误而不是返回None
。 3.这要求所有派生类都覆盖__new__()
,好像不是很方便。
@Sven:啊,对了,写了一个关于Python 2的注释。至于要求所有派生类覆盖__new__()
...只需要调用object.__new__()
。不过,我会记下另一种方法。
@JAB:如果NoInstantiation
有基类,您可能需要调用其中一个基类的__new__()
方法,这在派生类中可能很难弄清楚。跨度>
我建议走引发错误的路线。根据我的经验,打印的警告信息无效。最好在 __init__
上进行,如 EDIT #2 中所述。
@JAB:我发布了一个如何改进的建议。我首先尝试将其放入评论中,但它不起作用:)【参考方案3】:
基于 JAB 的回答,这样写 __new__()
可能更方便:
class NoInstantiation(object):
def __new__(cls, *args, **kwargs):
if cls is NoInstantiation:
raise RuntimeError(
"NoInstantiation isn't meant to be instantiated")
else:
return super(NoInstantiation, cls).__new__(cls, *args, **kwargs)
这样,您不需要在派生类中覆盖__new__()
。
编辑:我发布了这个答案作为对 JAB 答案的改进,但我建议不要实际使用上述代码。它以某种方式故意削弱你的班级。通常的 Python 方法是清楚地记录类并不意味着被实例化。但也许有人会找到一种方法,它是如何有用的——你永远不知道人们会以什么方式使用你的图书馆。
【讨论】:
我同意,我认为这不是真正的“Pythonic”方法。 Python,至少就它最初的构想而言,非常相信“我们都是同意的成年人”的心态。阅读 Von Rossum 关于强类型与弱类型的原始陈述——他拒绝类型化参数的理由是“如果有人想滥用你的代码,那就是他们的问题”。我的建议:在那里放一个打印声明,“这个类本身通常是无用的。使用风险自负。” (除了文档字符串) @cwallenpoole:Python 是强类型的。您的意思可能是“静态与动态类型”。【参考方案4】:您可以使用abc
模块中的abstractmethod
decorator 将所有派生类覆盖的方法之一标记为抽象。
In [1]: import abc
In [2]: class C:
...: __metaclass__ = abc.ABCMeta
...:
...: @abc.abstractmethod
...: def my_abstract_method(self, *args) :
...: pass
...:
...:
In [3]: c = C()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
/home/afoglia/<ipython console> in <module>()
TypeError: Can't instantiate abstract class C with abstract methods my_abstract_method
不过,我确实质疑班级组织。如果这个“超类”只不过是子类需要调用的函数,那为什么它不只是不同机制可以使用的模块呢?如果它是派生类完全实现的接口的定义(可能作为模板方法模式),那么抽象方法表示需要由派生类实现的函数。在那种情况下,我什至不会费心使基础不可构造,要么让人们通过工厂方法获得必要的派生,要么让用户在调用失败的函数时弄清楚。 (不过,请在文档字符串中添加注释。)
【讨论】:
你可能是对的,我可能想重新考虑类结构,但我不确定。超类既有需要重写的方法,也有每个子类应该调用以实现相同身份验证的方法。我想我可以翻转它,以便 TwitterClient 和 FacebookClient 类是接口的子类,但超类的有状态位/方法可能是一个完全独立的类,每个类都作为其自己的实例,用于用户的身份验证令牌上的 CRUD 操作。也许我只是做错了。 ;)以上是关于不可实例化的超类的主要内容,如果未能解决你的问题,请参考以下文章