龙空技术网

一文带你了解Python猴子补丁(Monky Patching)

编程实践 1338

前言:

目前看官们对“破解python程序 猴子补丁”大约比较关怀,同学们都需要了解一些“破解python程序 猴子补丁”的相关文章。那么小编也在网络上网罗了一些关于“破解python程序 猴子补丁””的相关资讯,希望兄弟们能喜欢,我们快快来学习一下吧!

猴子补丁

在讲解什么是 Python 猴子补丁之前,先来看看没有猴子补丁的情况下,一些特殊需求需要怎么来实现?(猴子补丁在百科上被称为很脏的编程技巧,那必然拥有其特别之处[奸笑])

# 定义一个Dog类class Dog:    # 定义一个say方法    def say(self):        print("汪汪汪")if __name__ == '__main__':    dog = Dog()    dog.say()# 汪汪汪

如上边的代码中, 先定义了一个 Dog 类, 并定义了一个 say 方法,并在方法中输出“汪汪汪”三个字,现在假设要求这里使用的 Dog 类的 say 方法,需要输出 Dog 的名字,那应该怎么办呢?显然,原先定义的方法并不能完成这个要求,只能重新定义一个方法来完成。这里又会出现的个问题,那就是,如果这个类是其他第三方提供的呢?我们来看看使用猴子补丁如何解决。

class Dog:    name = None    def say(self):        print("汪汪汪")def say_name(name):    print(f"我是{name}, 汪汪汪")if __name__ == '__main__':    dog = Dog()    dog.say = say_name    dog.say("旺财")# 我是旺财, 汪汪汪

如上述代码, 我们新定义了 say_name 函数,并设置 name 参数,重写了函数内部实现, 并在下方运行之前,将新定义的 say_name 函数,赋值给 dog.say 方法,可以看到,调用方法时,实际执行了新的逻辑。这就是一个非常典型的猴子补丁。

猴子补丁概念

Python作为一门典型的动态脚本编程语言,其中包含着动态类型与动态操作模型两大主要特征。具体体现在Python编程环境下,类(class)的特性具有可塑性,即它是灵活、易改变的;而对于方法而言,亦可视为类的另一项重要属性,同样具备在运行过程中的随时修改及替换特性,此一性质通常被表述为“猴子补丁”特性。

对熟悉Java编程技术的从业者来说,此一特性可能颇感意外。因为在Java这样的静态编程语言环境下,类的方法在类的定义阶段便已确定并固化,运行过程中几乎无法进行任何形式的变更。正是由于这个原因,动态可改性的特性恰恰成为动态脚本语言的显著优点之一

尤其值得关注的是,“猴子补丁”的最大特色恰恰就在于能够在程序运行期间,通过动态方式实现属性的替换与修改。在此基础之上,无论是函数、类还是模块等各类结构都能在Python中获得动态定制与修改的可能。

猴子补丁本质

以下我们来详细探索一下,猴子补丁到底发生了什么?依然使用上边示例代码,在 main 中打印一下不同的值。

if __name__ == '__main__':    dog = Dog()    print(dog.say)    dog.say = say_name    dog.say("旺财")    print(dog.say)    print(f"hasattr(dog, 'say') = {hasattr(dog, 'say')}")    print(f"hasattr(dog, 'say_name') ={hasattr(dog, 'say_name')}")# <bound method Dog.say of <__main__.Dog object at 0x000001FACDFCE540>># 我是旺财, 汪汪汪# <function say_name at 0x000001FACDF85260># hasattr(dog, 'say') = True# hasattr(dog, 'say_name') =False

从上边代码运行结果中可以看出,虽然我们使用猴子补丁将 dog.say 方法替换为了 say_name,但是替换的方法并没有发生实质性的改变,say_name 依然是普通函数,没有变更为绑定函数,通过 hasattr 函数运行的结果可以看出,say_name 函数并没有被绑定到类上,同时 say 方法也没有被覆盖。

所以可以得出以下结论:

使用猴子方法替换的方法并没有实质性的被替换,只是地址指向了新的函数。在调用的时候依然要使用原来的函数名,因为实例上边并没有新函数的属性。新的方法相当于只是重新实现了逻辑过程对类实例而言,其并不可见,因此只会影响当前实例。猴子补丁本质上是类属性指针重新指到了新的函数。使用场景实例级别修改

在上边的代码中,修改的都是实例级别的代码,我们使用猴子补丁修改了 dog 实例的方法,因此,后续此实例所有的使用该方法都会调用新的实现逻辑。此种修改方式,影响范围有限,只在使用的地方有用,非修改实例不会被修改。(限定在修改的实例上)

类级别修改

在 Python 中,还可以对其他对象使用猴子补丁,比如,类,模块等,如以下示例(经典的不修改代码实现,来替换功能)。

在 Python 中,我们一般会使用 json 模块来完成相应的功能,但是 Python 内置模块性能较差,因此会想到替换为 ujson 模块,这时就会发现,如果所有使用到的地方都替换,那必然是一个非常大的工作量,而且修改后是否会产生其他问题,有需要大量的测试,这时候,类级别的猴子补丁就非常有用了, 我们只需要将模块的功能替换后,在后续的所有使用上都会使用被替换后的逻辑。

import jsonimport ujsondef using_powerful_json():    json.__name__ = 'ujson'    json.dumps = ujson.dumps    json.loads = ujson.loads

如上边代码,我们定义一个using_powerful_json 函数, 在其中将 json 的 __name__,dumps,loads 方法都替换为 ujson 中的,我们只需要在主程序运行之初,执行该段代码,则当前程序内所有地方的 json 方法都会被替换为最新的逻辑功能。

注意事项

猴子补丁是一个非常强大的功能,它得益于动态语言的动态性,这就可以让我们在任何时候修改类或实例的功能。但是,所有的事物都会有两面性,猴子补丁也一样,随意的修改性,必然会导致代码不明确,给后续接手工作的同事留下坑,而且这种操作也会破坏程序的封装性,因此在使用时,必须要谨慎。那在哪些场景下适合使用呢?

修改第三方代码时,做部分功能的优化,这就是既修改逻辑,又不修改其源码修改一个面积较广的 bug 或做大面积的优化(如上述:ujson 示例)在条件允许的情况下,不要使用猴子补丁,尽量修改源码尽量控制修改范围,能修改实例不修改类,能修改最终类,不修改中间类,这都是为了防止代码混乱

#挑战30天在头条写日记# #文章首发挑战赛#

标签: #破解python程序 猴子补丁