防止Python中的函数重写

26 投票
3 回答
19740 浏览
提问于 2025-04-16 05:35

有没有办法阻止子类重写父类的方法呢?

我猜可能没有这种办法,但我之前是在.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

这能强制执行吗?我知道编译器不会帮忙,那是不是可以通过一些运行时检查来实现?还是说这样做不符合Python的风格呢?

3 个回答

10

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()
21

如果一个API允许你提供某个类的子类,并且调用你合法重写的方法,但也会调用这个类中一些简单命名的方法,比如“add”,那么不小心重写这些方法可能会导致难以追踪的错误。最好至少给用户一个警告。

用户想要或需要重写一个会完全破坏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

你可以这样使用:

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
20

你说得对:你想做的事情和Python的结构以及它的文化是相悖的。

要把你的API(应用程序接口)写好文档,让用户知道怎么使用它。毕竟这是他们的程序,如果他们还是想要覆盖你的函数,你又有什么理由去阻止他们呢?

撰写回答