【问题标题】:Deleting existing class variable yield AttributeError删除现有的类变量 yield AttributeError
【发布时间】:2019-10-02 17:14:31
【问题描述】:

我正在通过 Python 的元类来操作类的创建。但是,虽然类由于其父类而具有属性,但我无法删除它。

class Meta(type):
    def __init__(cls, name, bases, dct):
        super().__init__(name, bases, dct)
        if hasattr(cls, "x"):
            print(cls.__name__, "has x, deleting")
            delattr(cls, "x")
        else:
            print(cls.__name__, "has no x, creating")
            cls.x = 13
class A(metaclass=Meta):
    pass
class B(A):
    pass

在创建类B 时,执行上述代码会产生一个AttributeError

A has no x, creating
B has x, deleting
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-3-49e93612dcb8> in <module>()
     10 class A(metaclass=Meta):
     11     pass
---> 12 class B(A):
     13     pass
     14 class C(B):

<ipython-input-3-49e93612dcb8> in __init__(cls, name, bases, dct)
      4         if hasattr(cls, "x"):
      5             print(cls.__name__, "has x, deleting")
----> 6             delattr(cls, "x")
      7         else:
      8             print(cls.__name__, "has no x, creating")

AttributeError: x

为什么我不能删除现有的属性?

编辑:我认为我的问题与delattr on class instance produces unexpected AttributeError 不同,后者试图通过实例删除类变量。相反,我尝试通过类(别名实例)删除类变量(别名实例)。因此,给定的修复在这种情况下不起作用。

EDIT2: olinox14 是对的,是“删除父类属性”的问题。问题可以简化为:

class A:
    x = 13
class B(A):
    pass
del B.x

【问题讨论】:

标签: python metaclass


【解决方案1】:

似乎python将x变量注册为A类的参数:

然后,当您尝试从B 类中删除它时,与delattr 方法存在一些冲突,就像@David Herring 提供的the link 中提到的那样......

解决方法可能是从A 类中明确删除参数:

delattr(A, "x")

【讨论】:

  • 这似乎是合理的。我添加了一个简化的代码,它批准了这一点。
  • 我认为这实际上并不能解释发生了什么。 A.x 存在于A.__dict__ - 并且del B.x 通常会尝试从B.__dict__ 中删除x,仅此而已。
【解决方案2】:

正如您在简化版本中得出的结论,发生的事情很简单:属性“x”不在类中,它在超类中,正常的 Python 属性查找将从那里获取它以进行读取和写入,即设置一个新的cls.x 会在他的子类中创建一个局部x:

In [310]: class B(A): 
     ...:     pass 
     ...:                                                                                                                         

In [311]: B.x                                                                                                                     
Out[311]: 1

In [312]: del B.x                                                                                                                 
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-312-13d95ac593bf> in <module>
----> 1 del B.x

AttributeError: x

In [313]: B.x = 2                                                                                                                 

In [314]: B.__dict__["x"]                                                                                                         
Out[314]: 2

In [315]: B.x                                                                                                                     
Out[315]: 2

In [316]: del B.x                                                                                                                 

In [317]: B.x                                                                                                                     
Out[317]: 1

如果您需要抑制继承类中的属性,可以通过元类中的自定义__getattribute__ 方法(不是__getattr__)。甚至不需要元类上的其他方法(尽管您可以使用它们,例如,编辑要抑制的属性列表)

class MBase(type):
    _suppress = set()

    def __getattribute__(cls, attr_name):
        val = super().__getattribute__(attr_name)
        # Avoid some patologic re-entrancies
        if attr_name.startswith("_"):
            return val
        if attr_name in cls._suppress:
            raise AttributeError()

        return val


class A(metaclass=MBase):
    x = 1

class B(A):
    _suppress = {"x",}

如果有人试图获得B.x,它会加注。

有了这个技巧,将__delattr____setattr__ 方法添加到元类可以删除在子类上的超类中定义的属性:

class MBase(type):
    _suppress = set()

    def __getattribute__(cls, attr_name):
        val = super().__getattribute__(attr_name)
        # Avoid some patologic re-entrancies
        if attr_name.startswith("_"):
            return val
        if attr_name in cls._suppress:
            raise AttributeError()

        return val

    def __delattr__(cls, attr_name):
        # copy metaclass _suppress list to class:
        cls._suppress = set(cls._suppress)
        cls._suppress.add(attr_name)
        try:
            super().__delattr__(attr_name)
        except AttributeError:
            pass

    def __setattr__(cls, attr_name, value):
        super().__setattr__(attr_name, value)
        if not attr_name.startswith("_"):
            cls._suppress -= {attr_name,}



class A(metaclass=MBase):
    x = 1

class B(A):
    pass

在控制台上:

In [400]: B.x                                                                                                                     
Out[400]: 1

In [401]: del B.x                                                                                                                 

In [402]: B.x                                                                                                                     
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
... 

In [403]: A.x                                                                                                                     
Out[403]: 1

【讨论】:

  • 我不得不接受 olinox14 的回答。没错,我不能删除父类的属性(出于任何原因)。但是,您的答案提供了一种替代方法来抑制它们。谢谢!
猜你喜欢
  • 1970-01-01
  • 2017-07-11
  • 2014-07-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多