Удаление существующей переменной класса yield AttributeError - PullRequest
4 голосов
/ 15 мая 2019

Я манипулирую созданием классов через метаклассы 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

Выполнение вышеуказанного кода дает AttributeError при создании класса B:

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 на экземпляре класса приводит к неожиданному AttributeError , который пытается удалить переменную класса через экземпляр. Напротив, я пытаюсь удалить переменную класса (экземпляр псевдонима) через класс (экземпляр псевдонима). Таким образом, данное исправление НЕ работает в этом случае.

EDIT2: olinox14 прав, это проблема «удалить атрибут родительского класса». Проблема может быть уменьшена до:

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

Ответы [ 2 ]

3 голосов
/ 15 мая 2019

Кажется, что python регистрирует переменную x как параметр параметра класса A:

capture

Затем, когда вы пытаетесь удалить ееиз класса B существует некоторый конфликт с методом delattr, как упомянуто в ссылке , которую @David Herring предоставил ...

Обходной путь может быть явно удален параметр из класса A:

delattr(A, "x")
2 голосов
/ 16 мая 2019

Как вы пришли к выводу в своей упрощенной версии, все происходит просто: атрибут «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
...