Reputation: 393
In Python 3.6, I can use the __set_name__
hook to get the class attribute name of a descriptor. How can I achieve this in python 2.x?
This is the code which works fine in Python 3.6:
class IntField:
def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__[self.name]
def __set__(self, instance, value):
if not isinstance(value, int):
raise ValueError('expecting integer')
instance.__dict__[self.name] = value
def __set_name__(self, owner, name):
self.name = name
class Example:
a = IntField()
Upvotes: 6
Views: 1570
Reputation: 6299
You may be looking for metaclasses, with it you can process the class attributes at class creation time.
class FooDescriptor(object):
def __get__(self, obj, objtype):
print('calling getter')
class FooMeta(type):
def __init__(cls, name, bases, attrs):
for k, v in attrs.iteritems():
if issubclass(type(v), FooDescriptor):
print('FooMeta.__init__, attribute name is "{}"'.format(k))
class Foo(object):
__metaclass__ = FooMeta
foo = FooDescriptor()
f = Foo()
f.foo
Output:
FooMeta.__init__, attribute name is "foo"
calling getter
If you need to change the class before it is created you need to override __new__
instead of __init__
at your metaclass. See this answer for more information on this topic: Is there any reason to choose __new__ over __init__ when defining a metaclass?
Upvotes: 6
Reputation: 78750
There are various solutions with different degrees of hackishness. I always liked to use a class decorator for this.
class IntField(object):
def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__[self.name]
def __set__(self, instance, value):
if not isinstance(value, int):
raise ValueError('expecting integer')
instance.__dict__[self.name] = value
def with_intfields(*names):
def with_concrete_intfields(cls):
for name in names:
field = IntField()
field.name = name
setattr(cls, name, field)
return cls
return with_concrete_intfields
You can use it like this:
@with_intfields('a', 'b')
class Example(object):
pass
e = Example()
Demo:
$ python2.7 -i clsdec.py
>>> [x for x in vars(Example) if not x.startswith('_')]
['a', 'b']
>>> Example.a.name
'a'
>>> e.a = 3
>>> e.b = 'test'
[...]
ValueError: expecting integer
Make sure to explicitly subclass from object
in Python 2.7, that got me tripped up when I drafted the first version of this answer.
Upvotes: 2