一锅类似函数重载的小杂烩

点击上方“编程派”,选择设为“设为星标”

优质文章,第一时间送达!

上周末的时候发了篇文章,希望更多的读者可以一起参与到技术分享中。很高兴当天就收到了几位朋友的积极响应,今天试着原创发表其中一位的文章。希望大家多留言给作者一些意见和建议。

作者个人简介较长,我附在文末了。

作者:????礁sir

这两天写一个爬虫的时候遇到一个场景,要求写一个类放在代码中运行,这个类针对不同的网站会有统一的验证有效性,清理数据步骤,但是不同网站每个步骤具体操作内容是不同的。

也就是说,要求写一个类,函数名称是统一的,但是要根据初始化实例的时候传入的域名修改每个步骤的操作内容。

于是我想到了材料里面的内容。

材料

  • functiontools.singledispatch

  • 抽象基类与切面

  • 多重继承与MRO

  • 重写,重载与猴子补丁

  • 运算符重载

  • 元编程

料理过程

  • functiontools.singledispatch:

这个方式确实可以实现类似于Java/C++的函数重载效果, 通过传入不同的参数类型来执行不同的函数体。但是我这里的需求是同样的参数类型根据不同域名执行不同的函数体,于是首先排除掉。

  • 抽象基类:

这个方式主要用来继承并实现里面的抽象方法,还是需要写各个子类,里面的方法还是要一个个实现,这样代码重复比较多,所以我排除掉了。
不过它还可以作为类工厂去动态生成类,这就是面向对象的实现方式了。

  • 切面(Mixins):

切面其实就是带实现的接口,那么也不符合我想要的,因为我依然要去写子类

  • 重写(override):

这个方式其实就是把继承来的方法再重新写一下

  • 函数重载(overload)和运算符重载:

这个可以排除掉,python并不支持,C/+/+和Java这些语言才行,至于运算符重载,我需要的是不同的方法实现内容,而不是运算符

  • 猴子补丁和鸭子类型:

最后猴子补丁(Monkey Patch)给了我灵感,Python作为动态语言,鸭子类型(Duck Typing)的特点就是不管你是什么类型,只管你有没有这个方法。而猴子补丁就表现出Python中的类方法既然可以在运行时替换,那么完全可以传入各种函数,然后在实例化时用locals()/globals()根据传入的域名来动态加载不同实现的函数。

也就是说,这个场景的实现方式就变成了写一个类+一个工具方法文件。而在工具方法文件里,把各个域名的具体操作写进去就可以了。


成品1:

  • 类文件(SampleOperator.py):

from sample_operations import *

class SampleOperator():
    def __init__(self, domain):
        # 根据传入的域名,确认返回什么样的方法
        self.domain = domain.replace('.', '_')
        self.special_methods = ['is_valid', 'clean_data']
        self.dynamic_load()


    # 用反射的方式加载方法
    def dynamic_load(self):
        print('--------{domain} start load--------'.format(domain=self.domain))
        for method in self.special_methods:
            print(method, hasattr(self, method))
            special_method = '_'.join([method, self.domain])
            setattr(self, method, globals()[special_method])
            print(method, hasattr(self, method))
            func = getattr(self, method)
            func()
        print('--------{domain} end load--------'.format(domain=self.domain))
    
if __name__ == '__main__':
    test = SampleOperator('test.com')
    test.is_valid()
    test.clean_data()
    abc = WebsiteOperator('abc.com')
    abc.is_valid()
    abc.clean_data()
  • 方法文件(sample_operations.py):

def is_valid_test_com():
    print('is_valid_test_com')


def clean_data_test_com():
    print('clean_data_test_com')

def is_valid_abc_com():
    print('is_valid_abc_com')


def clean_data_abc_com():
    print('clean_data_abc_com')
  • 运行效果: 实现了之前设想的通过传入不同域名来动态替换对应的方法内容,用同一个方法名调用

python SampleOperator.py
--------test_com start load--------
is_valid False
is_valid True
is_valid_test_com
clean_data False
clean_data True
clean_data_test_com
--------test_com end load--------
is_valid_test_com
clean_data_test_com
--------abc_com start load--------
is_valid False
is_valid True
is_valid_abc_com
clean_data False
clean_data True
clean_data_abc_com
--------abc_com end load--------
is_valid_abc_com
clean_data_abc_com

成品2:

可以利用globals()来根据域名动态载入要实例化的类,然后把每个统一步骤里面的相同操作放在父类里面,子类在实现的时候先调用父类的统一操作再添加细节

>>> class A:
...     def name(self):
...             print('A')
...
>>> class B:
...     def name(self):
...             print('B')
...
>>> globals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'A': <class '__main__.A'>, 'B': <class '__main__.B'>}
>>> globals()['A']
<class '__main__.A'>
>>> globals()['A']()
<__main__.A object at 0x11016d0b8>
>>> globals()['A']().name()
A

进一步料理:考虑到可能会有十几二十个网站,每个网站最好是单独放一个文件,这样更方便管理和调整。于是想到了python的反射里面动态载入模块

  • 先看看修改后的结构

tree
.
├── SampleOperator.py
└── web_operations
    ├── __pycache__
    │   ├── abc_com.cpython-37.pyc
    │   └── test_com.cpython-37.pyc
    ├── abc_com.py
    └── test_com.py
  • SampleOperator.py

import importlib

class SampleOperator():
    def __init__(self, domain):
        self.domain = domain.replace('.', '_')
        self.special_methods = ['is_valid', 'clean_data']
        self.dynamic_load()

    # 用反射的方式加载方法
    def dynamic_load(self):
        print('--------{domain} start load--------'.format(domain=self.domain))
        special_module = importlib.import_module(
                            '.'.join(['web_operations', self.domain]) 
                        )
        for method in self.special_methods:
            print(method, hasattr(self, method))
            # 动态匹配到网站专门的方法
            special_method = '_'.join([method, self.domain])
            setattr(self, method, getattr(special_module, method))
            print(method, hasattr(self, method))
        print('--------{domain} end load--------'.format(domain=self.domain))
    
if __name__ == '__main__':
    test = SampleOperator('test.com')
    test.is_valid()
    test.clean_data()
    abc = SampleOperator('abc.com')
    abc.is_valid()
    abc.clean_data()
  • abc_com.py

def is_valid():
    print('is_valid_abc_com')


def clean_data():
    print('clean_data_abc_com')
  • test_com.py

def is_valid():
    print('is_valid_test_com')


def clean_data():
    print('clean_data_test_com')

作者个人简介:

大学时用记事本学写html以为这就是编程,后来学java才知面向对象的妙。再到python深入体会到函数式编程,响应式编程的独特风味。方才领悟到这就是我想要的味道。

于是看cpython源码的时候体会到c语言的稳当,再把python里面学到的语法,数据结构,算法,内在特性迁移对比到golang,ruby,lua,js等其他语言,最后能以更加宽广的视角看待编程语言。

我知道编程是片大海,我只是条小鱼,哪怕尽情扑腾也没有多少浪花,但是我只想慢慢感受。

回复下方「关键词」,获取优质资源

回复关键词「 pybook03」,立即获取主页君与小伙伴一起翻译的《Think Python 2e》电子版
回复关键词「入门资料」,立即获取主页君整理的 10 本 Python 入门书的电子版
回复关键词「m」,立即获取Python精选优质文章合集
回复关键词「book 数字」,将数字替换成 0 及以上数字,有惊喜好礼哦~
题图:pexels,CC0 授权。

好文章,我在看❤️
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值