6

次のコードを見てください。

class A(object):
    defaults = {'a': 1}

    def __getattr__(self, name):
        print('A.__getattr__')
        return self.get_default(name)

    @classmethod
    def get_default(cls, name):
        # some debug output
        print('A.get_default({}) - {}'.format(name, cls))
        try:
            print(super(cls, cls).defaults) # as expected
        except AttributeError: #except for the base object class, of course
            pass

        # the actual function body
        try:
            return cls.defaults[name]
        except KeyError:
            return super(cls, cls).get_default(name) # infinite recursion
            #return cls.__mro__[1].get_default(name) # this works, though

class B(A):
    defaults = {'b': 2}

class C(B):
    defaults = {'c': 3}


c = C()
print('c.a =', c.a)

クラスの階層があり、それぞれにいくつかのデフォルト値を含む独自の辞書があります。クラスのインスタンスに特定の属性がない場合は、代わりにそのデフォルト値を返す必要があります。属性のデフォルト値が現在のクラスのdefaultsディクショナリに含まれていない場合は、スーパークラスのdefaultsディクショナリを検索する必要があります。

再帰クラスメソッドを使用してこれを実装しようとしていget_defaultます。残念ながら、プログラムは無限再帰でスタックします。私の理解super()は明らかに欠けています。にアクセスすること__mro__で、正しく機能させることができますが、これが適切な解決策かどうかはわかりません。

答えはこの記事のどこかにあるように感じますが、まだ見つけることができていません。おそらく、メタクラスの使用に頼る必要がありますか?

編集:私のアプリケーションでは、__getattr__最初にチェックしますself.base。そうでない場合はNone、そこから属性をフェッチする必要があります。それ以外の場合のみ、デフォルト値を返す必要があります。おそらくオーバーライドできます__getattribute__。それがより良い解決策でしょうか?

編集2:以下は私が探している機能の拡張例です。これは現在、__mro__(私の元の再帰的方法とは対照的に、unutbuの以前の提案)を使用して実装されています。誰かがよりエレガントな解決策を提案できない限り、私はこの実装を使用して満足しています。これで問題が解決することを願っています。

class A(object):
    defaults = {'a': 1}

    def __init__(self, name, base=None):
        self.name = name
        self.base = base

    def __repr__(self):
        return self.name

    def __getattr__(self, name):
        print(" '{}' attribute not present in '{}'".format(name, self))
        if self.base is not None:
            print("  getting '{}' from base ({})".format(name, self.base))
            return getattr(self.base, name)
        else:
            print("  base = None; returning default value")
            return self.get_default(name)

    def get_default(self, name):
        for cls in self.__class__.__mro__:
            try:
                return cls.defaults[name]
            except KeyError:
                pass
        raise KeyError

class B(A):
    defaults = {'b': 2}

class C(B):
    defaults = {'c': 3}


c1 = C('c1')
c1.b = 55

print('c1.a = ...'); print('   ...', c1.a) # 1
print(); print('c1.b = ...'); print('   ...', c1.b) # 55
print(); print('c1.c = ...'); print('   ...', c1.c) # 3

c2 = C('c2', base=c1)
c2.c = 99

print(); print('c2.a = ...'); print('   ...', c2.a) # 1
print(); print('c2.b = ...'); print('   ...', c2.b) # 55
print(); print('c2.c = ...'); print('   ...', c2.c) # 99

出力:

c1.a = ...
 'a' attribute not present in 'c1'
  base = None; returning default value
   ... 1

c1.b = ...
   ... 55

c1.c = ...
 'c' attribute not present in 'c1'
  base = None; returning default value
   ... 3

c2.a = ...
 'a' attribute not present in 'c2'
  getting 'a' from base (c1)
 'a' attribute not present in 'c1'
  base = None; returning default value
   ... 1

c2.b = ...
 'b' attribute not present in 'c2'
  getting 'b' from base (c1)
   ... 55

c2.c = ...
   ... 99
4

6 に答える 6

8

実際には答えではなく、観察です。

これは私には過剰に設計されているように見えます。これは、Pythonマジックを使用するための言い訳を探すときによくある罠です。

defaultsクラスのdictを定義するのが面倒な場合は、代わりに属性を定義してみませんか?効果は同じです。

class A:
    a = 1

class B(A):
    b = 2

class C(B):
    c = 3


c = C()
print('c.a =', c.a)

編集:

質問への回答については、おそらく__getattribute__次のような提案と組み合わせて使用​​します。

