Python: удаление атрибута класса в подклассе - PullRequest
37 голосов
/ 19 мая 2011

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

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

>>> 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

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

Ответы [ 7 ]

41 голосов
/ 10 апреля 2013

Вы можете использовать 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')
18 голосов
/ 19 мая 2011

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

class B(A):
   x = None

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

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

9 голосов
/ 19 мая 2011

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

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

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

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

6 голосов
/ 06 мая 2017

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

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

    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), так как магические методы и протоколы дескрипторов там не поддерживаются.Надеюсь, это в прошлом.

6 голосов
/ 19 мая 2011

Возможно, вы могли бы установить 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 голосов
/ 17 сентября 2012

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

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

1 голос
/ 06 мая 2016

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

Похоже, что это не делается с помощью "правильного" наследования, потому что поиск по умолчанию 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 повысить 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 не нарушается.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...