如何在Python中执行BaseClass方法,以在DerivedClass方法覆盖之前调用它

1 投票
6 回答
1175 浏览
提问于 2025-04-18 07:10

我几乎可以肯定我想做的事情有一个专业术语,但因为我不太熟悉,所以我会尽量详细描述我的想法。我有一组类,它们都继承自一个基类。这些类几乎都是由各自特有的方法组成,只有少数几个方法在名称、功能和一些逻辑上是相似的,但它们的具体实现还是有很大不同。所以我想知道,是否可以在基类中创建一个方法,执行一些所有方法都相似的逻辑,然后再继续执行各个类特有的方法。希望这样说能让你明白,我会给出一个基本的例子来说明我的想法。

首先,想象一个基类,它大概是这样的:

class App(object):

    def __init__(self, testName):
        self.localLog = logging.getLogger(testName)

    def access(self):
        LOGIC_SHARED

然后是一个派生类的例子:

class App1(App):

    def __init__(self, testName):
        . . .   
        super(App1, self).__init__(testName)

    def access(self):
        LOGIC_SPECIFIC

我希望实现的效果是,当调用任何App类的access方法时,能够先执行基类中的access方法里的LOGIC_SHARED部分,然后再执行各个派生类中access方法的LOGIC_SPECIFIC部分(顾名思义,这部分是特定于每个类的)。

如果这有什么不同的话,LOGIC_SHARED部分主要包含一些日志记录和维护任务。

希望这样说够清楚,能让你理解我的想法。

注意 1:在LOGIC_SHARED部分中使用了一些特定于类的参数。

注意 2:重要的是要仅使用Python内置的函数和模块来实现这个行为。

注意 3LOGIC_SHARED部分大概是这样的:

try:
    self.localLog.info("Checking the actual link for %s", self.application)
    self.link = self.checkLink(self.application)
    self.localLog.info("Actual link found!: %s", self.link)
except:
    self.localLog.info("No links found. Going to use the default link: %s", self.link)

所以,我有很多特定类的实例属性,但我不太确定如何从基类中使用这些属性。

6 个回答

1

我个人更喜欢Jonathon Reinhart的回答,但既然你想要更多选择,这里还有两个方法。我可能永远不会使用元类的方法,尽管它很酷,但我可能会考虑第二种使用装饰器的方法。

使用元类

这个方法使用了一个元类来处理基类,这样可以确保基类的访问方法会优先被调用,而不需要单独的私有函数,也不需要明确调用super或其他类似的东西。最终的结果是:在继承类中没有额外的工作或代码。

而且,它的效果就像魔法一样 </spongebob>

下面是实现这个功能的代码。在这里 http://dbgr.cc/W 你可以实时跟踪代码,看看它是怎么工作的:

#!/usr/bin/env python

class ForceBaseClassFirst(type):
    def __new__(cls, name, bases, attrs):
        """
        """
        print("Creating class '%s'" % name)

        def wrap_function(fn_name, base_fn, other_fn):
            def new_fn(*args, **kwargs):
                print("calling base '%s' function" % fn_name)
                base_fn(*args, **kwargs)
                print("calling other '%s' function" % fn_name)
                other_fn(*args, **kwargs)

            new_fn.__name__ = "wrapped_%s" % fn_name
            return new_fn

        if name != "BaseClass":
            print("setting attrs['access'] to wrapped function")
            attrs["access"] = wrap_function(
                "access",
                getattr(bases[0], "access", lambda: None),
                attrs.setdefault("access", lambda: None)
            )

        return type.__new__(cls, name, bases, attrs)

class BaseClass(object):
    __metaclass__ = ForceBaseClassFirst

    def access(self):
        print("in BaseClass access function")


class OtherClass(BaseClass):
    def access(self):
        print("in OtherClass access function")

print("OtherClass attributes:")
for k,v in OtherClass.__dict__.iteritems():
    print("%15s: %r" % (k, v))

o = OtherClass()

print("Calling access on OtherClass instance") 
print("-------------------------------------")
o.access()

这个代码使用元类来替换OtherClass的访问函数,变成一个先调用BaseClass的访问函数,再调用OtherClass的访问函数的函数。想了解元类的最佳解释可以查看这里 https://stackoverflow.com/a/6581949

逐步跟踪代码应该能帮助你理解事情的顺序。

使用装饰器

这个功能也可以很容易地放入一个装饰器中,如下所示。同样,你可以在这里找到一个可逐步调试和运行的版本 http://dbgr.cc/0

#!/usr/bin/env python

def superfy(some_func):
    def wrapped(self, *args, **kwargs):
        # NOTE might need to be changed when dealing with
        # multiple inheritance
        base_fn = getattr(self.__class__.__bases__[0], some_func.__name__, lambda *args, **kwargs: None)

        # bind the parent class' function and call it
        base_fn.__get__(self, self.__class__)(*args, **kwargs)

        # call the child class' function
        some_func(self, *args, **kwargs)

    wrapped.__name__ = "superfy(%s)" % some_func.__name__
    return wrapped

class BaseClass(object):
    def access(self):
        print("in BaseClass access function")


class OtherClass(BaseClass):
    @superfy
    def access(self):
        print("in OtherClass access function")

print("OtherClass attributes")
print("----------------------")
for k,v in OtherClass.__dict__.iteritems():
    print("%15s: %r" % (k, v))
print("")

o = OtherClass()

