Подтвердить что ты не робот

Python: удаление атрибута класса в подклассе

У меня есть подкласс, и я хочу, чтобы он не включал атрибут класса, который присутствует в базовом классе.

Я пробовал это, но он не работает:

>>> class A(object):
...     x = 5
>>> class B(A):
...     del x
Traceback (most recent call last):
  File "<pyshell#1>", line 1, in <module>
    class B(A):
  File "<pyshell#1>", line 2, in B
    del x
NameError: name 'x' is not defined

Как я могу это сделать?

4b9b3361

Ответ 1

Подумайте, почему вы хотите это сделать; вы, вероятно, этого не делаете. Полагайте, что B не наследуется от A.

Идея подклассификации - специализировать объект. В частности, дети класса должны быть действительными экземплярами родительского класса:

>>> class foo(dict): pass
>>> isinstance(foo(), dict)
... True

Если вы реализуете это поведение (например, x = property(lambda: AttributeError)), вы нарушаете концепцию подкласса, и это плохо.

Ответ 2

Вы можете использовать delattr(class, field_name) чтобы удалить его из определения класса.

Полный пример:

# python 3
class A:  
    def __init__(self):
        self.field_to_keep = "keep this in B"
        self.field_to_delete = "don't want this in B"

class B(A):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        delattr(self, 'field_to_delete')

Ответ 3

Вам не нужно его удалять. Просто отмените это.

class B(A):
   x = None

или просто не ссылайтесь на него.

Или рассмотрим другой дизайн (атрибут экземпляра?).

Ответ 4

Возможно, вы могли бы установить x как property и поднять AttributeError всякий раз, когда кто-то пытается получить к нему доступ.

>>> class C:
        x = 5

>>> class D(C):
        def foo(self):
             raise AttributeError
        x = property(foo)

>>> d = D()
>>> print(d.x)
File "<pyshell#17>", line 3, in foo
raise AttributeError
AttributeError

Ответ 5

Ни один из ответов не сработал для меня.

Например, delattr(SubClass, "attrname") (или его точный эквивалент, del SubClass.attrname) не будет "скрывать" родительский метод, потому что это не то, как работает разрешение метода. Вместо этого произойдет сбой с AttributeError('attrname',), поскольку у подкласса нет attrname. И, конечно же, замена атрибута на None фактически не удаляет его.

Давайте рассмотрим этот базовый класс:

class Spam(object):
    # Also try with 'expect = True' and with a '@property' decorator
    def expect(self):
        return "This is pretty much expected"

Я знаю только два способа подкласса, скрывая атрибут expect:

  1. Использование класса дескриптора, который вызывает AttributeError из __get__. При поиске атрибутов будет исключение, обычно неотличимое от ошибки поиска.

    Самый простой способ - просто объявить свойство, которое вызывает AttributeError. По сути, это то, что предложил @JBernardo.

    class SpanishInquisition(Spam):
        @property
        def expect(self):
            raise AttributeError("Nobody expects the Spanish Inquisition!")
    
    assert hasattr(Spam, "expect") == True
    # assert hasattr(SpanishInquisition, "expect") == False  # Fails!
    assert hasattr(SpanishInquisition(), "expect") == False
    

    Однако это работает только для экземпляров, а не для классов (hasattr(SpanishInquisition, "expect") == True утверждение будет нарушено).

    Если вы хотите, чтобы все приведенные выше утверждения выполнялись, используйте это:

    class AttributeHider(object):
        def __get__(self, instance, owner):
            raise AttributeError("This is not the attribute you're looking for")
    
    class SpanishInquisition(Spam):
        expect = AttributeHider()
    
    assert hasattr(Spam, "expect") == True
    assert hasattr(SpanishInquisition, "expect") == False  # Works!
    assert hasattr(SpanishInquisition(), "expect") == False
    

    Я считаю, что это самый элегантный метод, так как код понятен, универсален и компактен. Конечно, нужно действительно думать дважды, если удаление атрибута является то, что они действительно хотят.

  2. Переопределение поиска атрибутов с __getattribute__ магического метода __getattribute__. Вы можете сделать это либо в подклассе (или в миксине, как в примере ниже, как я хотел написать это только один раз), и это будет скрывать атрибут в экземплярах подкласса. Если вы также хотите скрыть метод от подкласса, вам нужно использовать метаклассы.

    class ExpectMethodHider(object):
        def __getattribute__(self, name):
            if name == "expect":
                raise AttributeError("Nobody expects the Spanish Inquisition!")
            return super().__getattribute__(name)
    
    class ExpectMethodHidingMetaclass(ExpectMethodHider, type):
        pass
    
    # I've used Python 3.x here, thus the syntax.
    # For Python 2.x use __metaclass__ = ExpectMethodHidingMetaclass
    class SpanishInquisition(ExpectMethodHider, Spam,
                             metaclass=ExpectMethodHidingMetaclass):
        pass
    
    assert hasattr(Spam, "expect") == True
    assert hasattr(SpanishInquisition, "expect") == False
    assert hasattr(SpanishInquisition(), "expect") == False
    

    Это выглядит хуже (более многословно и менее обобщенно), чем описанный выше метод, но можно рассмотреть и этот подход.

    Обратите внимание, что это не работает со специальными ("волшебными") методами (например, __len__), потому что они обходят __getproperty__. Обратитесь к разделу Поиск специальных методов документации Python для получения более подробной информации. Если это то, что вам нужно отменить, просто переопределите его и вызовите реализацию object, пропустив родительский object.

