views:

75

answers:

2

Source

from copy import deepcopy

class Field(object):
    def __init__(self):
        self.errors = []

class BaseForm(object):
    pass

class MetaForm(type):
    def __new__(cls, name, bases, attrs):
        attrs['fields'] = dict([(name, deepcopy(attrs.pop(name))) for name, obj in attrs.items() if isinstance(obj, Field)])
        return type.__new__(cls, name, bases, attrs)

class Form(BaseForm):
    __metaclass__ = MetaForm

class MyForm(Form):
    field1 = Field()

f1 = MyForm()
f1.fields['field1'].errors += ['error msg']

f2 = MyForm()
print f2.fields['field1'].errors

Output

['error msg']

Question

Why does it output that? I thought I cloned the errors list before modifying it, and that they shouldn't both refer to the same list?

+2  A: 

By setting the dict fields in the metaclass, you are creating a class attribute.

The __new__ method you defined is only run once -- on class creation.

Update

You should manipulate attrs in __new__ like you are, but name it something like _fields. Then create an __init__ method that performs a deepcopy into an attribute called fields.

Zach
Ah.. guess I had a fundamental misunderstanding of how `__new__` worked.
Mark
An `__init__` on which class? The `BaseForm`? Because `MetaForm.__init__` is only called once as well, isn't it?
Mark
`__init__` for the `Form` class
Zach
@Zach: Well... the `Form` class is deliberately left blank, so I'll stuff it in the base. Same diff. Thanks!
Mark
A: 

A more explicit solution:

from copy import deepcopy

class Field(object):
    def __init__(self):
        self.errors = []

class BaseForm(object):
    def __init__(self):
        self.fields = deepcopy(self.fields)

class MetaForm(type):
    def __new__(cls, name, bases, attrs):
        attrs['fields'] = dict([(name, attrs.pop(name)) for name, obj in attrs.items() if isinstance(obj, Field)])
        return type.__new__(cls, name, bases, attrs)

class Form(BaseForm):
    __metaclass__ = MetaForm

class MyForm(Form):
    field1 = Field()

f1 = MyForm()
f1.fields['field1'].errors += ['error msg']

f2 = MyForm()
print f2.fields['field1'].errors

Just moved the deepcopy into BaseForm.__init__ instead, which actually is called each time a MyForm is instantiated.

Mark