print("Calling access on OtherClass instance") 
print("-------------------------------------")
o.access()

上面的装饰器会先获取BaseClass同名的函数,然后在调用OtherClass的函数之前先调用它。

1

希望这个简单的方法能帮到你。

class App:

    def __init__(self, testName):

        self.localLog = logging.getLogger(testName)

        self.application = None
        self.link = None

    def access(self):
        print('There is something BaseClass must do')
        print('The application is ', self.application)
        print('The link is ', self.link)


class App1(App):

    def __init__(self, testName):

        # ...
        super(App1, self).__init__(testName)

    def access(self):
        self.application = 'Application created by App1'
        self.link = 'Link created by App1'

        super(App1, self).access()

        print('There is something App1 must do')


class App2(App):

    def __init__(self, testName):

        # ...
        super(App2, self).__init__(testName)

    def access(self):
        self.application = 'Application created by App2'
        self.link = 'Link created by App2'

        super(App2, self).access()

        print('There is something App2 must do')

这是测试的结果:

>>> 
>>> app = App('Baseclass')
>>> app.access()
There is something BaseClass must do
The application is  None
The link is  None
>>> app1 = App1('App1 test')
>>> app1.access()
There is something BaseClass must do
The application is  Application created by App1
The link is  Link created by App1
There is something App1 must do
>>> app2 = App2('App2 text')
>>> app2.access()
There is something BaseClass must do
The application is  Application created by App2
The link is  Link created by App2
There is something App2 must do
>>> 
1

如果我理解得没错,这条评论的意思是,你想在派生类中使用传递给父类的额外参数。

这个内容是基于Jonathon Reinhart的回答

下面是你可以这样做的方式:

class Base(object):
    def access(self,
                    param1 ,param2, #first common parameters
                    *args,          #second positional parameters
                    **kwargs        #third keyword arguments
               ):
        # Shared logic 1
        self._specific_logic(param1, param2, *args, **kwargs)
        # Shared logic 2

    def _specific_logic(self, param1, param2, *args, **kwargs):
        # Nothing special to do in the base class
        pass

        # Or you could even raise an exception
        raise Exception('Called access on Base class instance')


class DerivedA(Base):
    # overrides Base implementation
    def _specific_logic(self, param1, param2, param3):
        # DerivedA specific logic

class DerivedB(Base):
    # overrides Base implementation
    def _specific_logic(self, param1, param2, param4):
        # DerivedB specific logic

def test():
    x = Base()

    a = DerivedA()
    a.access("param1", "param2", "param3")           # Shared logic 1
                                                     # Derived A specific logic
                                                     # Shared logic 2

    b = DerivedB()
    b.access("param1", "param2", param4="param4")   # Shared logic 1
                                                    # Derived B specific logic
                                                    # Shared logic 2
4

你想要做的事情最简单的方法就是在子类的 access 方法里直接调用父类的 access 方法。

class App(object):
    def __init__(self, testName):
        self.localLog = logging.getLogger(testName)

    def access(self):
        LOGIC_SHARED

class App1(App):
    def __init__(self, testName):
        super(App1, self).__init__(testName)

    def access(self):
        App.access(self)
        # or use super
        super(App1, self).access()

不过,你们共享的功能主要是记录日志和维护。除非有特别的理由把这些功能放在父类里,否则你可以考虑把这些共享的功能重构成一个装饰器函数。如果你想在类里的多个方法中重复使用类似的日志记录和维护功能,这样做特别有用。

你可以在这里了解更多关于函数装饰器的内容:http://www.artima.com/weblogs/viewpost.jsp?thread=240808,或者在Stack Overflow上查看这个问题:如何创建一系列函数装饰器?

def decorated(method):
    def decorated_method(self, *args, **kwargs):
        LOGIC_SHARED
        method(self, *args, **kwargs)
    return decorated_method

记住,在Python中,函数是第一类对象。这意味着你可以把一个函数作为参数传递给另一个函数。装饰器函数就是利用这一点。装饰器函数接受另一个函数作为参数(这里叫做方法),然后创建一个新的函数(这里叫做decorated_method),这个新函数替代了原来的函数。

你的App1类看起来会是这样的:

class App1(App):
    @logged
    def access(self):
        LOGIC_SPECIFIC

这实际上是这个的简写:

class App1(App):
    def access(self):
        LOGIC_SPECIFIC

decorated_access = logged(App.access)
App.access = decorated_access

我觉得这样比在超类中添加方法来捕获共享功能要优雅得多。

11

当然,可以把具体的逻辑放在一个“私有”的函数里,这样派生类就可以重写这个函数,而把access留在基类中。

class Base(object):
    def access(self):
        # Shared logic 1
        self._specific_logic()
        # Shared logic 2

    def _specific_logic(self):
        # Nothing special to do in the base class
        pass

        # Or you could even raise an exception
        raise Exception('Called access on Base class instance')


class DerivedA(Base):
    # overrides Base implementation
    def _specific_logic(self):
        # DerivedA specific logic

class DerivedB(Base):
    # overrides Base implementation
    def _specific_logic(self):
        # DerivedB specific logic

def test():
    x = Base()
    x.access()           # Shared logic 1
                         # Shared logic 2

    a = DerivedA()
    a.access()           # Shared logic 1
                         # Derived A specific logic
                         # Shared logic 2

    b = DerivedB()
    b.access()           # Shared logic 1
                         # Derived B specific logic
                         # Shared logic 2

撰写回答