Излишне говорить, что это относится только к "классам нового стиля" (классам, наследуемым от object), поскольку там не поддерживаются магические методы и протоколы дескрипторов. Надеюсь, это в прошлом.

Ответ 6

У меня тоже была проблема, и я подумал, что у меня есть веская причина удалить атрибут класса в подклассе: мой суперкласс (назовите его A) имеет свойство только для чтения, которое предоставило значение атрибута, но в моем подклассе (назовите его B) атрибут был переменной экземпляра чтения/записи. Я обнаружил, что Python вызывал функцию свойства, хотя я думал, что переменная экземпляра должна была ее переопределить. Я мог бы сделать отдельную функцию геттера, которая будет использоваться для доступа к базовому свойству, но это казалось ненужным и неэлегантным загромождением пространства имен интерфейса (как будто это действительно имеет значение).

Как оказалось, ответ заключался в том, чтобы создать новый абстрактный суперкласс (назовите его S) с исходными общими атрибутами A и получить A и B из S. Поскольку Python имеет утиную печать, это не имеет большого значения что B не расширяет A, я все еще могу использовать их в тех же местах, поскольку они неявно реализуют один и тот же интерфейс.

Ответ 7

Попытка сделать это, вероятно, плохая идея, но...

Кажется, что это не происходит с помощью "правильного" наследования из-за того, как работает поиск B.x по умолчанию. При получении B.x x сначала просматривается в B, и если он не найден там, он ищет в A, но, с другой стороны, при установке или удалении B.x будет выполняться поиск только B. Так, например,

>>> class A:
>>>     x = 5

>>> class B(A):
>>>    pass

>>> B.x
5

>>> del B.x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>  
AttributeError: class B has no attribute 'x'

>>> B.x = 6
>>> B.x
6

>>> del B.x
>>> B.x
5

Здесь мы видим, что сначала мы, кажется, не можем удалить B.x, поскольку он не существует (A.x существует, и это то, что получает, когда вы оцениваете B.x). Однако, установив B.x в 6, будет существовать B.x, его можно получить с помощью B.x и удалить с помощью del B.x, с помощью которого оно перестает существовать, после чего снова A.x будет отправлено в ответ на B.x > .

То, что вы могли делать с другой стороны, - использовать метаклассы, чтобы сделать B.x raise AttributeError:

class NoX(type):
    @property
    def x(self):
        raise AttributeError("We don't like X")

class A(object):
    x = [42]

class B(A, metaclass=NoX):
    pass

print(A.x)
print(B.x)

Теперь, конечно, пуристы могут кричать, что это нарушает LSP, но это не так просто. Все это сводится к тому, что если вы считаете, что вы создали подтип, сделав это. Методы issubclass и isinstance говорят "да", но LSP говорит "нет" (и многие программисты предполагают "да", поскольку вы наследуете от A).

LSP означает, что если B является подтипом A, тогда мы могли бы использовать B всякий раз, когда мы могли бы использовать A, но поскольку мы не можем этого сделать при выполнении этой конструкции, мы можем заключить, что B фактически не является подтипом A, и поэтому LSP не нарушается.