防止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
这能强制执行吗?我知道编译器不会帮忙,那是不是可以通过一些运行时检查来实现?还是说这样做不符合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(应用程序接口)写好文档,让用户知道怎么使用它。毕竟这是他们的程序,如果他们还是想要覆盖你的函数,你又有什么理由去阻止他们呢?