防止Python中的函数重写

时间:2010-10-16 12:25:00

标签: 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

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

3 个答案:

答案 0 :(得分:13)

你是对的:你所尝试的是与Python的结构及其文化背道而驰。

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

答案 1 :(得分:9)

如果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

你可以像这样使用它:

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

答案 2 :(得分:4)

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

#! /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()