Passing new fields to an object

Steven D'Aprano steve+comp.lang.python at pearwood.info
Fri Jun 12 21:25:33 EDT 2015


On Fri, 12 Jun 2015 16:53:08 +0100, Paulo da Silva wrote:

> I would like to do something like this:
> 
> class C:
> 	def __init__(self,**parms):
> 	...
> 
> c=C(f1=1,f2=None)
> 
> I want to have, for the object
> 	self.f1=1
> 	self.f2=None
> 
> for an arbitrary number of parameters.
> 
> What is the best way to achieve this?


Others have suggested that you update the instance dict with the keyword 
parameters:

    self.__dict__.update(parms)


But I suggest that you should be very careful with this technique, 
because it can lead to surprising problems and bugs in your code. If your 
class has methods (and what sort of class doesn't have methods?), this 
will override them and lead to mysterious failures in your code:

instance = C(a=1, b=2, method=3)
# much later
result = instance.method(args)  # will raise exception


You should use SimpleNamespace, as Peter suggests, but *not* subclass it. 
If you subclass it and add methods:

class C(SimpleNamespace):
    def foo(self, arg):
        print("called foo")


then you risk overriding foo method, as above. If you don't add methods, 
there is no need to subclass.

Instead, use composition: your class should *contain* a SimpleNamespace, 
not *be* one:

class C:
    def __init__(self, **param):
        self.ns = SimpleNamespace(param)
    def __getattr__(self, attrname):
        return getattr(self.ns, attrname)
    def foo(self, arg):
        print("called foo")


instance = C(a=1, b=2, foo=3)
# later
instance.foo("x")  # prints "called foo"


The special method __getattr__ only runs if the attribute name is not 
found in the usual way, so the method foo will continue to be found and 
not be overridden by the param foo.



-- 
Steve



More information about the Python-list mailing list