其他答案中的 mixin 方法很好,并且对于大多数情况可能更好。但尽管如此,它破坏了部分乐趣 - 也许迫使你拥有单独的行星层次结构 - 就像必须忍受两个抽象类,每个类都是“可破坏”和“不可破坏”的祖先。
第一种方法:描述符装饰器
但是Python有一个强大的机制,称为“描述符协议”,它用于从类或实例中检索任何属性 - 它甚至通常用于从实例中检索方法 - 因此,可以在它检查它是否“应该属于”该类,否则引发属性错误。
描述符协议要求每当您尝试从 Python 中的实例对象获取任何属性时,Python 将检查该属性是否存在于该对象的类中,如果存在,则该属性本身是否有一个名为__get__
。如果有的话,__get__
被调用(使用它被定义为参数的实例和类) - 无论它返回什么都是属性。 Python 使用它来实现方法:Python 3 中的函数有一个__get__
方法,当调用时,将返回另一个可调用对象,反过来,当调用时将插入self
调用原始函数时的参数。
因此,可以创建一个类,其__get__
方法将决定是否将函数作为绑定方法返回,具体取决于外部类被标记为如此 - 例如,它可以检查特定标志non_destrutible
。这可以通过使用装饰器用此描述符功能包装方法来完成
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
work:
In [113]: hasattr(BorgWorld(), "destroy")
Out[113]: False
尽管如此,如果尝试直接从类而不是从实例检索方法,它将不起作用 - 在这种情况下instance
参数为__get__
设置为 None,我们不能说它是从哪个类检索的 - 只是owner
类,它被声明的地方。
In [114]: BorgWorld.destroy
Out[114]: <function __main__.Planet.destroy>
第二种方法:__delattr__
在元类上:
在写上面的时候,我突然想到Python确实有__delattr__
特殊方法。如果Planet
类本身实现__delattr__
我们会尝试删除destroy
特定派生类上的方法,它不会工作:__delattr__
gards 实例中属性的属性删除 - 如果您尝试del
实例中的“destroy”方法无论如何都会失败,因为该方法位于类中。
然而,在Python中,类本身就是它的“元类”的一个实例。那通常是type
。正确的__delattr__
在“Planet”的元类上可以通过在类创建后发出“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__
method.
由于元类允许自定义包含类名称空间的对象,因此可以有一个对象来响应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
's __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