stackoverflow question
Ethan Furman
ethan at stoneleaf.us
Sat Mar 10 17:21:55 EST 2012
Owen Jacobson wrote:
> On 2012-03-09 22:10:18 +0000, Ethan Furman said:
>
>> Hey all!
>>
>> I posted a question/answer on SO earlier, but there seems to be some
>> confusion around either the question or the answer (judging from the
>> comments).
>>
>> http://stackoverflow.com/q/9638921/208880
>>
>> If anyone here is willing to take a look at it and let me know if I
>> did not write it well, I would appreciate the feedback.
>>
>>
>> Here's the question text:
>> ------------------------
>> I'm writing a metaclass to do some cool stuff, and part of its
>> processing is to check that certain attributes exist when the class is
>> created. Some of these are mutable, and would normally be set in
>> `__init__`, but since `__init__` isn't run until the instance is
>> created the metaclass won't know that the attribute *will* be created,
>> and raises an error. I could do something like:
>>
>> class Test(meta=Meta):
>> mutable = None
>> def __init__(self):
>> self.mutable = list()
>>
>> But that isn't very elegant, and also violates DRY.
>>
>> What I need is some way to have:
>>
>> class Test(metaclass=Meta):
>> mutable = list()
>>
>> t1 = Test()
>> t2 = Test()
>> t1.mutable.append('one')
>> t2.mutable.append('two')
>> t1.mutable # prints ['one']
>> t2.mutable # prints ['two']
>>
>> Any ideas on how this can be accomplished?
>>
>> Also, the metaclass doing the checking doesn't care what type of
>> object the attribute is, only that it is there.
>> ---------------------------
>
> Why check what you can ensure? The __init__ function your metaclass
> passes to type() doesn't have to be the __init__ method your metaclass
> received. Consider the following:
>
>>>> import functools as f
>>>>
>>>> def make_init(real_init):
>>>> """Define an __init__ method that ensures ``self.mutable`` is
>>>> set. If the
>>>> passed ``real_init`` function later replaces ``self.mutable``,
>>>> that value
>>>> is preserved; otherwise, ``self.mutable`` is set to a new, empty
>>>> list.
>>>>
>>>> Arguments to the generated ``__init__`` method are passed to the
>>>> original
>>>> ``real_init`` unchanged.
>>>> """
>>>> def __init__(self, *args, **kwargs):
>>>> self.mutable = list()
>>>> if real_init is not None:
>>>> return real_init(self, *args, **kwargs)
>>>> if real_init is not None:
>>>> f.update_wrapper(__init__, real_init)
>>>> return __init__
>>>>
>>>> class Meta(type):
>>>> def __new__(meta, name, parents, attributes):
>>>> attributes['__init__'] =
>>>> make_init(attributes.get('__init__', None))
>>>> return type.__new__(Meta, name, parents, attributes)
>>>>
>>>> class C(object):
>>>> __metaclass__ = Meta
>>>>
>>>> a, b = C(), C()
>>>>
>>>> a.mutable.append(3)
>>>> b.mutable.append(5)
>>>>
>>>> a.mutable
> [3]
>>>> b.mutable
> [5]
>
> All instances of classes whose metaclass is Meta will, guaranteed, have
> an instance field named 'mutable'. Its value is a list created at
> instance creation time, unless the instance's __init__ provides a
> different value.
The idea is good. The devil is in the details, as usual. How is the
metaclass going to know:
1) which attributes to replace
2) what to replace them with?
~Ethan~
More information about the Python-list
mailing list