javex
javex

Reputation: 7554

How to create a class property with a metaclass that only overrides when the class itself doesn't have it defined?

I have a setup like this:

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


class Test(object):
    __metaclass__ = Meta
    test = "Test"


class TestSub(object):
    test = "TestSub"

print(Test.test, TestSub.test)

Which yields the following output:

('Meta', 'TestSub')

What I would have expected would be:

('Test', 'TestSub')

I know why that happens: test is assigned on Test before the metaclass Meta is executed. But I have no idea about how to implement a clean way of changing this behavior. I know I could hack around in __init__ and __new__ of the Meta class, but that seems dirty because I'd have to modify it for each new property. So is there a clean way (like writing a new decorator) to get this?

I also don't like the idea of creating an intermediate class just to work around this, but would accept it as a last resort.

Upvotes: 1

Views: 83

Answers (3)

unutbu
unutbu

Reputation: 880547

If that's what you want, don't use a metaclass. Use inheritance:

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

class Test(Base):
    test = "Test"

class TestSub(object):
    test = "TestSub"

print(Test.test, TestSub.test)

yields

('Test', 'TestSub')

Upvotes: 1

Alex Pertsev
Alex Pertsev

Reputation: 948

I'm not sure is it what you need?

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

    def __new__(cls, name, bases, dct):
        if 'test' not in dct:
            dct['test'] = Meta.test
        return super(Meta, cls).__new__(cls, name, bases, dct)

class Test(object):
    __metaclass__ = Meta
    test = "Test"

class TestSub(object):
    test = "TestSub"

class TestWithoutTest(object):
    __metaclass__ = Meta

print(Test.test, TestSub.test, TestWithoutTest.test)

Upvotes: 0

BrenBarn
BrenBarn

Reputation: 251518

In fact, your Test class's test attribute is not overwritten. It's still there:

>>> Test.__dict__['test']
'Test'

However, doing Test.test doesn't access it, because, according to the documentation:

If an instance’s dictionary has an entry with the same name as a data descriptor, the data descriptor takes precedence.

property creates a data descriptor. So by using a property in the metaclass, you block access to the ordinary class variable on the class.

How best to solve this is not clear, because it's not clear what you're trying to accomplish with this structure. Given the code you posted, it's not clear why you're using a metaclass at all. If you just want override class attributes on subclasses, you can do it with simple inheritance as described in unutbu's answer.

Upvotes: 1

Related Questions