如何使类属性专属于超类 [英] How to make a class attribute exclusive to the super class

查看:87
本文介绍了如何使类属性专属于超类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个星球大师班:

  class Planet:

def __init __( self,name):
self.name = name
(...)

def destroy(self):
(...)

我还有一些类继承自 Planet 和我想让其中一个无法被销毁(不继承 destroy 函数)



示例:

  class Undestroyable(Planet):

def __init __(self,name):
super( ).__ init __(name)
(...)

#现在它不应该有破坏(自我)功能

所以当这个运行时,

  Undestroyable('This Planet ').destroy()

它应该产生如下错误:

  AttributeError:Undestroyable没有属性'destroy'


解但是,它会破坏部分乐趣 - 也许你必须拥有独立的星球层次结构 - 就像生活在两个抽象类中,每个祖先都是可破坏的和不可破坏的。



第一种方法:描述符装饰器



但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):
应用装饰器时调用
self.func = func
返回self

def __get __(self,instance,owner):
if instance和getattr(instance,self.flag_attr,False):
引发AttributeError('{0}类型的对象没有{1}方法'.format(实例.__ class __.__ name __,self.func .__ name__))
return self.func .__ get __(instance,owner)


class Planet:
def __init __(self,name =):
pass

@Muteable(unftroyable)
def destroy(self):
print(Destroyed)


class BorgWorld(Planet):
unextroyable = True

并且在互动上提示:

 在[110]中:Planet()。destroy()
销毁

在[111]中:BorgWorld()。destroy()
...
AttributeError:BorgWorld类型的对象没有破坏方法

在[112]中:BorgWorld( ).destroy
AttributeError:BorgWorld类型的对象没有销毁方法

感觉不像简单重写该方法,这种方法在检索属性时引发错误 - 甚至会使 hasattr 工作:

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

虽然如果试图直接从类中检索方法而不是从实例中检索方法,它将无效 - 在这种情况下,实例参数 __ get __ 设置为无,我们无法说出它是从哪个类中检索的 - 只是所有者 class,声明它的位置。

 在[114 ]:BorgWorld.destroy 
Out [114]:< function __main __。Planet.destroy>



第二种方法:元数据上的 __ delattr __



在编写上述内容时,我发现Pythn确实拥有 __ delattr __ 特殊方法。如果 Planet 类本身实现 __ delattr __ ,我们会尝试删除 destroy 特定于派生类的方法,它不会起作用: __ delattr __ 判断实例中属性的属性删除 - 如果你试图 del 实例中的destroy方法,无论如何都会失败,因为该方法在类中。



但是,在Python,类本身就是一个实例 - 它的元类。通常是类型。 Planet元类的正确 __ delattr __ 可以通过在创建类之后发出del UndestructiblePlanet.destroy来实现destroy方法的解除。 / p>

同样,我们使用描述符协议在子类上有一个正确的删除方法:

  class已删除:
def __init __(self,cls,name):
self.cls = cls .__ name__
self.name = name
def __get__ (self,instance,owner):
raise AttributeError(类型为'{0}的对象'没有'{1}'方法.format(self.cls,self.name))

class可删除(类型):
def __delattr __(cls,attr):
print(删除自,cls)
setattr(cls,attr,Deleted(cls,attr) )


class Planet(元类=可删除):
def __init __(self,name =):
传递

def destroy(self):
print(Destroyed)


class BorgWorld(Planet):
传递

del BorgWorld.destroy

使用这种方法,甚至尝试检索或检查类本身上的方法existense将起作用:

 在[129]中:BorgWorld.destroy 
...
AttributeError:'BorgWorld'类型的对象没有'destroy'方法

在[130]中:hasattr(BorgWorld,destroy)
Out [130] :False



具有自定义 __ prepare __ 方法的元类。



由于元类允许自定义包含类命名空间的对象,因此可以有一个响应 del的对象语句,添加已删除描述符。



对于使用此元类的用户(程序员),它几乎是相同的,但对于 del 语句已被允许进入类体本身:

  class已删除:
def __init __(self,name):
self.name = name
def __get __(self,instance,owner):
引发AttributeError(类'{1}'上的No'{0}'方法。format(self.name,owner .__ name__))

class可删除(类型):
def __prepare __(mcls,arg):

class D(字典):
def __delitem__ (self,attr):
self [attr] =已删除(attr)

返回D()

class Planet(元类=可删除):
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) :
引发AttributeError(类型为'{0}的对象'没有'{1}'方法.format(self.cls,self.name))


def mute(* methods):
def decorator(cls):
方法中的方法:
setattr(cls,method,Deleted(cls,method))
return cls
返回装饰者


类星球:
def destroy(self):
print(destroyed)

@mute ('destroy')
类BorgPlanet(Planet):
传递



修改 __ getattribute __ 机制:



为了完整起见 - 真正让Python在超类上获取方法和属性的原因是 __ getattribute __ 致电。 对象版本的 __ getattribute __ 是具有数据描述符,实例,类,链优先级的算法of base-classes,...对属性检索进行编码。



因此,更改该类是获得合法属性的一个简单唯一的点错误,不需要在以前的方法中使用不存在的descritor。



问题是对象's __ getattribute __ 没有使用类型来搜索类中的属性 - 如果有的话所以,只需在元类上实现 __ getattribute __ 即可。必须在实例上执行此操作以避免方法的实例lookp,并在元类上执行此操作以避免元类查找。当然,元类可以注入所需的代码:

  def blocker_getattribute(target,attr,attr_base):
尝试:
muted = attr_base .__ getattribute __(target,'__muted__')
除了AttributeError:
muted = []
如果attr处于静音状态:
raise AttributeError( object {}没有属性'{}'。format(target,attr))
返回attr_base .__ getattribute __(target,attr)


def instance_getattribute(self,attr) ):
返回blocker_getattribute(self,attr,object)


类M(类型):
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):
__mu ted __ = ['destroy']#或使用装饰器来设置它! :-)
通过


I have a master class for a planet:

class Planet:

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

    def destroy(self):
        (...)

I also have a few classes that inherit from Planet and I want to make one of them unable to be destroyed (not to inherit the destroy function)

Example:

class Undestroyable(Planet):

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

    #Now it shouldn't have the destroy(self) function

So when this is run,

Undestroyable('This Planet').destroy()

it should produce an error like:

AttributeError: Undestroyable has no attribute 'destroy'

解决方案

The mixin approach in other answers is nice, and probably better for most cases. But nevertheless, it spoils part of the fun - maybe obliging you to have separate planet-hierarchies - like having to live with two abstract classes each ancestor of "destroyable" and "non-destroyable".

First approach: descriptor decorator

But Python has a powerful mechanism, called the "descriptor protocol", which is used to retrieve any attribute from a class or instance - it is even used to ordinarily retrieve methods from instances - so, it is possible to customize the method retrieval in a way it checks if it "should belong" to that class, and raise attribute error otherwise.

The descriptor protocol mandates that whenever you try to get any attribute from an instance object in Python, Python will check if the attribute exists in that object's class, and if so, if the attribute itself has a method named __get__. If it has, __get__ is called (with the instance and class where it is defined as parameters) - and whatever it returns is the attribute. Python uses this to implement methods: functions in Python 3 have a __get__ method that when called, will return another callable object that, in turn, when called will insert the self parameter in a call to the original function.

So, it is possible to create a class whose __get__ method will decide whether to return a function as a bound method or not depending on the outer class been marked as so - for example, it could check an specific flag non_destrutible. This could be done by using a decorator to wrap the method with this descriptor functionality

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

And on the interactive prompt:

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

Perceive that unlike simply overriding the method, this approach raises the error when the attribute is retrieved - and will even make hasattr work:

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

Although, it won't work if one tries to retrieve the method directly from the class, instead of from an instance - in that case the instance parameter to __get__ is set to None, and we can't say from which class it was retrieved - just the owner class, where it was declared.

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

Second approach: __delattr__ on the metaclass:

While writting the above, it occurred me that Pythn does have the __delattr__ special method. If the Planet class itself implements __delattr__ and we'd try to delete the destroy method on specifc derived classes, it wuld nt work: __delattr__ gards the attribute deletion of attributes in instances - and if you'd try to del the "destroy" method in an instance, it would fail anyway, since the method is in the class.

However, in Python, the class itself is an instance - of its "metaclass". That is usually type . A proper __delattr__ on the metaclass of "Planet" could make possible the "disinheitance" of the "destroy" method by issuing a `del UndestructiblePlanet.destroy" after class creation.

Again, we use the descriptor protocol to have a proper "deleted method on the subclass":

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    

And with this method, even trying to retrieve or check for the method existense on the class itself will work:

In [129]: BorgWorld.destroy
...
AttributeError: Objects of type 'BorgWorld' have no 'destroy' method

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

metaclass with a custom __prepare__ method.

Since metaclasses allow one to customize the object that contains the class namespace, it is possible to have an object that responds to a del statement within the class body, adding a Deleted descriptor.

For the user (programmer) using this metaclass, it is almost the samething, but for the del statement been allowed into the class body itself:

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

(The 'deleted' descriptor is the correct form to mark a method as 'deleted' - in this method, though, it can't know the class name at class creation time)

As a class decorator:

And given the "deleted" descriptor, one could simply inform the methods to be removed as a class decorator - there is no need for a metaclass in this case:

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

Modifying the __getattribute__ mechanism:

For sake of completeness - what really makes Python reach methods and attributes on the super-class is what happens inside the __getattribute__ call. n the object version of __getattribute__ is where the algorithm with the priorities for "data-descriptor, instance, class, chain of base-classes, ..." for attribute retrieval is encoded.

So, changing that for the class is an easy an unique point to get a "legitimate" attribute error, without need for the "non-existent" descritor used on the previous methods.

The problem is that object's __getattribute__ does not make use of type's one to search the attribute in the class - if it did so, just implementing the __getattribute__ on the metaclass would suffice. One have to do that on the instance to avoid instance lookp of an method, and on the metaclass to avoid metaclass look-up. A metaclass can, of course, inject the needed code:

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

这篇关于如何使类属性专属于超类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