[Tutor] question about metaclasses

Albert-Jan Roskam sjeik_appie at hotmail.com
Thu Jan 18 12:14:43 EST 2018


On Jan 10, 2018 19:32, Peter Otten <__peter__ at 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 at python.org
> To unsubscribe or change subscription options:
> https://mail.python.org/mailman/listinfo/tutor


More information about the Tutor mailing list