从父类控制子类方法

4

假设我有以下代码:

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!")

最后一个调用显然只输出了“hello world”。我需要让它同时写出“-来自Foo”,但不能修改Bar类,只能修改Foo类。我尝试使用__bases__ 和其他方法,但都不能满足我的需求。


如果这是 Python 2.x,请确保继承自 object 以获取新式类,其具有更广泛的神秘能力。 - Lie Ryan
4个回答

4

我完全同意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

请注意,这只是一个hack,当使用继承或者甚至从类中访问write时,可能会出现问题:

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

1
此外,加1是因为它可以实现OP想要的功能并提到了一些注意事项...尽管它忽略了一个事实,即它会在某种程度上减慢对Bar对象的所有属性/方法的访问速度。 - martineau

4

以下是使用元类魔法的一种方法;在我看来,它比其他方法更加健壮和灵活,还可以处理未限定调用(例如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元类非常强大,尽管有些人说,你真的不想经常使用这种魔法。


我认为这是"前进的道路"(如果我们想做OP所要求的事情),但对我来说,__getattribute__比元类更容易理解。 - Bakuriu

3

如果不修改Bar(),没有(好的)方法可以完成这个操作 - 你需要在Bar()内使用super(),这将允许你调用父类方法。

如果你正在使用一个无法修改且未执行此操作的类,则最好的解决方案是创建一个包装类,并手动执行你想要的操作,使用不友好的类。例如:

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()语法。


由于OP显然正在使用Python 2.x,因此我认为你应该用这些术语给出你的答案,而不仅仅是在最后提到它需要修改才能在那个版本中运行。 - martineau
1
@martineau,实际上我没有检查过OP使用的版本,已经编辑过了。 - Gareth Latty

2

以下是使用元类的另一种方法。它与使用__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__()中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接