【问题标题】:How to execute BaseClass method before it gets overridden by DerivedClass method in Python如何在 Python 中的 DerivedClass 方法覆盖 BaseClass 方法之前执行它
【发布时间】:2014-05-21 08:42:16
【问题描述】:

我几乎可以肯定我想做的事情有一个合适的术语,但由于我不熟悉它,我将尝试明确地描述整个想法。所以我所拥有的是一组类,它们都继承自一个基类。所有类几乎完全由仅在每个类中相关的不同方法组成。但是,有几种方法具有相似的名称、通用功能和一些逻辑,但它们的实现仍然大不相同。所以我想知道的是是否可以在基类中创建一个方法,该方法将执行一些类似于所有方法的逻辑,但仍继续在类特定方法中执行。希望这是有道理的,但我会尝试给出一个我想要的基本示例。

所以考虑一个看起来像这样的基类

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

所以我想要实现的是基类access方法中的LOGIC_SHARED部分在调用anyApp类的access方法时执行before 执行LOGIC_SPECIFIC 部分(正如它所说)特定于所有派生类的每个access 方法。

如果这有什么不同的话,LOGIC_SHARED 主要由日志记录和维护任务组成。

希望这足够清楚并且这个想法是有意义的。

注意 1LOGIC_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)

因此,我使用了很多特定的类实例属性,但我不确定如何使用基类中的这些属性。

【问题讨论】:

  • 如果您展示这些“类特定参数”的示例,我可以向您展示如何使用我描述的相同技术将它们从问题中抽象出来。
  • @JonathonReinhart 非常感谢您的努力。非常感谢!我添加了 NOTE 3 以及共享逻辑部分的示例供您参考。如果我能提供更多信息,请告诉我。
  • 那么您是说self.application 在基类的不同实现中无效?基本上,您只需要在子类中以不同方式覆盖的方法后面隐藏特定于类的细节。
  • super(App1, self).access() 还不够吗?就像构造函数一样。
  • @JonathonReinhart self.application 未在基类中定义。它在派生类中定义。由于每个派生类代表一个应用程序,self.application 具有特定的应用程序名称。 self.link 也是如此。我希望我能理解你的问题。

标签: python oop inheritance


【解决方案1】:

当然,只要把具体的逻辑放在它自己的“私有”函数中,它可以被派生类覆盖,并将access留在Base中。

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

【讨论】:

  • 您好,感谢您的回答。但这是否意味着我必须为每个派生类调用 _specific_logic 函数而不是“访问”?
  • @EugeneS 不,这没有任何意义,因为那时access 根本不会参与其中。派生类继承了Base 的所有内容,包括access
  • 再次感谢。我现在明白了。但是,“访问”方法正在使用一些特定于类的参数。不确定如何从我的基类访问这些参数?
  • @EugeneS 与我展示特定函数的方式相同。要么在基类中为它们分配默认值,要么实现像我展示的那样工作的 getter 函数。
【解决方案2】:

最简单的方法是在子类的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 上阅读:How to make a chain of function decorators?

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

请记住,在 python 中,函数是第一类对象。这意味着您可以获取一个函数并将其作为参数传递给另一个函数。装饰器功能利用了这一点。装饰器函数将另一个函数作为参数(此处称为方法),然后创建一个新函数(此处称为装饰方法)来代替原始函数。

您的 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

我会发现这比向超类添加方法来捕获共享功能更优雅。

【讨论】:

  • 嗨。感谢您的回答和想法。听起来很有趣!但是,您能否详细解释一下装饰器函数的语法以及如何将其应用于函数。我还想知道是否可以将该装饰器功能存储在单独的文件中?再次感谢
  • 我已经更新了答案以解释装饰器(并指向其他几篇文章)。装饰器函数可以是任何将可调用(函数或方法)作为其唯一参数的普通函数,因此您可以在单独的文件中定义它。
  • +1 使用继承来进行这种常见的代码提取几乎总是一个坏主意,因为它不灵活并且会导致复杂而脆弱的类层次结构。
  • 就像我告诉我的儿子,父母不是为了给孩子做家务而发明的。
【解决方案3】:

如果我很好理解这条评论 (How to execute BaseClass method before it gets overridden by DerivedClass method in Python),您希望将附加参数传递给派生类中使用的父类

基于Jonathon Reinhart's answer

你可以这样做

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】:

    我个人更喜欢 Jonathon Reinhart 的回答,但鉴于您似乎想要更多选择,这里还有两个。我可能从不使用元类,尽管它很酷,但我可能会考虑使用装饰器的第二个。

    使用元类

    此方法为基类使用元类,它将强制首先调用基类的访问方法,而无需单独的私有函数,也无需显式调用super 或类似的东西。最终结果:没有额外的工作/代码进入继承类。

    另外,它的工作方式类似于 maaaagiiiiic </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'函数之前先调用它。

    【讨论】:

      【解决方案5】:

      希望这个简单的方法能有所帮助。

      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
      >>> 
      

      【讨论】:

      • 感谢您的意见。
      【解决方案6】:

      添加一个 combine 函数,我们可以将两个函数组合起来,然后依次执行,如下所示

      def combine(*fun):
          def new(*s):
              for i in fun:
                  i(*s)
          return new
      
      
      class base():
          def x(self,i):
              print 'i',i
      
      class derived(base):
          def x(self,i):
              print 'i*i',i*i
          x=combine(base.x,x)
      
      new_obj=derived():
      new_obj.x(3)
      

      输出波纹管

      i 3
      i*i 9
      

      它不必是单层层次结构,它可以有任意数量的层次或嵌套

      【讨论】:

        猜你喜欢
        • 2012-10-17
        • 1970-01-01
        • 1970-01-01
        • 2011-05-24
        • 1970-01-01
        • 2014-07-13
        • 1970-01-01
        • 2017-11-19
        • 2022-01-23
        相关资源
        最近更新 更多