简体   繁体   中英

How to override a method used by a 3rd party library

This would be the layout

some_function.py

def some_function():
    print("some_function")

some_library.py

from some_function import some_function

class A:
    def xxx(self):
        some_function()

main.py

from some_library import A
from some_function import some_function

def new_some_function():
    print("new_some_function")

if __name__ == '__main__':
    some_function = new_some_function
    a = A()
    a.xxx()

In the class A , the method xxx , calls some_function , so is it possible to override it with something else, without re-implementing the entire class?

You provide very little information about your use case here. As one of the comments points out, this might be a case for inheritance. If you are in a testing context, you may not want to use inheritance though, but you might rather want to use a mock-object.

Here is the inheritance version:

from some_library import A

def new_some_function():
    print("new_some_function")

class B(A):
    def xxx(self):
        new_some_function()

if __name__ == '__main__':
    a = B()
    a.xxx()

Note, how class B derives from class A through the class B(A) statement. This way, class B inherits all functionality from A and the definition of class B only consists of the parts where B differs from A . In your example, that is the fact that the xxx method should call new_some_function instead of some_function .

Here is the mock version:

from unittest import mock
from some_library import A

def new_some_function():
    print("new_some_function")

if __name__ == '__main__':
    with mock.patch('some_library.some_function') as mock_some_function:
        mock_some_function.side_effect = new_some_function
        a = A()
        a.xxx()

As mentioned above this approach is mostly useful if you are in a testing context and if some_function does something costly and/or unpredictable. In order to test code that involves a call to some_function , you may temporarily want to replace some_function by something else, that is cheap to call and behaves in a predictable way. In fact, for this scenario, replacing some_function by new_some_function might even be more than what is actually needed. Maybe, you just want an empty hull that can be called and that always returns the same value (instead of the side_effect line, you can specify a constant .return_value in the above code example). One of the key functionalities of mock objects is that you can later check if that function has been called. If testing is your use case, I would very much recommend looking at the documentation of the python mock module.

Note that the example uses the mock.patch context manager. This means that within the managed context (ie the block inside the with -statement) some_library.some_function is replaced by a mock object, but once you leave the managed context, the original functionality is put back in place.

I think you are looking for monkey patching (means changing classes/modules dynamically while running). This way you don't need to overwrite the class A and use inheritance as suggested by other comments - you said you don't want that, so try this solution:

import some_class  # import like this or will not work (cos of namespaces)

def new_some_function():
   print("new_some_function")

if __name__ == '__main__':
    # Import and use like this, other ways of import will not work.
    # Because other way imports method to your namespace and then change it in your namespace,
    # but you need to change it in the original namespace
    some_class.some_function = new_some_function

That way replace the original method and even other classes will use it then. Be careful, if the original method is a class/instance method, you need to create new function with proper params, like this:

def new_some_function(self):
    # for instance methods, you may add other args, but 'self' is important

def new_some_function(cls):
    # for class methods, you may add other args, but 'cls' is important

You may just create another class and override the method you need.
Just as an example:

class myInt(int):
    def __pow__(self, x):
        return 0

a = myInt(10)
a+10 # 20
a**2 # 0

In this case a is an int and has access to all the method of the int class, but will use the __pow__ method I've defined.

What you need is inheritance, You can subclass a class and with super method you can inherit all the parent class functions. If you want to override parent class functions, you just need to provide a different implementation by the same name in child class.
from some_library import A from some_function import some_function

 def new_some_function():
     print("new_some_function")

class B(A):
    def __init__(*args, **kwargs):
       super().__init__(self)
       pass
    def xxx(self):
       new_some_function()


if __name__ == '__main__':
    a = B()
    a.xxx()

Output:
     new_some_function

you syntax may differ depending upon the python version.
In python3
class B(A):
   def __init__(self):
       super().__init__() 

In Python 2,
class B(A):
    def __init__(self):
      super(ChildB, self).__init__()

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