您的位置:首页 > 编程语言 > Python开发

利用装饰器给python的函数加上类型限制

2016-09-27 13:36 267 查看
作为一名python的脑残粉,请先跟我念一遍python大法好。

其作为动态语言的灵活,简介的代码,确实在某些情况下确实比其他编程语言要好。但你有没有想过,有时这些灵活的语法,可能会造成一些糟糕的体验。尤其是针对新手,python易上手不假,但动态语言写得项目规模一大,其实比相对严谨的静态语言,更考验程序员的内力。

哪怕你只是用过python写过一些初等的项目,那你可能也体验过以下这种情况。
莫名其妙的传错参数类型。

python不需要显式声明参数类型,同样,什么样的变量都可以往函数里扔,包括函数(python支持函数式编程),这容易出现一个问题,如果一个函数不是自己设计的,你很可能网里面传了错误类型的参数。当然,这样大多数触发异常,因为传错类型意味着函数的一些操作该类型不支持。但有时传了错误的参数类型,却并不会触发异常(比如字符串相加和数字相加,以及一切对象的==判断),不会触发异常结果却是错误的,这就意味着出现了问题更难确定位置,甚至如果这个函数的返回值,再传进其他函数(假设叫B)时,当你发现得到结果错误时,你很可能以为是B函数的逻辑设计出现了错误,从而花费大量的时间在错误的地方,使用python多数是对开发效率比较重要的场景,而很可能因为一个粗心,使得写代码的时间短了,结果将时间都花在找BUG上了。
进行操作之前忘记了转型

典型的就是把参数类型为
str
int
相加。或者把
str
传进
range()
里面。
列表越界

python的列表类似于动态数组,没有长度的限制。虽然大多数我们只需用
for x in one_list
即可完成对列表的访问,而不需要去考虑列表的长度。但其实还有一种情景,比如说一个列表(或者元组)中元素的次序是有意义的,比如说
[name,age,sex]
而且这可能是某个函数动态生成的,比如爬虫爬取网站后从里面挑选信息后返回,这时,如果这个网站中age,sex的信息缺失,python可不会自动补充None下去,至少我没看见有人的函数或方法会考虑到这一点,而是直接给你返回
[name]
,而如果你需要获取age,而直接访问下标为1的元素,则会触发异常。

类似的还有种种,当然并非不可解决,比如足够多的
assert
isinstance
,足够严谨的逻辑设计,枯燥但很有必要的单元测试等等……但使用python很多时候就是为了加快开发效率,上面的这些措施显然太过麻烦。

断断续续的写了一两天,弄了几个装饰器来解决这些问题,下面开始分享一下。


什么是装饰器

需要了解pythopn中装饰器的基本概念,可以参考一下廖老师的py教程
点这里


如何利用装饰器限制函数的参数和返回值

使用装饰器可以使得一个函数外面加上某些操作然后在重新返回到你定义的函数名字指定的对象上。

说实话,我很难用言语描述出这种关系,直接上代码好了。

以使用装饰器限制函数参数类型为例:

装饰器的实现如下:

12345678910111213141516171819def type_limit(*typeLimit,**returnType):    def test_value_type(func):        def wrapper(*param,**kw):            length = len(typeLimit)            if length != len(param):                raise LimitError("The list of typeLimit and param must have the same length")            for index in range(length):                t = typeLimit[index]                p = param[index]                if not isinstance(p,t):                    raise LimitError("The param %s should be %s,but it's %s now!"%(str(p),type(t()),type(p)))              res = func(*param,**kw)            if "returnType" in returnType:                limit = returnType["returnType"]                if  not isinstance(res,limit):                    raise LimitError("This function must return a value that is %s,but now it's %s"%(limit,type(res) ) )            return res        return wrapper    return test_value_type
假设我希望实现一个函数,实现两数求和,为了避免传进去的是两个字符串,造成字符串连接,我需要限制其类型都为
int
这时,我们可以这么做:

1

2

3

@type_limit(int,int)

def
test(x,y):

    return
x+y

这个定义的过程发生了什么呢?上述代码等价于

1

2

temp
=
type_limit(int,int)
#temp =  test_value_type

test
=
temp(test)
#这是,test已经在原test上经过修饰,指向wrapper

而在wrapper中,最终会返回调用原test的结果,这个装饰器做的,只不过是在调用原test前,利用
isinstance
进行了一遍类型检测而已。这样,我们可以简单的模仿像
java
C++
这样的静态语言一样,在声明的时候就对参数类型进行限制了。

理解这个装饰器把握着一下几点:
函数可以作为变量使用,即可以作为参数和返回值
装饰器利用了函数内的函数,可以访问外层函数之间的一些变量从而对内层函数进行修饰。(比如对将要传进内层函数的参数进行检测等),从而实现对参数的类型进行限制。

理解这两点后,你可以自由的修改和拓展这些装饰器,如果你有更好的实现,记得在
github
pull
给我哦,
github
地址稍后给出。


其他相关的限制

除上述外,我还是实现了其他限制:
列表长度的限制,不足指定长度,自动补充指定元素。
对二维列表的每一维列表进行长度限制,不足指定长度,自动补充指定元素。主要为某些算法进行限制。
常量类Const,目测没有什么用
对列表的每一元素的类型进行限制


后记

限于篇幅,其他的代码不一一在这里介绍,关键思路在上文已给出,其余代码开源在github上,如果需要,你可以直接拿去使用。不过记得不要滥用。

github地址

如有更好的建议和或不正确的地方,可以在本文或
github
下告知。

如有错别字……请忽略(^ ^)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: