在使用元类时,如何让继承优先于类属性?

时间:2014-03-17 23:11:22

标签: python python-2.7

之前我发过一个类似的问题,但我解释了我的问题是错误的,所以我将其标记为删除,并提出了新的正确问题。

我的总体目标如下:我想在上使用property,因此我使用我的问题{{3}上建议的属性在元类上实现它}。此外,我希望用户能够子类化基类并使用静态值覆盖此属性。这里的事情是,如果用户没有提供属性,我想计算一个合理的默认值,因为所有配置都是在类级别完成的,所以我需要一个类级别的属性。

一个基本的例子将显示:

class Meta(type):
    @property
    def test(self):
        return "Meta"


class Test(object):
    __metaclass__ = Meta


class TestSub(Test):
    test = "TestSub"


class TestSubWithout(Test):
    pass

print(TestSub.test, TestSubWithout.test)

以下是它的印刷品:

('Meta', 'Meta')

我希望它打印出来:

('TestSub', 'Meta')

基本上,在TestSub上,用户自己会覆盖test属性。现在,TestSub是正确的输出,因为用户提供了它。在TestSubWithout的情况下,用户不提供属性,因此应该计算默认值(当然,实际计算不仅仅是静态返回,这只是为了显示它)。

我知道这里发生了什么:首先将属性test分配给TestSub,然后元类覆盖它。但我不知道如何以干净和pythonic 的方式阻止它。

2 个答案:

答案 0 :(得分:1)

我能想到的最干净的方法是创建一个处理这种情况的property子类:

class meta_property(property):
    def __init__(self, fget, fset=None, fdel=None, doc=None):
        self.key = fget.__name__
        super(meta_property, self).__init__(fget, fset, fdel, doc)

    def __get__(self, obj, type_):
        if self.key in obj.__dict__:
            return obj.__dict__[self.key]
        else:
            return super(meta_property, self).__get__(obj, type_)

通过存储函数的名称并返回被覆盖的值(如果存在)来处理这种情况。这似乎是一个很好的解决方案,但我愿意接受更高级的建议。

答案 1 :(得分:1)

property是一个“数据描述符”,这意味着它在属性搜索路径中优先于存储在实例字典中的值(或者对于一个类,其MRO中其他类的实例字典) )。

相反,编写自己的非数据描述符,其作用类似于property

class NonDataProperty(object):
    def __init__(self, fget):
        self.fget = fget

    def __get__(self, obj, type):
        if obj:
            return self.fget(obj)
        else:
            return self

    # don't define a __set__ method!

以下是对它的测试:

class MyMeta(type):
    @NonDataProperty
    def x(self):
        return "foo"

class Foo(metaclass=MyMeta):
    pass # does not override x

class Bar(metaclass=MyMeta):
    x = "bar" # does override x

class Baz:
    x = "baz"

class Baz_with_meta(Baz, metaclass=MyMeta):
    pass # inherited x value will take precedence over non-data descriptor

print(Foo.x) # prints "foo"
print(Bar.x) # prints "bar"
print(Baz_with_meta.x) # prints "baz"