简体   繁体   中英

Execute parent class method without calling super()

We have the following class structure:

class NamePrinter():
    def phone():
        print(self.name)

    def email():
        print(self.name)


class PhoneCaller(NamePrinter):
    def __init__(self, name, number, mail):
        self.name = name
        self.number = number
        self.mail = mail

    def phone(self):
        # here, NamePrinter.phone() should be executed
        compose_number(self.number)

    def email(self):
        # here, NamePrinter.email() should be executed
        compose_mail(self.mail)

I want NamePrinter.phone() to be executed when PhoneCaller.phone() is called, without having to mention super.phone() in PhoneCaller.

The idea is that the only modification to be applied to PhoneCaller in order for it to execute NamePrinter's behaviour when PhoneCaller.phone is executed, is that PhoneCaller inherits from parent, and nothing more. In particular, no need to modify any individual PhoneCaller method.

To put it simpler:

  • PhoneCaller inherits from NamePrinter => name is printed before composing number
  • PhoneCaller doesn't inherit from NamePrinter => name is not printed
  • No need to mess around into PhoneCaller.phone

Is this possible?

Yes, it is possible at least with metaclass:

def new_attr(attr_name, attr):
    name_printer_attr = getattr(NamePrinter, attr_name)

    def _new_attr(self, *args, **kwargs):
        name_printer_attr(self)
        return attr(self, *args, **kwargs)

    return _new_attr


class Meta(type):
    def __new__(cls, name, bases, attrs):
        if name == 'NamePrinter':
            cls.attrs = attrs
        else:
            for attr_name, attr in attrs.items():
                if callable(attr) and attr_name in cls.attrs:
                    attrs[attr_name] = new_attr(attr_name, attr)
        return type.__new__(cls, name, bases, attrs)


class NamePrinter(metaclass=Meta):
    def phone(self):
        print('NamePrinter phone')


class PhoneCaller1:
    def phone(self):
        print('PhoneCaller1 phone')


class PhoneCaller2(NamePrinter):
    def phone(self):
        print('PhoneCaller2 phone')


p1 = PhoneCaller1()
p1.phone()  # will print only "PhoneCaller1 phone"
p2 = PhoneCaller2()
p2.phone()  # will print "NamePrinter phone" and "PhoneCaller2 phone" on next line

And there is another solution with decorator. It saves you from misusing inheritance and is more clear and flexible (IMHO):

def new_attr(attr_name, attr, from_cls):
    from_cls_attr = getattr(from_cls, attr_name)

    def _new_attr(self, *args, **kwargs):
        from_cls_attr(self)
        return attr(self, *args, **kwargs)

    return _new_attr


def use_methods(from_cls):
    dir_from_cls = dir(from_cls)
    def modify(cls):
        for attr_name in dir(cls):
            if not attr_name.startswith('__') and attr_name in dir_from_cls:
                attr = getattr(cls, attr_name)
                if callable(attr):
                    setattr(cls, attr_name, new_attr(attr_name, attr, from_cls))
        return cls
    return modify


class NamePrinter:
    def phone(self):
        print('NamePrinter phone')


class PhoneCaller1:
    def phone(self):
        print('PhoneCaller1 phone')


@use_methods(NamePrinter)
class PhoneCaller2:
    def phone(self):
        print('PhoneCaller2 phone')


p1 = PhoneCaller1()
p1.phone()
p2 = PhoneCaller2()
p2.phone()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM