On Jan 10, 2018 19:32, Peter Otten <__pete...@web.de> wrote: > > Albert-Jan Roskam wrote: > > > Why does following the line (in #3) > > > # 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) > > > not convert the normal attribute int > a property? > > > > setattr(cls, attr, property(lambda self: obj)) # incorrect! > > cls is Meta itself, not MyReadOnlyConst (which is an instance of Meta). > When the code in Meta.__new__() executes MyReadOnlyConst does not yet exist, > but future attributes are already there, in the form of the attrs dict. > Thus to convert the integer value into a read-only property you can > manipulate that dict (or the return value of super().__new__()): > > class Meta(type): > def __new__(cls, name, bases, attrs): > for attr, obj in attrs.items(): > if attr.startswith('_'): > continue > elif not isinstance(obj, property): > attrs[attr] = property(lambda self, obj=obj: obj) > > return super().__new__(cls, name, bases, attrs) > > class MyReadOnlyConst(metaclass=Meta): > YES = property(lambda self: 1) > NO = property(lambda self: 0) > DUNNO = property(lambda self: 42) > THROWS_ERROR = 666 > > c = MyReadOnlyConst() > try: > c.THROWS_ERROR = 42 > except AttributeError: > pass > else: > assert False > assert c.THROWS_ERROR == 666
Thanks all for your replies! Awesome, this is exactly what I want. I think I'll also override __setattr__ so that each newly added attribute is automatically converted into a property Is a metaclass the best/preferred/only way of doing this? Or is a class decorator an alternative route? Is the following analogy for doing stuff when a class is created ('born') correct? Metaclass --> prenatal surgery __new__ --> perinatal surgery Class decorator --> postnatal surgery > PS: If you don't remember why the obj=obj is necessary: > Python uses late binding; without that trick all lambda functions would > return the value bound to the obj name when the for loop has completed. > A simplified example: > > >>> fs = [lambda: x for x in "abc"] > >>> fs[0](), fs[1](), fs[2]() > ('c', 'c', 'c') > >>> fs = [lambda x=x: x for x in "abc"] > >>> fs[0](), fs[1](), fs[2]() > ('a', 'b', 'c') > > > _______________________________________________ > Tutor maillist - Tutor@python.org > To unsubscribe or change subscription options: > https://mail.python.org/mailman/listinfo/tutor _______________________________________________ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor