如何使类属性成为超级类的专用属性

2024-09-30 22:28:57 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个星球的硕士班:

class Planet:

    def __init__(self,name):
        self.name = name
        (...)

    def destroy(self):
        (...)

我还有一些从Planet继承的类,我想让其中一个不能被销毁(不是继承destroy函数)

示例:

^{pr2}$

所以当这一切开始的时候

Undestroyable('This Planet').destroy()

它会产生如下错误:

AttributeError: Undestroyable has no attribute 'destroy'

Tags: 函数nameself示例initdef错误this
3条回答

其他答案中的mixin方法很好,而且可能在大多数情况下更好。但无论如何,它破坏了部分乐趣-也许迫使你有单独的星球等级-像必须生活在两个抽象类的祖先“可摧毁”和“不可摧毁”。在

第一种方法:描述符修饰符

但是Python有一种强大的机制,叫做“描述符协议”,用于从类或实例中检索任何属性,甚至通常用于从实例中检索方法,因此,可以通过检查方法检索是否“应该属于”该类来定制方法检索,否则会引发属性错误。在

描述符协议规定,每当您试图从Python中的实例对象获取任何属性时,Python将检查该属性是否存在于该对象的类中,如果存在,则检查该属性本身是否具有名为__get__的方法。如果有,则调用__get__(使用实例和类将其定义为参数),并且返回的是属性。Python使用它来实现方法:python3中的函数有一个__get__方法,当调用该方法时,它将返回另一个可调用对象,而当调用该对象时,它将在对原始函数的调用中插入self参数。在

因此,可以创建一个类,该类的__get__方法将决定是否将函数作为绑定方法返回,这取决于外部类是否标记为So—例如,它可以检查特定的标志non_destrutible。这可以通过使用decorator用描述符功能包装方法来完成

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

在交互式提示下:

^{pr2}$

请注意,与简单地重写方法不同,此方法在检索属性时会引发错误,甚至会使hasattr工作:

In [113]: hasattr(BorgWorld(), "destroy")
Out[113]: False

但是,如果试图直接从类中而不是从实例中检索方法,那么它将不起作用——在这种情况下,instance参数被设置为None,而且我们不能说它是从哪个类中检索的——只需要声明它的owner类。在

In [114]: BorgWorld.destroy
Out[114]: <function __main__.Planet.destroy>

第二种方法:__delattr__在元类上:

在写上述内容时,我突然想到Pythn确实有__delattr__特殊方法。如果Planet类本身实现了__delattr__,并且我们试图删除特定派生类上的destroy方法,它将无法工作:__delattr__禁止实例中属性的属性删除-如果您试图在实例中del调用“destroy”方法,它无论如何都会失败,因为该方法在类中。在

然而,在Python中,类本身就是它的“元类”的一个实例。通常是type。“Planet”元类上的一个适当的__delattr__可以通过发出一个'del'来实现“destroy”方法的“deshinity”不可摧毁的飞机。摧毁“课后创作。在

同样,我们使用描述符协议来拥有一个适当的“删除子类的方法”:

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

(“deleted”描述符是将方法标记为“deleted”的正确形式-但是在这个方法中,它在类创建时无法知道类名)

作为班级装饰师:

如果使用“deleted”描述符,您可以简单地将要删除的方法作为类装饰器删除-不需要f或者本例中的元类:

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__调用中发生的事情。n在object版本的__getattribute__中,对属性检索的具有“数据描述符、实例、类、基类链等”优先级的算法进行编码。在

因此,对类进行更改是一个简单的独特点,可以获得“合法”的属性错误,而不需要前面的方法中使用的“不存在”描述符。在

问题是object__getattribute__没有使用type的一个来搜索类中的属性-如果这样做了,只在元类上实现{}就足够了。必须在实例上这样做以避免方法的实例查找,而在元类上这样做可以避免元类查找。当然,元类可以注入所需的代码:

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

如果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方法可能更易于维护。您可以通过使Destructibleabstract base class来进一步改进它:

^{pr2}$

这样做的好处是,如果您尝试实例化抽象类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

与其删除继承的属性,不如通过mix-in类在子类中继承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
    ...

您可以在DestructiblePlanet或从{}继承的任何类中为destroy提供合适的定义。在

相关问题 更多 >