[Tutor] question about metaclasses
Peter Otten
__peter__ at web.de
Wed Jan 10 13:29:58 EST 2018
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 into
> 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
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')
More information about the Tutor
mailing list