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.

What've I missed?

-o

--
http://mail.python.org/mailman/listinfo/python-list

Reply via email to