def __getattribute__(self, name):
    try:
        return object.__getattribute__(self.base, name)
    except AttributeError:
        return object.__getattribute__(self, name)
于 2011-01-05T20:56:03.717 に答える
2

問題の原因は の目的を誤解していることにあると思いますsuper()

http://docs.python.org/library/functions.html#super

基本的に、オブジェクト (またはクラス) を super() でラップすると、Python は属性ルックアップを行うときに、最近継承されたクラスをスキップします。あなたのコードでは、get_default を探すときにクラス C がスキップされますが、C は get_default を定義していないため、これは実際には何もしません。当然、これは無限ループになります。

解決策は、A から派生する各クラスでこの関数を定義することです。これは、メタクラスを使用して実行できます。

class DefaultsClass(type):
    def __init__(cls, name, bases, dct):

        def get_default(self, name):
            # some debug output
            print('A.get_default(%s) - %s' % (name, cls))
            try:
                print(cls.defaults) # as expected
            except AttributeError: #except for the base object class, of course
                pass

            # the actual function body
            try:
                return cls.defaults[name]
            except KeyError:
                return super(cls, self).get_default(name) # cooperative superclass

        cls.get_default = get_default
        return super(DefaultsClass, cls).__init__(name, bases, dct)

class A(object):
    defaults = {'a': 1}
    __metaclass__ = DefaultsClass

    def __getattr__(self, name):
        return self.get_default(name)



class B(A):
    defaults = {'b': 2}

class C(B):
    defaults = {'c': 3}


c = C()
print('c.a =', c.a)
print('c.b =', c.b)
print('c.c =', c.c)

結果:

A.get_default(c) - <class '__main__.C'>
{'c': 3}
('c.c =', 3)
A.get_default(b) - <class '__main__.C'>
{'c': 3}
A.get_default(b) - <class '__main__.B'>
{'b': 2}
('c.b =', 2)
A.get_default(a) - <class '__main__.C'>
{'c': 3}
A.get_default(a) - <class '__main__.B'>
{'b': 2}
A.get_default(a) - <class '__main__.A'>
{'a': 1}
('c.a =', 1)

ほとんどの Python 関係者は、これを非常に奇抜な解決策と見なすことに注意してください。これは、本当に必要な場合 (レガシー コードをサポートする場合など) にのみ使用する必要があります。

于 2011-01-05T22:26:27.800 に答える
1

「ベース」と「デフォルト」のケースをより明確にするため。

>>> class A(object):
...     a = 1
... 
>>> class B(A):
...     b = 2
... 
>>> class C(B):
...     c = 3
... 
>>> a = A()
>>> b = B()
>>> c = C()
>>> 
>>> b.b = 23
>>> b.a
1
>>> b.b
23
>>> c.a
1
>>> c.b
2
>>> c.c
3
>>> c.c = 45
>>> c.c
45

これはあなたが述べたユースケースをカバーしています。魔法はまったく必要ありません。ユースケースが多少異なる場合は、それが何であるかを説明してください。魔法を使わずにそれを行う方法を説明します。;)

于 2011-01-05T23:26:50.263 に答える
1

どうですか:

class A(object):
    def __init__(self,base=None):
        self.a=1
        if base is not None:
            self.set_base(base)
        super(A,self).__init__() 
    def set_base(self,base):
        for key in ('a b c'.split()):
            setattr(self,key,getattr(base,key))
class B(A): 
    def __init__(self,base=None):
        self.b=2
        super(B,self).__init__(base)        
class C(B): 
    def __init__(self,base=None):
        self.c=3
        super(C,self).__init__(base)

c1=C()
c1.b=55
print(c1.a)
print(c1.b)
print(c1.c)
# 1
# 55
# 3

c2=C(c1)
c2.c=99
print(c2.a)
print(c2.b)
print(c2.c)
# 1
# 55
# 99

c1.set_base(c2)
print(c1.a)
print(c1.b)
print(c1.c)
# 1
# 55
# 99
于 2011-01-05T21:19:56.623 に答える
0

ここでは名前マングリングを使用する必要があります。

defaultsに名前を変更__defaults

これにより、各クラスに明確な属性が与えられるため、互いに混同されることはありません

于 2011-01-05T20:53:46.337 に答える
0

質問の2番目の編集で提案されたソリューションは、アプリケーションが必要とするすべてを提供する唯一のものです。unutbu のコードは理解しやすいかもしれませんが、この__mro__ソリューションにはいくつかの利点があります (コメントを参照)。

于 2011-01-11T18:35:28.430 に答える