[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