Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python Method Inheritance

I have a parent class and two child class. The parent class is an abstract base class that has method combine that gets inherited by the child classes. But each child implements combine differently from a parameter perspective therefore each of their own methods take different number of parameters. In Python, when a child inherits a method and requires re-implementing it, that newly re-implemented method must match parameter by parameter. Is there a way around this? I.e. the inherited method can have dynamic parameter composition?

like image 319
user1234440 Avatar asked Mar 19 '23 00:03

user1234440


2 Answers

This code demonstrates that signature of overridden method can easily change.

class Parent(object):
    def foo(self, number):
        for _ in range(number):
            print "Hello from parent"



class Child(Parent):
    def foo(self, number, greeting):
        for _ in range(number):
            print greeting


class GrandChild(Child):
    def foo(self):
        super(GrandChild,self).foo(1, "hey")


p = Parent()
p.foo(3)

c = Child()
c.foo(2, "Hi")

g = GrandChild()
g.foo()
like image 164
Maria Zverina Avatar answered Mar 23 '23 10:03

Maria Zverina


As the other answer demonstrates for plain classes, the signature of an overridden inherited method can be different in the child than in the parent.

The same is true even if the parent is an abstract base class:

import abc

class Foo:
    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    def bar(self, x, y):
        return x + y

class ChildFoo(Foo):
    def bar(self, x):
        return super(self.__class__, self).bar(x, 3)

class DumbFoo(Foo):
    def bar(self):
        return "derp derp derp"

cf = ChildFoo()
print cf.bar(5)

df = DumbFoo()
print df.bar()

Inappropriately complicated detour

It is an interesting exercise in Python metaclasses to try to restrict the ability to override methods, such that their argument signature must match that of the base class. Here is an attempt.

Note: I'm not endorsing this as a good engineering idea, and I did not spend time tying up loose ends so there are likely little caveats about the code below that could make it more efficient or something.

import types
import inspect

def strict(func):
    """Add some info for functions having strict signature.
    """
    arg_sig = inspect.getargspec(func)
    func.is_strict = True
    func.arg_signature = arg_sig
    return func


class StrictSignature(type):
    def __new__(cls, name, bases, attrs):
        func_types = (types.MethodType,) # include types.FunctionType?

        # Check each attribute in the class being created.
        for attr_name, attr_value in attrs.iteritems():
            if isinstance(attr_value, func_types):

                # Check every base for @strict functions.
                for base in bases:
                    base_attr = base.__dict__.get(attr_name)
                    base_attr_is_function = isinstance(base_attr, func_types)
                    base_attr_is_strict = hasattr(base_attr, "is_strict")

                    # Assert that inspected signatures match.
                    if base_attr_is_function and base_attr_is_strict:
                        assert (inspect.getargspec(attr_value) == 
                                base_attr.arg_signature)

        # If everything passed, create the class.
        return super(StrictSignature, cls).__new__(cls, name, bases, attrs)



# Make a base class to try out strictness
class Base:
    __metaclass__ = StrictSignature

    @strict
    def foo(self, a, b, c="blah"):
        return a + b + len(c)

    def bar(self, x, y, z):
        return x


#####
# Now try to make some classes inheriting from Base.
#####
class GoodChild(Base):

    # Was declared strict, better match the signature.
    def foo(self, a, b, c="blah"):
        return c

    # Was never declared as strict, so no rules!
    def bar(im_a_little, teapot):
        return teapot/2


# These below can't even be created. Uncomment and try to run the file
# and see. It's not just that you can't instantiate them, you can't
# even get the *class object* defined at class creation time.
#
#class WrongChild(Base):
#    def foo(self, a):
#        return super(self.__class__, self).foo(a, 5)
#
#class BadChild(Base):
#    def foo(self, a, b, c="halb"):
#        return super(self.__class__, self).foo(a, b, c)

Note, like with most "strict" or "private" type ideas in Python, that you are still free to monkey-patch functions onto even a "good class" and those monkey-patched functions don't have to satisfy the signature constraint.

# Instance level
gc = GoodChild()
gc.foo = lambda self=gc: "Haha, I changed the signature!"

# Class level
GoodChild.foo = lambda self: "Haha, I changed the signature!"

and even if you add more complexity to the meta class that checks whenever any method type attributes are updated in the class's __dict__ and keeps making the assert statement when the class is modified, you can still use type.__setattr__ to bypass customized behavior and set an attribute anyway.

In these cases, I imagine Jeff Goldblum as Ian Malcolm from Jurassic Park, looking at you blankly and saying "Consenting adults, uhh, find a way.."

like image 42
ely Avatar answered Mar 23 '23 12:03

ely