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为一个类定义一个字典而烦恼,为什么不直接定义属性呢?效果是一样的。

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):
    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 回答
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 回答
0

您应该在这里使用名称修饰

重命名defaults__defaults

这为每个类提供了一个明确的属性,因此它们不会相互混淆

于 2011-01-05T20:53:46.337 回答
0

在问题的第二次编辑中提出的解决方案仍然是唯一提供我的应用程序所需的一切的解决方案。虽然 unutbu 的代码可能更容易理解,但该__mro__解决方案提供了一些 IMO 优势(见评论)。

于 2011-01-11T18:35:28.430 回答