scraplesh
scraplesh

Reputation: 1376

Playing with object creation

I have two classes with method foo:

Foo = type('Foo', (object,), {'foo': lambda s: 'Foo method'})
Bar = type('Bar', (object,), {'foo': lambda s: 'Bar method'})

And I have some other class which I need to be subclassed of one of the above classes according to the parameter.

My Solution:

class Subject(object):
    def __new__(cls, key):
        base = (Foo if key else Bar)

        name = cls.__name__ + base.__name__
        dict_ = dict(cls.__dict__.items() + base.__dict__.items())
        bases = (base, cls)

        t = type(name, bases, dict_)
        return base.__new__(t)

    def bar(self):
        return 'Subject method'

Testing:

print(Subject(True).foo(), Subject(True).bar())
print(Subject(False).foo(), Subject(False).bar())

Output:

('Foo method', 'Subject method')
('Bar method', 'Subject method')

Is this solution safe enough? Or I need something more to know? Is there more pythonic way to do this kind of unregular stuff?

Upvotes: 1

Views: 115

Answers (3)

Cito
Cito

Reputation: 5613

If you avoid metaclasses (magic) the code will be more readable (and thus more pythonic). Go with the approaches suggested by Chris and Raymond, i.e.

use composition:

class Subject(object):
    def __init__(self, key):
        self.foo = (Foo if key else Bar)().foo
    def bar(self):
        return 'Subject method'

or use a factory function:

def Subject(key):
    class Subject(Foo if key else Bar):
        def bar(self):
            return 'Subject method'
    return Subject()

Upvotes: 1

Andrew Clark
Andrew Clark

Reputation: 208475

Here is an example of some unexpected behavior that could be caused by your current method, even if Subject overrides something from Foo or Bar, the Foo or Bar version of the method will be called:

class Subject(object):
    # all of your current methods as they were above
    def foo(self):
        return 'Subject foo'

>>> Subject(False).foo()
'Bar method'

Even though you could fix this by putting base.__dict__.items() before cls.__dict__.items() on the line where you create dict_, I would suggest moving away from this approach entirely, possibly using Chris Lutz' comment or Raymond's answer.

If all you are interested in doing is having a dynamic method, I would suggest the following. Create private versions of the methods inside of the Subject class, and then assign whichever method you want to use in Subject.__init__():

class Subject(object):
    def __init__(self, key):
        self.foo = self._Foo_foo if key else self._Bar_foo
    def _Foo_foo(self):
        return 'Foo method'
    def _Bar_foo(self):
        return 'Bar method'

>>> Subject(True).foo()
'Foo method'
>>> Subject(False).foo()
'Bar method'

Upvotes: 0

Raymond Hettinger
Raymond Hettinger

Reputation: 226336

I think most people would see the above code and recommend using composition rather than inheritance. Subject would define a foo method that dispatched to the correct class based on the boolean value.

Alternatively, you could use a factory function to create either a Foo or Bar as needed.

def subject(selector):
     'Factory function that chooses between Foo and Bar'
     return Foo() if selector else Bar()

If needed, make both Foo and Bar inherit from a common class so that the factory function always returns an instance of a subclass of the common class.

Upvotes: 2

Related Questions