V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
imn1
V2EX  ›  Python

迫于年纪大健忘,写了个简陋的装饰器,转换输入类型

  •  
  •   imn1 · 2020-11-24 16:30:03 +08:00 · 1845 次点击
    这是一个创建于 1242 天前的主题,其中的信息可能已经有所发展或是发生改变。

    首先,是 真 简陋

    自用的,出了问题抽自己嘴巴就行了,但无法对他人负责,所以各位要用就谨慎再谨慎,测试多几遍,尽量只用在基础类型,泛类型或者复杂的类型嵌套估计这个解决不了

    作用:作为装饰器,根据函数的 type hints,相同类型跳过,类型不符就调用 funs 指定函数转换,再传给被装饰的函数。 不建议用在产出环境,当然你能把“真简陋”变为“真完美”,那就另说

    起意:就是健忘,经常忘记以前(三天前就算“以前”)写的函数输出类型,新写个函数,处理字符串输出,却发现传入的是个 int / float,想改某个列表元素,却发现传入是个 tuple,用在这些检查、修改花了不少时间,就想简单套个装饰器,先跑通再说,review 修正留待以后处理

    因为自用,我写的 py 几乎没有对外,所以弄个 @修正就算了,懒得逐个查找更改,如果值不对再查,或者后期要完善代码时,再去掉 @慢慢改

    说明:

    1. 可作为无参数装饰器
    2. varnames 默认 None,就对传入的参数轮询一遍,符合 type hint 的,或者 funs / despatcher 没有的类型跳过。 如果非 None,格式是个字符串列表( list/tuple/set 等),列出需要检查的变量名。 没做递归,所以 args,kwargs 传入的就没用了(这两个也没 type hint 吧?)
    3. despatcher 默认 None,就是预置的 funs,是个 “类型:函数名” 的键值对 dict,可以外部传入。 那些 to 什么的函数我就不提供了,放出来会害死人的,例如我 toStr 直接把列表类型用逗号串起来了,因为几乎只用在 sql 相关的函数,字段名是字串,但总是忘记上一个函数输出的是列表还是字串
    4. checktypes 及 is_instance 是人家写的,这里就不贴了,代码在 https://stackoverflow.com/questions/55503673/how-do-i-check-if-a-value-matches-a-type-in-python 很长看上去也很严谨,我就没详细研究了,伸手了事
    5. type hint 就不用说版本了吧,这个仅在 3.7.5 测试,手头也没更多 py 版本
    6. 有错误请指出,也帮我以后避免引入新问题
    7. 这段代码无版权,各取所需(谁用谁负责),至于前面第 4 点引用的代码,版权自理,我也不清楚
    import inspect
    from functools import singledispatch, wraps, partial
    from checktypes import is_instance # 说明 4
    
    def toStr(source)->str:
        return str(source)
    
    def toInt(source)->int:
        return int(source)
    
    def typeshint(func=None, *, varnames=None, despatcher=None):
        if func is None:
            return partial(typeshint, varnames=varnames, despatcher=despatcher)
    
        funs = { # 说明 3
            int: toInt,
            str: toStr,
            # list: toList,
            # tuple: toTuple,
            # set: toSet,
            # dict: toDict,
        }
        despatcher = despatcher if despatcher else funs
        sig = inspect.signature(func)
        parameters = sig.parameters
        varnames = varnames if varnames else parameters.keys()
    
        @wraps(func)
        def wrapper(*args, **kwargs):
            varkw = inspect.getfullargspec(func).varkw
            binds = sig.bind(*args, **kwargs)
            for n in varnames:
                annotation = parameters[n].annotation
                var = binds.arguments[n] if n in binds.arguments else kwargs[n]
                if is_instance(var, annotation) or (annotation not in despatcher): continue
                ff = despatcher.get(annotation)
                if n in binds.arguments:
                    binds.arguments[n] = ff(var)
                else:
                    binds.arguments[varkw][n] = ff(var)
            return func(*binds.args, **binds.kwargs)
        return wrapper
    
    if __name__ == "__main__":
        @typeshint
        def myfunc(a:str, b:int=4, c=0):
            print(type(a), a, b, c)
    
        myfunc(20, '123', '0') # <class 'str'> 20 123 0
    
    

    两三事:

    python 真是什么都是对象,才发现类型也能做 keys

    这个 varnames 报错查了几个小时,就因为最初 sig 是写在 wrapper 里面,在 wrapper 里面用 if varnames is None 就报错,要一起移到 wrapper 外面。唉,水平低,会抄会改不会写……

    另外有两个类似的,一个自动适应格式的,例如 json/yaml 转字典、csv 转二维,诸如此类,一个根据 return type hint 转换输出类型的,因为魔改得太厉害(只有自己能用),害死人的代码就不放出来了,反正目的和思路差不多——先跑通再说

    3 条回复    2020-11-24 22:13:43 +08:00
    ClericPy
        1
    ClericPy  
       2020-11-24 21:52:24 +08:00
    以前也做过类似的, 把一个有 type hints 的函数转交互式命令行, 各种类型折腾的, 后来被 pydantic.parse_obj 教做人...
    imn1
        2
    imn1  
    OP
       2020-11-24 22:04:47 +08:00
    @ClericPy
    搜了几天 type hint 都没遇到这个模块,都算神奇,抽空看看
    ClericPy
        3
    ClericPy  
       2020-11-24 22:13:43 +08:00
    @imn1 这是圈内最流行那个了(好像 fastapi 带起来的?), 还做了 Benchmark... 现在拿它做 schema 校验好用的一塌糊涂, 能把不符合的类型尝试性去转换, 结果让接手我代码的以为我在写 go.....
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2853 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 38ms · UTC 14:33 · PVG 22:33 · LAX 07:33 · JFK 10:33
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.