我有一个星球大师班:
class Planet:
def __init__(self,name):
self.name = name
(...)
def destroy(self):
(...)
我还有一些继承自Planet
的类,我想让其中一个不能被销毁(不继承destroy
函数)
示例:
class Undestroyable(Planet):
def __init__(self,name):
super().__init__(name)
(...)
#Now it shouldn't have the destroy(self) function
所以当这个运行时,
Undestroyable('This Planet').destroy()
它应该产生如下错误:
AttributeError: Undestroyable has no attribute 'destroy'
答案 0 :(得分:16)
其他答案中的mixin方法很好,对大多数情况可能更好。但是,它会破坏部分乐趣 - 也许你必须拥有独立的星球层次结构 - 就像生活在两个抽象类中,每个祖先都是“可破坏的”和“不可破坏的”。
但Python有一个强大的机制,称为“描述符协议”,用于从类或实例中检索任何属性 - 它甚至用于从实例中检索方法 - 因此,可以自定义方法以某种方式检索它是否“应该属于”该类,并以其他方式引发属性错误。
描述符协议要求每当您尝试从Python中的实例对象获取任何属性时,Python将检查该对象的类中是否存在该属性,如果是,则该属性本身是否具有名为{{1}的方法}。如果有,则调用__get__
(使用实例和类将其定义为参数) - 并且它返回的是属性。 Python使用它来实现方法:Python 3中的函数有一个__get__
方法,当被调用时,将返回另一个可调用对象,反过来,当被调用时,会在调用原始对象时插入__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
虽然,如果试图直接从类而不是实例中检索方法,它将无法工作 - 在这种情况下,In [113]: hasattr(BorgWorld(), "destroy")
Out[113]: False
参数instance
设置为无,并且我们不能说它是从哪个类中检索出来的 - 只是声明它的__get__
类。
owner
In [114]: BorgWorld.destroy
Out[114]: <function __main__.Planet.destroy>
:在编写上述内容时,我发现Pythn确实拥有__delattr__
特殊方法。如果__delattr__
类本身实现Planet
并且我们尝试删除特定派生类上的__delattr__
方法,那么它就不会起作用:destroy
gards属性删除属性在实例中 - 如果你在实例中尝试__delattr__
“destroy”方法,它无论如何都会失败,因为该方法在类中。
然而,在Python中,类本身是一个实例 - 它的“元类”。通常是del
。 “Planet”元类的正确type
可以通过在创建类之后发出“del UndestructiblePlanet.destroy”来实现“destroy”方法的“消失”。
同样,我们使用描述符协议在子类上有一个正确的“删除方法”:
__delattr__
使用这种方法,即使尝试检索或检查类本身上的方法存在也会有效:
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
机制:为了完整起见 - 真正让Python在超类上获取方法和属性的是__getattribute__
调用中发生的事情。 __getattribute__
object
版本的__getattribute__
是用于对属性检索的“数据描述符,实例,类,基类链......”的优先级进行编码的算法。
因此,更改该类是一个容易的唯一点,以获得“合法”属性错误,而不需要在以前的方法中使用“不存在”的descritor。
问题是object
的{{1}}没有使用__getattribute__
来搜索类中的属性 - 如果它这样做,只是实现type
元类上的1}}就足够了。必须在实例上执行此操作以避免方法的实例lookp,并在元类上避免元类查找。当然,元类可以注入所需的代码:
__getattribute__
答案 1 :(得分:9)
如果Undestroyable
是一个独特的(或至少是不寻常的)情况,那么重新定义destroy()
可能最简单:
class Undestroyable(Planet):
# ...
def destroy(self):
cls_name = self.__class__.__name__
raise AttributeError("%s has no attribute 'destroy'" % cls_name)
从类的用户的角度来看,这将表现为Undestroyable.destroy()
不存在......除非他们一直在寻找hasattr(Undestroyable, 'destroy')
,这总是有可能。
如果更频繁地发生您希望子类继承某些属性而不是其他属性,则chepner's answer中的mixin方法可能更易于维护。您可以通过Destructible
和abstract base class:
from abc import abstractmethod, ABCMeta
class Destructible(metaclass=ABCMeta):
@abstractmethod
def destroy(self):
pass
class BasePlanet:
# ...
pass
class Planet(BasePlanet, Destructible):
def destroy(self):
# ...
pass
class IndestructiblePlanet(BasePlanet):
# ...
pass
这样做的好处是,如果您尝试实例化抽象类Destructible
,则会出现错误,指出您遇到的问题:
>>> Destructible()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Destructible with abstract methods destroy
...同样,如果您继承Destructible
但忘记定义destroy()
:
class InscrutablePlanet(BasePlanet, Destructible):
pass
>>> InscrutablePlanet()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class InscrutablePlanet with abstract methods destroy
答案 2 :(得分:5)
不是删除继承的属性,而是仅通过混合类在适用的子类中继承destroy
。这保留了正确的“is-a”继承语义。
class Destructible(object):
def destroy(self):
pass
class BasePlanet(object):
...
class Planet(BasePlanet, Destructible):
...
class IndestructiblePlanet(BasePlanet): # Does *not* inherit from Destructible
...
您可以在destroy
,Destructible
或任何继承自Planet
的类中为Planet
提供合适的定义。
答案 3 :(得分:3)
元类和描述符协议很有趣,但可能有点过分。有时,对于原始功能,你无法击败好的'__slots__
。
class Planet(object):
def __init__(self, name):
self.name = name
def destroy(self):
print("Boom! %s is toast!\n" % self.name)
class Undestroyable(Planet):
__slots__ = ['destroy']
def __init__(self,name):
super().__init__(name)
print()
x = Planet('Pluto') # Small, easy to destroy
y = Undestroyable('Jupiter') # Too big to fail
x.destroy()
y.destroy()
Boom! Pluto is toast!
Traceback (most recent call last):
File "planets.py", line 95, in <module>
y.destroy()
AttributeError: destroy
答案 4 :(得分:0)
您不能只继承某个类的一部分。它的全部或全部。
你可以做的是将destroy函数放在类的第二级,比如你有没有destry-function的Planet-class,然后你创建一个DestroyablePlanet-Class来添加destroy-function,所有可破坏的行星都使用它。
或者你可以在Planet-Class的构造中放置一个标志,它确定destroy函数是否能够成功,然后在destroy-function中检查。