不可实例化的超类

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 操作。也许我只是做错了。 ;)

以上是关于不可实例化的超类的主要内容,如果未能解决你的问题,请参考以下文章

Java:Effective java学习笔记之 通过私有构造器强化不可实例化的能力

effective java学习笔记之不可实例化的类

第4条:通过私有构造器强化不可实例化的能力

第4条:通过私有构造器强化不可实例化的能力

第4条:通过私有构造器强化不可实例化的能力

防止类在 Python 中直接实例化