linkyndy
linkyndy

Reputation: 17900

Do additional operations on object instantation, based on where the object has been instantiated

I have an API class (it will be extended and used by other classes):

class A(object):
    def __init__(self, **kwargs):
        self.kwargs = kwargs

    @classmethod
    def create(cls, **kwargs):
        return cls(**kwargs)

It is extended by:

class B(A):
    pass

Now, this is what I want: if I instantiate class B like B(arg1=1, arg2=2) I would like to do some additional operations when initializing it, like validate the kwargs arguments (and this code should reside in class A, not in B). Then, if I do B.create(arg1=1, arg2=2), that validation should not occur.

In short, I would like to do extra operations when initializing an object only from the outside of the class it was defined; initalizing an object from a classmethod inside its class should not trigger any extra operation.

Upvotes: 1

Views: 55

Answers (2)

Will
Will

Reputation: 1541

If your "extra operations" are in the constructor then I'm afraid they'll always get run, regardless of whether you instantiate directly or through a factory. You could consider only allowing object creation through a set of factory methods (some with validation and some without).

class A(object):
    def __init__(self, **kwargs):
        self.kwargs = kwargs
        #no extra stuff

@classmethod
def create_with_extra_stuff(cls, **kwargs):
    c = cls(**kwargs)
    c.extra_stuff()
    return c

@classmethod
def create_withuot_extra_stuff(cls, **kwargs):
    return cls(**kwargs)

@classmethod
def create(cls, with_extra_stuff = False, **kwargs):
    if with_extra_stuff:
        return cls.create_with_extra_stuff(**kwargs)
    else:
        return cls.create_without_extra_stuff(**kwargs)

Of course, I don't know your full use case but factory patterns are pretty much designed for this sort of thing.

This section was added after the comments were made:

class A(object):
    def __init__(self, do_extra_stuff = True, **kwargs):
        if do_extra_stuff:
            self.do_extra_stuff(**kwargs)
        self.kwargs = kwargs

@classmethod
def create(cls, **kwargs):
    return cls(do_extra_stuff = False, **kwargs)

Further edit showing alternative:

class A(object):
    def __init__(self, **kwargs):
        #lightweight constructor
        self.kwargs = kwargs

    def validate(self):
        #Does not alter class variables. That would be bad in this case
        #Do stuff with self.kwargs


@classmethod
def create(cls, **kwargs):
    return cls(**kwargs)

main():
    a = A({...})
    a.validate()

    b = A.create({...})
    #b.validate() not called

The point is that the constructor will be called on construction. The trick then is to decide what additional stuff needs to be selective called.

Upvotes: 1

ndpu
ndpu

Reputation: 22561

You can use additional argument to distinguish method of class initialisation:

class A(object):
    def __init__(self, __a_validate=True, **kwargs):
        self.kwargs = kwargs
        if __a_validate:
            print 'do validation'

    @classmethod
    def create(cls, **kwargs):
        return cls(__a_validate=False, **kwargs)

Demo:

>>> B.create(arg1=1, arg2=2)
<__main__.B object at 0x9b82f4c>

>>> B(arg1=1, arg2=2)
do validation
<__main__.B object at 0x9b7bbcc>

update for comments:

This is another solution. You can modify class attribute in create method, for example set validate function to None, call constructor and then set validate back to original state:

class A(object):
    def __init__(self, **kwargs):
        self.kwargs = kwargs
        if self.validate:
            self.validate(kwargs)

    def validate(self, kwargs):
        print 'do validation'

    @classmethod
    def create(cls, **kwargs):
        tmp = cls.validate
        cls.validate = None
        instance = cls(**kwargs)
        cls.validate = tmp
        return instance

Upvotes: 1

Related Questions