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