web-dev-qa-db-ja.com

Python:サブクラスのクラス属性を削除する

サブクラスがありますnot基本クラスにあるクラス属性を含めます。

私はこれを試しましたが、うまくいきません:

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

これどうやってするの?

43
Ram Rachum

なぜこれを実行するのかを慎重に検討してください。あなたはおそらくしません。 BがAから継承しないようにすることを検討してください。

サブクラス化のアイデアは、オブジェクトを特殊化することです。特に、クラスの子は親クラスの有効なインスタンスである必要があります。

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

この動作を実装する場合(例:x = property(lambda: AttributeError))、サブクラス化の概念に違反しており、これは悪いことです。

12
Katriel

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')
51
Bhushan

削除する必要はありません。オーバーライドするだけです。

class B(A):
   x = None

または単にそれを参照しないでください。

または、別のデザイン(インスタンス属性?)を検討してください。

17
Keith

答えはどれもうまくいきませんでした。

たとえば、delattr(SubClass, "attrname")(またはそれに相当する_del SubClass.attrname_)は、親メソッドを「非表示」にしません。これは、メソッド解決が機能しないためです。サブクラスにはattrnameがないため、代わりにAttributeError('attrname',)で失敗します。そしてもちろん、attributeをNoneで置き換えても実際には削除されません。

この基本クラスを考えてみましょう:

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

私はそれをサブクラス化する2つの唯一の方法を知っており、expect属性を隠しています:

  1. ___get___ からAttributeErrorを上げる記述子クラスを使用します。属性のルックアップでは、例外が発生しますが、通常はルックアップの失敗と区別がつきません。

    最も簡単な方法は、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アサーションは無効になります)。

    上記のすべてのアサーションを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___マジックメソッドを使用 。これは、サブクラス(または以下の例のように1回だけ記述したいため、ミックスイン)で実行でき、サブクラスinstancesの属性を非表示にします。メソッドをサブクラスからも非表示にする場合は、メタクラスを使用する必要があります。

    _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___)に対してnotを機能させることに注意してください。詳細については、Pythonのドキュメントの 特別なメソッドルックアップ セクションをご覧ください。これを元に戻す必要がある場合は、オーバーライドしてobject 'sを呼び出します。実装、親をスキップします。

言うまでもなく、これは「新しいスタイルのクラス」(objectから継承するクラス)にのみ適用されます。魔法のメソッドと記述子プロトコルはそこでサポートされていないためです。うまくいけば、それらは過去のものです。

7
drdaeman

たぶん、あなたはxpropertyとして設定し、誰かがアクセスしようとするたびに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
7
JBernardo

私も同じ問題を抱えていて、サブクラスのクラス属性を削除する正当な理由があると思いました:スーパークラス(Aと呼びます)には、属性の値を提供する読み取り専用プロパティがありましたが、私のサブクラス(Bと呼びます)では、属性は読み取り/書き込みインスタンス変数でした。 Pythonは、インスタンス変数がそれをオーバーライドする必要があると考えていたにもかかわらず、プロパティ関数を呼び出していました。基になるプロパティにアクセスするために使用する別のゲッター関数を作成することもできましたが、 (それが本当に重要であるかのように)インターフェース名前空間の不必要でエレガントでない乱雑なように見えました。

結局のところ、答えはAの元の共通属性を持つ新しい抽象スーパークラス(Sと呼ぶ)を作成し、AとBをSから派生させることでした。Python 、BがAを拡張しなくても問題ありません。暗黙的に同じインターフェイスを実装しているため、同じ場所で使用できます。

5
acjay

これをしようとすることはおそらく悪い考えですが、...

デフォルトで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への応答として提供されます。

一方、あなたがcouldすることは、メタクラスを使用して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は、BAのサブタイプである場合、Bを使用できるときはいつでもAを使用できますが、これができないためこの構成を実行している間、Bは実際にはAのサブタイプではないため、LSPに違反していないと結論付けることができます。

1
skyking