防止Python中的函数重写

Kla*_*sen 13 python

有什么办法可以阻止子类覆盖基类中的方法吗?

我的猜测是没有,但我来自.NET世界,我正在努力使我的API尽可能健壮,所以任何输入都非常感激.

class Parent:
    def do_something(self):
        '''This is where some seriously important stuff goes on'''
        pass

class Child(Parent):
    def do_something(self):
        '''This should not be allowed.'''
        pass
Run Code Online (Sandbox Code Playgroud)

是否有可能强制执行此操作?我知道编译器不会有帮助,所以可能通过一些运行时检查?或者它不是一种传播事物的pythonic方式?

Ned*_*der 13

你是对的:你所尝试的是与Python的结构和文化相悖的.

记录您的API,并教育您的用户如何使用它.这是他们的程序,所以如果他们仍然想要覆盖你的功能,你是谁阻止他们?

  • 这是一个复杂系统的良好程序设计原则,除了语言惯例.示例:您有一个模块框架,它为所有模块公开了一个接口,并将其称为"run()".在超类中,run()执行所有模块共有的一些内部前/后处理步骤(例如,设置self.hasRun标志)并运行self.runBody().在子类中,要运行的实际代码体在runBody()方法中.为了强制执行安全的模块设计,我想防止覆盖run().以Pythonic的方式解决我的问题:) (6认同)
  • (在Java中,我只是在超类中声明run()为final,并将runBody声明为abstract.) (2认同)

uzu*_*aki 10

如果API允许您提供某个类的子类并调用您的(合法)重写方法,而且还使用简单名称(如"add")调用该类的其他API方法,则意外地覆盖这些方法可能会导致难以跟踪错误.最好至少警告用户.

用户想要/需要覆盖将完全破坏API的方法的情况几乎为零.用户意外地覆盖他不应该做的事情并且需要数小时才能找到罪魁祸首的情况更为频繁.调试由此引起的错误行为可能很麻烦.

这是我用来警告或保护属性不被意外覆盖的方式:

def protect(*protected):
    """Returns a metaclass that protects all attributes given as strings"""
    class Protect(type):
        has_base = False
        def __new__(meta, name, bases, attrs):
            if meta.has_base:
                for attribute in attrs:
                    if attribute in protected:
                        raise AttributeError('Overriding of attribute "%s" not allowed.'%attribute)
            meta.has_base = True
            klass = super().__new__(meta, name, bases, attrs)
            return klass
    return Protect
Run Code Online (Sandbox Code Playgroud)

你可以像这样使用它:

class Parent(metaclass=protect("do_something", "do_something_else")):
    def do_something(self):
        '''This is where some seriously important stuff goes on'''
        pass

class Child(Parent):
    def do_something(self):
        '''This will raise an error during class creation.'''
        pass
Run Code Online (Sandbox Code Playgroud)


Noc*_*wer 7

uzumaki已经提供了一个元类作为上述问题的可能解决方案,但是这里是另一个带有示例用法的解决方案。在尝试创建Child类之后,显示了另一种使方法难以覆盖的方法。在属性名称之前而不是之后加上两个下划线将自动导致名称修改。有关手动访问此功能的简便方法,请参见另一个问题的答案

#! /usr/bin/env python3
class Access(type):

    __SENTINEL = object()

    def __new__(mcs, name, bases, class_dict):
        private = {key
                   for base in bases
                   for key, value in vars(base).items()
                   if callable(value) and mcs.__is_final(value)}
        if any(key in private for key in class_dict):
            raise RuntimeError('certain methods may not be overridden')
        return super().__new__(mcs, name, bases, class_dict)

    @classmethod
    def __is_final(mcs, method):
        try:
            return method.__final is mcs.__SENTINEL
        except AttributeError:
            return False

    @classmethod
    def final(mcs, method):
        method.__final = mcs.__SENTINEL
        return method


class Parent(metaclass=Access):

    @Access.final
    def do_something(self):
        """This is where some seriously important stuff goes on."""
        pass


try:
    class Child(Parent):

        def do_something(self):
            """This should not be allowed."""
            pass
except RuntimeError:
    print('Child cannot be created.')


class AnotherParent:

    def __do_something(self):
        print('Some seriously important stuff is going on.')

    def do_parent_thing(self):
        self.__do_something()


class AnotherChild(AnotherParent):

    def __do_something(self):
        print('This is allowed.')

    def do_child_thing(self):
        self.__do_something()


example = AnotherChild()
example.do_parent_thing()
example.do_child_thing()
Run Code Online (Sandbox Code Playgroud)