从父类控制子类方法

时间:2013-02-14 17:01:39

标签: python class python-2.7 parent-child

假设我有这段代码:

class Foo:
    def write(self, s=""):

        # Make sure that overwritten
        # 'write' method in child class
        # does what it's specified, and
        # then what comes next...

        print "-From Foo"

class Bar(Foo):
    def write(self, s=""):
        print s

baz = Bar()
baz.write("Hello, World!")

最后一次通话显然会自动输出你好的世界。我需要让它写“-From Foo”,但不修改Bar类,只需要Foo类。我已尝试使用__bases__和其他内容,但它不适用于我的目的。

4 个答案:

答案 0 :(得分:4)

我100%同意Lattyware:你不应该这样做。父类不应该“知道”子类或它们如何工作。

但我必须说可以使用一些__getattribute__魔法:

class Foo(object):
    def __getattribute__(self, attr):
        if attr != 'write':
            return super(Foo, self).__getattribute__(attr)
        meth = super(Foo, self).__getattribute__(attr)
        if meth.im_func is Foo.write.im_func:
            # subclass does not override the method
            return meth

        def assure_calls_base_class(*args, **kwargs):
            meth(*args, **kwargs)
            Foo.write(self, *args, **kwargs)
        return assure_calls_base_class

    def write(self, s=""):
        print "-From Foo"



class Bar(Foo):
    def write(self, s=""):
        print s

运行代码:

>>> b = Bar()
>>> b.write('Hello, World!')
Hello, World!
-From Foo

但是请注意,这只是一个黑客攻击,并且在使用一些继承时可能会中断,或者即使您从类中访问write

>>> Bar.write(b, 'Hello, World!')  #should be equivalent to b.write('Hello, World!')
Hello, World!

答案 1 :(得分:3)

在没有修改Bar()的情况下,没有(好的)方法可以做到这一点 - 您要做的是在super()内使用Bar(),这将允许您调用父方法。

如果您正在使用一个无法修改的类而不执行此操作,那么最好的解决方案是创建一个包装类,使用不好玩的类手动执行您想要的操作。 E.g:

class BarWrapper(Foo):
    def __init__(self, *args, **kwargs):
        self.bar = Bar(*args, **kwargs)

    def write(self, *args, **kwargs):
        super(BarWrapper, self).write(*args, **kwargs)
        self.bar.write(*args, **kwargs)

(当然,根据你的课程有多少,需要更多,并且在3.x中注意,你可以通过删除参数来使用更简单的super()语法。)

答案 2 :(得分:3)

这是使用元类魔法做到这一点的一种方法;恕我直言,它比其他方法更强大和灵活,它还处理无限呼叫(例如Bar.write(x, "hello"))和单一继承(见下面的Baz):

class ReverserMetaclass(type):
    def __new__(cls, name, bases, dct):
        """ This metaclass replaces methods of classes made from it
            with a version that first calls their base classes
        """
        # create a new namespace for the new class
        new_dct = {}
        for member_name, member in dct.items():
            # only decorate methods/callable in the new class
            if callable(member):
                member = cls.wrap(bases, member_name, member)
            new_dct[member_name] = member
        # construct the class
        return super(ReverserMetaclass, cls).__new__(cls, name, bases, new_dct)

        # instead of the above, you can also use something much simpler
        #     dct['read'] = cls.wrap(bases, 'read', dct['read'])
        #     return super(ReverserMetaclass, cls).__new__(cls, name, bases, dct)
        # if you have a specific method that you want to wrap and want to 
        # leave the rest alone

    @classmethod
    def wrap(cls, bases, name, method):
        """ this method calls methods in the bases before calling the method """
        def _method(*args, **kwargs):
            for base in bases:
                if hasattr(base, name):
                    getattr(base, name)(*args, **kwargs)
            # put this above the loop if you want to reverse the call order
            ret = method(*args, **kwargs)
            return ret
        return _method

示例控制台运行:

>>> class Foo(object):
...     __metaclass__ = ReverserMetaclass
...     def write(self, s=""):
...         # Make sure that overwritten
...         # 'write' method in child class
...         # does what it's specified, and
...         # then what comes next...
...         print "Write - From Foo", s
...     def read(self):
...         print "Read - From Foo"
...
>>> class Bar(Foo):
...     def write(self, s=""):
...         print "Write - from Bar", s
...     def read(self):
...         print "Read - From Bar"
...
>>> class Baz(Bar):
...     def write(self, s=""):
...         print "Write - from Baz", s
...
>>> x = Bar()
>>> x.write("hello")
Write - From Foo hello
Write - from Bar hello
>>> Bar.read(x)
Read - From Foo
Read - From Bar
>>>
>>> x = Baz()
>>> x.read()
Read - From Foo
Read - From Bar
>>> x.write("foo")
Write - From Foo foo
Write - from Bar foo
Write - from Baz foo

Python元类非常强大,但正如其他人所说,你真的不想经常做这种魔术。

答案 3 :(得分:1)

这是使用元类进行此操作的另一种方法。它使用__getattribute__()的一个重要优点是,访问或使用其他子类属性和方法不会产生额外的开销。如果定义了它的子类,它还支持单继承。

class Foo(object):
    class __metaclass__(type):
        def __new__(metaclass, classname, bases, classdict):
            clsobj = super(metaclass, metaclass).__new__(metaclass, classname, 
                                                         bases, classdict)
            if classname != 'Foo' and 'write' in classdict:  # subclass?
                def call_base_write_after(self, *args, **kwargs):
                    classdict['write'](self, *args, **kwargs)
                    Foo.write(self, *args, **kwargs)

                setattr(clsobj, 'write', call_base_write_after)  # replace method

            return clsobj

    def write(self, s=""):
        print "-From Foo"

class Bar(Foo):
    def write(self, s=""):
        print 'Bar:', s

class Baz(Bar):  # sub-subclass
    def write(self, s=""):
        print 'Baz:', s

Bar().write('test')
Baz().write('test')

输出:

Bar: test
-From Foo
Baz: test
-From Foo

如果您希望子类write()方法之后调用他们的基类的版本而不是根(Foo)类,只需更改硬编码:

    Foo.write(self, *args, **kwargs)

致电:

    super(clsobj, self).write(*args, **kwargs)

Foo.__new__()