14

I want to expand the class Foo by the class Bar, the issue that I have is that I can't expand it in the usual way (class Foo(Bar)) because the class Bar is somewhat dynamically generated.

I made this small example to illustrate my desired outcome:

class Bar:
    def super_cool_function():
        print("Cool")

class Foo:
    def __init__(self, another_class):
        # I want to extend Foo by another_class

# Desired result
foobar = Foo(Bar)
foobar.super_cool_function()

Again this is not what I'm looking for:

class Foo(Bar):
    pass

foobar = Foo()
foobar.super_cool_function()
3
  • 2
    At what point is Bar known? class Foo(X): works just fine even if X is a variable, not the literal name of the parent class. Commented Aug 12, 2018 at 21:12
  • I should point out that, in order for super_cool_function to be callable by an instance of Foo, it'd need the self parameter (def super_cool_function(self):). I'm not going to edit your question to correct it, because that would invalidate abarnert's answer. Commented Aug 13, 2018 at 3:16
  • You can also consider the strategy pattern: sourcemaking.com/design_patterns/strategy/python/1 Commented Aug 30, 2018 at 23:50

2 Answers 2

25

TL;DR: Yes, using python closures

"The class Bar is somewhat dynamically generated" That's fine... as long as it follows the blueprint (of a class that should be extended by Foo), you can leverage python closures here. Dynamically create a new class by creating it inside, and returning it from a function.

def get_class(superclass):
    class Foo(superclass):
        def __init__(self, ...):
           ...

    return Foo

DynamicFoo = get_class(Bar)
myobj = DynamicFoo()

This is a common pattern you'll see in python - leveraging closures to dynamically create callbacks and classes.


The answer above assumes that Bar is correctly defined, when it in fact is not. The super_cool_function is missing a self parameter. Instance methods are always called with the first parameter (the instance itself) automatically being passed in as the first attribute.

So, the correct definition for Bar would be:

class Bar:
   def super_cool_function(self):
       print("Cool")

Now, defining get_class with the simplest definition of the inner class Foo:

def get_class(superclass):
    class Foo(superclass):
        pass

    return Foo

DynamicFoo = get_class(Bar)
myobj = DynamicFoo()
myobj.super_cool_function()
# Cool
Sign up to request clarification or add additional context in comments.

Comments

4

Your desired use is a little strange:

foobar = Foo(Bar)

You're constructing a Foo instance by handing it the Bar class object, and expecting to get back something that acts like a Bar instance. Normally, a proxy class is designed to take an object to proxy to, or look on up somewhere, not just construct one with no arguments.

But, other than that oddness, which just means an __init__ method that constructs the object, this is just a bog-standard proxy class. So:

class Foo:
    def __init__(self, cls):
        self._inst = cls()
    def __getattr__(self, name):
        return getattr(self._inst, name)
    def __setattr__(self, name, value):
        if name in {'_inst'}:
            super().__setattr__(name, value)
        else:
            setattr(self._inst, name, value)
    def __delattr__(self, name):
        delattr(self._inst, name)

Of course you still won't be able to call that super_cool_function on a foobar any more than you could on a Bar instance, because it's defined as a method and doesn't have a self parameter. But you'll get the same error from the Foo instance that you would have gotten from a Bar instance:

>>> foobar.super_cool_function
<bound method Bar.super_cool_function of <__main__.Bar object at 0x129f95080>>
>>> foobar.super_cool_function()
TypeError: super_cool_function() takes 0 positional arguments but 1 was 

Any reason why you used object.__setattr__ instead of super().__setattr__?
@Aran-Fey Probably just habit left over from Guido's original Python 2.2 examples or something, and I can't see any good reason not to use super, so… edited.

Your Answer

Draft saved
Draft discarded

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.