aaa90210
aaa90210

Reputation: 12083

Python set attributes during object creation in __new__

When using __new__ to customize the creation of a metaclass, we can pass attributes to the type().__new__ method which will be set on the object before it is returned, e.g.

class Foo(type):    
    def __new__(cls, name, bases, attrs):
        attrs['customAttr'] = 'someVal' 
        return type.__new__(cls, name, bases, attrs)

So that:

>> Foo.__dict__
{'customeAttr': 'someVal', ...}

However I don't know how to do the same for a normal (non-meta) class, which causes a problem when using __setattr__:

class Bar(object):
    def __new__(cls, someVal):
        obj = object().__new__(cls) # cant pass custom attrs
        obj.customAttr = someVal # obj is already a Bar and invokes __setattr__
        return obj

    def __setattr__(*args): raise Exception('read-only class')

So that unfortunately:

>>> Bar(42)
...
Exception: read-only class

In the __new__ of Bar I get back a fully fledged class instance from object() and any attribute access goes through normal lookup rules, in this case invoking __setattr__. Metaclass Foo avoids this as type() will set attributes before returning the instance during low-level creation whereas object() will not.

Is there a way of passing attributes to object() or is another another type I can use as the instance returned from __new__ that does allow attributes to be set before it becomes a full class instance? I am not interesting in solutions like setting __class__ after instance creation.

Upvotes: 2

Views: 2858

Answers (1)

ShadowRanger
ShadowRanger

Reputation: 155323

You have to explictly bypass your own class's __setattr__ by calling the super or root object __setattr__. So you'd change:

obj.customAttr = someVal

to:

object.__setattr__(obj, 'customAttr', someVal)

A less general approach (doesn't apply to __slots__ based classes) is to directly assign to __dict__ using dict operations:

obj.__dict__['customAttr'] = someVal  # Equivalently: vars(obj)['customAttr'] = someVal

The first approach is what the newly __slots__-ed uuid.UUID now uses; before it became __slots__-ed, it used the second approach. In both cases this was needed because they used the same __setattr__ trick to make the type as immutable as possible (without going to the trouble of subclassing tuple, a la typing.NamedTuple).

Upvotes: 3

Related Questions