[Tutor] question about metaclasses

Albert-Jan Roskam sjeik_appie at hotmail.com
Wed Jan 10 11:08:04 EST 2018


Hi,


In another thread on this list I was reminded of types.SimpleNamespace. This is nice, but I wanted to create a bag class with constants that are read-only. My main question is about example #3 below (example #2 just illustrates my thought process). Is this a use case to a metaclass? Or can I do it some other way (maybe a class decorator?). I would like to create a metaclass that converts any non-special attributes (=not starting with '_') into properties, if needed. That way I can specify my bag class in a very clean way: I only specify the metaclass, and I list the attributes as normal attrbutes, because the metaclass will convert them into properties.


Why does following the line (in #3) not convert the normal attribute into a property?

setattr(cls, attr, property(lambda self: obj))  # incorrect!



# 1-------------------------------------------------
# nice, but I want the constants to be read-only
from types import SimpleNamespace
const = SimpleNamespace(YES=1, NO=0, DUNNO=9)
const.YES = 0
print(const)


# 2-------------------------------------------------
# works, but I wonder if there's a builtin way
class Const(object):
    """Adding attributes is ok, modifying them is not"""
    YES = property(lambda self: 1)
    NO = property(lambda self: 0)
    DUNNO = property(lambda self: 42)
    #THROWS_ERROR = 666

    def __new__(cls):
        for attr in dir(cls):
            if attr.startswith('_'):
                continue
            elif not isinstance(getattr(cls, attr), property):
                raise ValueError("Only properties allowed")
        return super().__new__(cls)
    def __repr__(self):
        kv = ["%s=%s" % (attr, getattr(self, attr)) for \
              attr in sorted(dir(Const)) if not attr.startswith('_')]
        return "ReadOnlyNamespace(" + ", ".join(kv) + ")"

c = Const()
print(repr(c))
#c.YES = 42  # raises AttributeError (desired behavior)

print(c.YES)


# 3-------------------------------------------------
class Meta(type):
    def __new__(cls, name, bases, attrs):
        for attr, obj in attrs.items():
            if attr.startswith('_'):
                continue
            elif not isinstance(obj, property):
                import pdb;pdb.set_trace()
                #setattr(cls, attr, property(lambda self: obj))  # incorrect!
                raise ValueError("Only properties allowed")
        return super().__new__(cls, name, bases, attrs)

class MyReadOnlyConst(metaclass=Meta):
    __metaclass__ = Meta
    YES = property(lambda self: 1)
    NO = property(lambda self: 0)
    DUNNO = property(lambda self: 42)
    THROWS_ERROR = 666


c2 = MyReadOnlyConst()
print(c2.THROWS_ERROR)
#c2.THROWS_ERROR = 777
#print(c2.THROWS_ERROR)


Thank you in advance and sorry about the large amount of code!


Albert-Jan



More information about the Tutor mailing list