硬核python,不修改源码条件下修改类方法

不修改源码条件下修改类方法,咋听起来是一件扯淡的事情,但对python来说,并非难事。

场景如下,先定义一个类

class Demo():
    def readfile(self, filename):
        """
        读取文件内容,这里简化为输出文件名称
        :param filename:
        :return:
        """
        print(filename)

demo = Demo()
demo.readfile('/data/1.txt')
demo.readfile('/data/2.txt')
demo.readfile('/data/3.txt')

这份程序一直运行在linux系统下,可突然间有了变化,要求也可以在windows下运行,这三份文件都存储在d:/data/目录下,文件名称不变。注意,是一份程序即可在linux下运行也可以在windows下运行。

此时你首先想到的是修改Demo类的radfile方法,根据程序运行所在系统来决定文件路径,这是一个非常正确的做法,但这样就得修改Demo类。修改Demo类有问题吗,如果Demo类是你自己写的,那当然没有问题,但假设Demo类不是你写的,而是第三方包提供的,或者是python内置的呢,这种情况下你就不能修改其源码。

面对此种困境,还存在一个常规的解决方案,自定义一个继承Demo的类,MyDemo, 重写readfile方法。这样,只需要修改demo实例就可以了

demo = MyDemo()

这个方案可行,但我要做的,是更加硬核的方法,在运行状态下修改Demo类的readfile方法。我的思路如下

  1. 定义一个readfile函数,函数参数与Demo类的readfile方法一致
  2. 使用setattr,设置Demo类的属性readfileOrigin 为 Demo.readfile
  3. 使用setattr,设置Demo类的属性readfile为第一步中的readfile函数
  4. 在函数readfile里调用Demo.readfileOrigin

代码实现如下

class Demo():
    def readfile(self, filename):
        """
        读取文件内容,这里简化为输出文件名称
        :param filename:
        :return:
        """
        print(filename)


def readfile(self, filename):
    # 这里先判断所在系统,修改filename, 本文未实现这部分
    self.readfileOrigin("修改后的:" + filename)


if getattr(Demo, 'readfileOrigin', None) is None:
    setattr(Demo, 'readfileOrigin', Demo.readfile)
    setattr(Demo, 'readfile', readfile)

demo = Demo()
demo.readfile('/data/1.txt')
demo.readfile('/data/2.txt')
demo.readfile('/data/3.txt')

我所描述的场景你或许觉得很极端,但最近在工作中却真实的遇到了。手里负责的工作是将部分pyspark任务迁移至阿里云的maxcompute,涉及到读写hdfs的地方要提供不同的路径,在公司内集群运行是hdfs路径,在maxcompute上运行需要提供oss路径。为了尽可能少的修改源码,我使用上面的方法修改了SparkContext.textFile 和 SparkContext.addFile方法。

扫描关注, 与我技术互动

QQ交流群: 211426309

加入知识星球, 每天收获更多精彩内容

分享日常研究的python技术和遇到的问题及解决方案