Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to dynamically inherit from a class that is only known at runtime in python?

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()
like image 525
Ivy F. Avatar asked Aug 12 '18 21:08

Ivy F.


People also ask

Can runtime change inheritance?

Absolutely. Options include, but aren't limited to, these: Implementing an interpreter where the programmer can define types.

Is it possible that a class inherits from multiple parent classes in Python?

In Python a class can inherit from more than one class. If a class inherits, it has the methods and variables from the parent classes. In essence, it's called multiple inheritance because a class can inherit from multiple classes.

How do you check if a class inherits from another class Python?

Python issubclass() is built-in function used to check if a class is a subclass of another class or not. This function returns True if the given class is the subclass of given class else it returns False . Return Type: True if object is subclass of a class, or any element of the tuple, otherwise False.

How do you prevent a class from being inherited in Python?

The way to "avoid" inheritance here would be to rename _private_var and make it a class-private name. i.e. __private_var . If you do this, running your code will cause an AttributeError: 'Child' object has no attribute '_Parent__private_var' (note the _Parent prefix automatically added).


Video Answer


2 Answers

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
like image 142
cs95 Avatar answered Oct 19 '22 03:10

cs95


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 
like image 43
abarnert Avatar answered Oct 19 '22 04:10

abarnert