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