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.
---------------------------

and the answer text:
-------------------
There are a couple ways to do this:

1. Have the metaclass check all the attributes, and if they are one of the mutables (`list`, `dict`, `set`, etc.) replace the attribute with a descriptor that will activate on first access and update the instance with a fresh copy of the mutable.

2. Provide the descriptor from (1) as a decorator to be used when writing the class.

I prefer (2) is it gives complete control to the class author, and simplifies those cases where the class-level mutable attribute *should* be shared amongst all the instances.

Here's the decorator-descriptor:

    class ReplaceMutable:
        def __init__(self, func):
            self.func = func
        def __call__(self):
            return self
        def __get__(self, instance, owner):
            if instance is None:
                return self
            result = self.func()
            setattr(instance, self.func.__name__, result)
            return result

and the test class:

    class Test:
        @ReplaceMutable
        def mutable():
            return list()

    t1 = Test()
    t2 = Test()
    t1.mutable.append('one')
    t2.mutable.append('two')
    print(t1.mutable)
    print(t2.mutable)

How it works:

Just like `property`, `ReplaceMutable` is a descriptor object with the same name as the attribute it is replacing. Unlike `property`, it does not define `__set__` nor `__delete__`, so when code tries to rebind the name (`mutable` in the test above) in the instance Python will allow it to do so. This is the same idea behind caching descriptors.

`ReplaceMutable` is decorating a function (with the name of the desired attribute) that simply returns whatever the instance level attribute should be initialized with (an empty `list` in the example above). So the first time the attribute is looked up on an instance it will not be found in the instance dictionary and Python will activate the descriptor; the descriptor then calls the function to retrieve the initial object/data/whatever, stores it in the instance, and then returns it. The next time that attribute is accessed *on that instance* it will be in the instance dictionary, and that is what will be used.
-----------------------------------

Thanks,
~Ethan~
--
http://mail.python.org/mailman/listinfo/python-list

Reply via email to