其他答案中的 mixin 方法很好,并且在大多数情况下可能更好。但是,尽管如此,它还是破坏了一部分乐趣——也许你不得不拥有单独的行星层次结构——比如不得不与两个抽象类一起生活,每个类都是“可破坏”和“不可破坏”的祖先。
第一种方法:描述符装饰器
但是 Python 有一个强大的机制,称为“描述符协议”,它用于从类或实例中检索任何属性——它甚至通常用于从实例中检索方法——因此,可以自定义方法以一种检查它是否“应该属于”该类的方式进行检索,否则会引发属性错误。
描述符协议规定,每当您尝试从 Python 中的实例对象获取任何属性时,Python 将检查该属性是否存在于该对象的类中,如果存在,则该属性本身是否具有名为 __get__ 的方法。如果有,则调用__get__(将其定义为参数的实例和类)-它返回的都是属性。 Python 使用它来实现方法:Python 3 中的函数有一个 __get__ 方法,当调用该方法时,将返回另一个可调用对象,反过来,在调用时将在对原始函数的调用中插入 self 参数。
因此,可以创建一个类,其__get__ 方法将根据标记为的外部类决定是否将函数作为绑定方法返回 - 例如,它可以检查特定标志 @ 987654326@。这可以通过使用装饰器来包装具有此描述符功能的方法来完成
class Muteable:
def __init__(self, flag_attr):
self.flag_attr = flag_attr
def __call__(self, func):
"""Called when the decorator is applied"""
self.func = func
return self
def __get__(self, instance, owner):
if instance and getattr(instance, self.flag_attr, False):
raise AttributeError('Objects of type {0} have no {1} method'.format(instance.__class__.__name__, self.func.__name__))
return self.func.__get__(instance, owner)
class Planet:
def __init__(self, name=""):
pass
@Muteable("undestroyable")
def destroy(self):
print("Destroyed")
class BorgWorld(Planet):
undestroyable = True
在交互式提示下:
In [110]: Planet().destroy()
Destroyed
In [111]: BorgWorld().destroy()
...
AttributeError: Objects of type BorgWorld have no destroy method
In [112]: BorgWorld().destroy
AttributeError: Objects of type BorgWorld have no destroy method
认识到与简单地覆盖方法不同,这种方法在检索属性时会引发错误 - 甚至会使hasattr 工作:
In [113]: hasattr(BorgWorld(), "destroy")
Out[113]: False
虽然,如果尝试直接从类中而不是从实例中检索方法,它将不起作用 - 在这种情况下,__get__ 的 instance 参数设置为 None,我们不能说它是从哪个类中检索到的 - 只是声明它的 owner 类。
In [114]: BorgWorld.destroy
Out[114]: <function __main__.Planet.destroy>
第二种方法:元类上的__delattr__:
在编写上述内容时,我突然想到 Pythn 确实有 __delattr__ 特殊方法。如果 Planet 类本身实现了 __delattr__ 并且我们尝试删除特定派生类上的 destroy 方法,它不会起作用:__delattr__ gards 删除实例中的属性 - 如果你愿意尝试del实例中的“destroy”方法,无论如何它都会失败,因为该方法在类中。
然而,在 Python 中,类本身就是一个实例——它的“元类”。这通常是 type 。在“Planet”的元类上适当的__delattr__ 可以通过在类创建后发出“del UndestructiblePlanet.destroy”来实现“destroy”方法的“disinheitance”。
再次,我们使用描述符协议在子类上有一个适当的“删除方法”:
class Deleted:
def __init__(self, cls, name):
self.cls = cls.__name__
self.name = name
def __get__(self, instance, owner):
raise AttributeError("Objects of type '{0}' have no '{1}' method".format(self.cls, self.name))
class Deletable(type):
def __delattr__(cls, attr):
print("deleting from", cls)
setattr(cls, attr, Deleted(cls, attr))
class Planet(metaclass=Deletable):
def __init__(self, name=""):
pass
def destroy(self):
print("Destroyed")
class BorgWorld(Planet):
pass
del BorgWorld.destroy
使用此方法,即使尝试检索或检查类本身的方法存在也将起作用:
In [129]: BorgWorld.destroy
...
AttributeError: Objects of type 'BorgWorld' have no 'destroy' method
In [130]: hasattr(BorgWorld, "destroy")
Out[130]: False
具有自定义 __prepare__ 方法的元类。
由于元类允许自定义包含类命名空间的对象,因此可以在类主体中拥有一个响应del 语句的对象,添加一个Deleted 描述符。
对于使用这个元类的用户(程序员)来说,几乎是一样的,但del 语句被允许进入类体本身:
class Deleted:
def __init__(self, name):
self.name = name
def __get__(self, instance, owner):
raise AttributeError("No '{0}' method on class '{1}'".format(self.name, owner.__name__))
class Deletable(type):
def __prepare__(mcls,arg):
class D(dict):
def __delitem__(self, attr):
self[attr] = Deleted(attr)
return D()
class Planet(metaclass=Deletable):
def destroy(self):
print("destroyed")
class BorgPlanet(Planet):
del destroy
(“已删除”描述符是将方法标记为“已删除”的正确形式 - 但在此方法中,它在创建类时无法知道类名)
作为类装饰器:
鉴于“已删除”描述符,可以简单地通知要作为类装饰器删除的方法 - 在这种情况下不需要元类:
class Deleted:
def __init__(self, cls, name):
self.cls = cls.__name__
self.name = name
def __get__(self, instance, owner):
raise AttributeError("Objects of type '{0}' have no '{1}' method".format(self.cls, self.name))
def mute(*methods):
def decorator(cls):
for method in methods:
setattr(cls, method, Deleted(cls, method))
return cls
return decorator
class Planet:
def destroy(self):
print("destroyed")
@mute('destroy')
class BorgPlanet(Planet):
pass
修改__getattribute__机制:
为了完整起见——真正让 Python 到达超类上的方法和属性的是在 __getattribute__ 调用中发生的事情。在object 版本的__getattribute__ 中,对属性检索的“数据描述符、实例、类、基类链......”具有优先级的算法进行编码。
因此,为类更改它是一个容易获得“合法”属性错误的独特点,而无需在以前的方法中使用“不存在”描述符。
问题是object 的__getattribute__ 没有使用type 来搜索类中的属性——如果这样做了,只需在元类上实现__getattribute__ 就足够了.必须在实例上执行此操作以避免方法的实例查找,并在元类上执行此操作以避免元类查找。当然,元类可以注入所需的代码:
def blocker_getattribute(target, attr, attr_base):
try:
muted = attr_base.__getattribute__(target, '__muted__')
except AttributeError:
muted = []
if attr in muted:
raise AttributeError("object {} has no attribute '{}'".format(target, attr))
return attr_base.__getattribute__(target, attr)
def instance_getattribute(self, attr):
return blocker_getattribute(self, attr, object)
class M(type):
def __init__(cls, name, bases, namespace):
cls.__getattribute__ = instance_getattribute
def __getattribute__(cls, attr):
return blocker_getattribute(cls, attr, type)
class Planet(metaclass=M):
def destroy(self):
print("destroyed")
class BorgPlanet(Planet):
__muted__=['destroy'] # or use a decorator to set this! :-)
pass