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

关于服务器数据验证,一种比较优雅简便的Python解决方法

2013-11-12 23:01 591 查看
这两天整服务器代码遇到如题所说问题,不得已上了chinaunix发了个帖子,寻找一种比较好的解决方式,最后找到了一种比较好的方法,中间学到了挺多东西,现记录在此。

原帖地址:http://bbs.chinaunix.net/thread-4111060-1-1.html

感谢@Hadron74 跨生物学界和编程界的博士后,业余时间也翻译了一本python书《Python
for Bioinformatics》,搞生物学的pythoner可以去读一下~~

他的博客地址是:http://blog.sciencenet.cn/home.php?mod=space&uid=565112

原抛出来的问题是这样的:

1楼:Sasoritattoo

要求是这样的,有一个项目是手机应用客户端与服务器通信,但是每次网络来的请求都会有很多的字符串参数需要转换,而且需要验证其基本信息是否符合基本格式要求,如果不符合会返回错误码给客户端,符合了就返回正常的数据。

拿一个具体的例子来说吧:

第一代原型函数是这样的:

@app.route('/test', methods = ['GET', 'POST'])

def test_route():

'''

这是一条路由,假如是IP+"/test/"就可以访问到此函数。然后其form中带了三个参数:lat,lon,id。

'''

lat = request.form.get('LAT')

lon = request.form.get('LON')

id = request.form.get('OBJECT_ID')

#接下来每个参数都要处理一遍,非常蛋疼的行为!

try:

lat = float(lat)

except:

error = ERROR_LAT

return error

try:

lon = float(lon)

except:

error = ERROR_LON

return error



try:

id = ObjectId(id)

except:

error = ERROR_ID

return error

do_something()

复制代码
这样的话如果参数一多的话,首先好几十行处理类型转换的代码,而且如果路由多了,那么这些冗余代码量是很可观的,因此这个函数演化诞生了第二代原型,如下:

@app.route('/test', methods = ['GET', 'POST'])

def test_route():

lat = request.form.get('LAT')

lon = request.form.get('LON')

id = request.form.get('OBJECT_ID')

#这种写法稍微好些,等于用check_format先把风险吃掉了,但是无法更新这里的lat,lon,id等值,所以还要下面再来一次

is_ok, error = check_format(lat=lat, lon=lon, id=id)

if not is_ok:

return error

lat = float(lat)

lon = float(lon)

id = ObjectId(id)

do_something()

@classmethod

def check_format(lat=None, lon=None, id=None):

,,,

本方法因为是与处理一个数据类中的值相关,所以与上面的方法不在同一个py文件中。

如果我的项目是mvc架构的话,那么它是应该放在model层的,上面的函数放在view层。

,,,

ret = True

error = ''

if lat is not None:

try:

lat = float(lat)

except:

error = ERROR_LAT

return False, error

if lon is not None:

try:

lon = float(lon)

except:

error = ERROR_LON

return False, error

if id is not None:

try:

id = ObjectId(id)

except:

error = ERROR_ID

return False, error

return ret, error

复制代码
第二代原型函数就是这样,不过你也看到了,在check_format()之后,还要自己再转一次,对于一个写代码喜欢追求美感的码农来说,这显然有点儿破坏美感,如果能够同时一次就改变了原函数中的值就好了,无奈精力和python造诣有限一时想不出好方法,因此特来求助于各位pythoner高手,希望解决这个需求,小弟先谢过了

待续。。。。

PS:

今日光棍节,愿天下程序猿光棍早日“脱光”

2楼:icymirror

我也是刚学习Python不久,最近刚刚在看metaclass相关的东西,也许下面的方法是你需要的。

使用的时候,只需要把新的句柄的开关加入到__routes__中,然后,定义下对应的方法就好了。

(风险:如果有几个检测是非常相似的,最好在检测时候仔细处理,免得被先检测的方法误"诊"了)

class MetaRouter(object):

def __init__(self):

self.__routes__ = ('lat', 'lon', 'id') # define the head of handler

def test_route(self, content):

handle = None

for index in xrange(len(self.__routes__)):

methodName = self.__routes__[index] + "_handler"

handler = getattr(self, methodName)

if handler(content) == True:

print("Current is %s", self.__routes__[index])

break

# below is the dosomething():

print "all is done.\n", "=" * 80

def lat_handler(self, content): # handler for lat

try:

float(content)

return True

except:

return False

def lon_handler(self, content): # handler for lon

try:

int(content)

return True

except:

return False

def id_handler(self, content): # handler for id

try:

return True

except:

return False

def main():

route = MetaRouter()

route.test_route("33") # verify lan

route.test_route("3.14") # verify lon

route.test_route("id=9") # verify id

if __name__ == '__main__':

main()

3楼:Hadron74

本帖最后由 Hadron74 于 2013-11-11 14:31 编辑

回复 1# Sasoritattoo

n

你可以充分利用字典的功能,用维护一个字典,记录所需的format的格式和错误代码。另外再用一个字典保存返回值。这个程序就简洁多了,没有冗余代码了。

具体程序如下:

class ObjectId(str):

def __init__(self,obj):

str.__init__(self,obj)

# This is the dictionary you need to support

key_format = {"LAT":(float,1)\

,"LON":(float,2)\

,"ObjectId":(ObjectId,3)\

}

def test_route(requestForm,key_format):

values = {}

for key in key_format:

value = requestForm[key]

#value = request.form.get(key) # You can use the line in your real function

try:

values[key] = key_format[key][0](value)

except:

raise ValueError("DATA FORMAT ERROR NO:%d"% (key_format[key][1],))

print values # to store the converted data

#do_something()

rf={"LAT":"12.0","LON":"2.0","ObjectId":"This is an example"}

test_route(rf,key_format)

rf={"LAT":"12.0","LON":"ERROR2.0","ObjectId":"This is an example"}

test_route(rf,key_format)

复制代码
结果:

$python test_route.py

{'LAT': 12.0, 'LON': 2.0, 'ObjectId': 'This is an example'}

Traceback (most recent call last):

File "test_route.py", line 33, in <module>

test_route(rf,key_format)

File "test_route.py", line 20, in test_route

raise ValueError("DATA FORMAT ERROR NO:%d"% (key_format[key][1],))

ValueError: DATA FORMAT ERROR NO:2

复制代码

4楼:Sasoritattoo

回复 3# Hadron74

其实,不只是做类型检查这么简单,类型检查只是一部分,我是希望通过check_format()方法实现预处理一下参数,不符合就返回错误,符合的话下面就直接用了。

5楼:Sasoritattoo

回复 2# icymirror

谢谢,不太能满足我的需求。

6楼:Hadron74

回复 4# Sasoritattoo

我写的只是一个思路,你可以用自定义函数的方法,处理任何你需要的预处理参数问题,如果你看懂了我的程序,不难实现。

在程序中只需要维护一个字典就能完成所有的处理。当然你也可以用一个try块把这部分程序封装在check_form函数中,用values做返回值,这样就能截获错误信息。代码我就不写了。

7楼:Sasoritattoo

回复 6# Hadron74

你的代码虽然懂了,但是实际操作起来仍然不好操作,尝试了几次都没找到比较好的实际操作方法,而且那些lat,lon,id在下面的do_something()阶段还要接着使用的

我想了一下,大概实现思路是这样的:

第一阶段:

lat, is_ok, error = check_format(lat='LAT')

if not_is_ok:

return error

lon, is_ok, error = check_format(lon='LON')

if not_is_ok:

return error

id, is_ok, error = check_format(id='OBJECT_ID')

if not_is_ok:

return error

lon = 123

lat = 321



这是分开写的,然后需要进化到把其变成

for item in items:

?, is_ok, error = check_format(?)

if not is_ok:

return error

lon = 123

lat = 321

关键是?这里怎么填写才能给lon,lat,id赋上值?而且下面还要用到lon和lat,不知您知道怎么实现吗?

这里关于check_format()应该不难,只要传入对应参数,做相应处理即可。

8楼:yjphhw

你可以通过表单来解决。看看表单,建立一个表单,然后,直接用表单的验证。

9楼:Hadron74

回复 7# Sasoritattoo

如果你非要直接用变量名,就把它们定义到globals里吧,不过该global变量不是好的编程,有崩溃的危险。(这里改了一版贴出来)

class ObjectId(str):

def __init__(self,obj):

str.__init__(self,obj)

# This is the dictionary you need to support

key_format = {"LAT":(float,1,"lat")\

,"LON":(float,2,"lon")\

,"ObjectId":(ObjectId,3,"object")\

}

def test_route(requestForm,key_format):

def check_format(requestForm,key_format):

values = {}

for key in key_format:

value = requestForm[key]

#value = request.form(key) # You can use the line in your real function

try:

values[key_format[key][2]] = key_format[key][0](value)

except:

values = (key,key_format[key][1])

return values

values = check_format(requestForm,key_format)

if type(values) != type({}):

key, err_number = values

# deal something for the error

print key,err_number, "heihei"

return

globals().update(values)

#do_something()

print lat, "xixixixi"

print object, "hahaha"

rf={"LAT":"12.0","LON":"2.0","ObjectId":"This is an example"}

test_route(rf,key_format)

test_route(rf,key_format)

rf={"LAT":"12.0","LON":"ERROR2.0","ObjectId":"This is an example"}

test_route(rf,key_format)



复制代码
结果

$python test_route.py

12.0 xixixixi

This is an example hahaha

LON 2 heihei

复制代码
还有一种方法是用类的属性函数,这里就不写了,但是lat前需要加个classname,用classname.lat

参考http://stackoverflow.com/questio ... -variable-in-python

不过我觉得最好的方法,也是最简单就是在后续程序中用values["lat"]来替代lat;

你说呢?

10楼:timespace

回复 7# Sasoritattoo

我觉得只要功能逻辑划分清楚,代码保持适当的冗余可以简化问题。把test_route的一堆“if”转移到check_format又能改变什么?反而让代码更难懂。大概意思如下

@app.route('/test', methods = ['GET', 'POST'])

def test_route():

lat, error = parse_float(request.form.get('LAT'))

if lat is None:

return error

lon, error = parse_float(request.form.get('LON'))

if lon is None:

return error

object_id, error = parse_object_id(request.form.get('OBJECT_ID'))

if object_id is None:

return error

do_something()

def parse_float(param):

pass

def parse_object_id(param):

pass

复制代码

11楼:Sasoritattoo

回复 8# yjphhw

我项目使用的是Flask,其有一个WTForms,不过我嫌建造太多Form太麻烦,而且有些限制,没有首先考虑。

12楼:Sasoritattoo

回复 9# Hadron74

这种方式也不符合,global变量是下策

13楼:Sasoritattoo

回复 10# timespace

太多的冗余代码会影响代码的观看,写程序不是堆砌代码,在我看来是思维逻辑的展现,尽可能的简洁,一下子就能看到逻辑,某一层有某一层的逻辑,不要混淆在一起。

14楼:Sasoritattoo

回复 10# timespace

回复 9# Hadron74

回复 8# yjphhw

想来想去,每种思路都是比较麻烦,不能够很好的达到我想要的效果。不过我最后我采用了一种在我看来稍微好些的思路,各位可以看一下:

check_format()函数是这样定义的

@classmethod

def check_format(cls, forms, args):

ret = True

error = ''

ret_dict = {}

if len(args) == 0:

return ret, error, ret_dict

for arg in args:

is_ok, error, value = check_protocol(arg, forms)

if not is_ok:

return is_ok, error, ret_dict

ret_dict[arg] = value

return ret, error, ret_dict

复制代码
另外我新建了一个protocol类,里面放着那些协议参数名,如下:

class Protocol(Object):

LON = 'lon'

LAT = 'lat'

ID = 'id'

def _LAT_LON():

try:

value = float(tmp)

except:

error = Error.ERROR_LAT_LON

return False, error, tmp

return True, '', value

def _ID():

pass

protocol_dict = {

Protocol.LON:'_LON()',

Protocol.LAT:'_LAT()',

Protocol.ID:'_ID()'

}

tmp = ''

def check_protocol(arg, args):

value = args.get(arg)

global tmp

tmp = value

func = protocol_dict[arg]

if func is not '':

return eval(func)

else:

return True, '', value

复制代码
在视图View层,你只需这样即可:

@app.route('/test/')

def test():

forms = request.args #或者request.forms

args = [Protocol.LAT, Protocol.LAT, Protocol.ID]

is_ok, error, DICTS = check_format(forms, args)

if not is_ok:

return error

lon = DICTS[Protocol.LON]

lat = DICTS[Protocol.LAT]

id = DICTS[Protocol.ID]

复制代码
这样的好处是你在视图函数里只需要知道check_format()即可,按照这个流程来就行了,知一知百,里面怎么实现不用关心,这样对于将来代码的维护者来说也是轻巧很多,很容易懂,而且减少了很多代码量,非常容易就看清框架思路。

目前,在我看来,这是目前来讲我感觉比较好些的解决思路。

关于check_protocol部分,如果能够实现通过eval执行函数并传递参数就更好了,只可惜我不知道怎么操作,如果有懂的人还请告知一下,至于protocol_dict中value值使用的是字符串,是担心直接函数的话会被多次执行。

如果有不同意见,欢迎继续交流哈~~

15楼:Hadron74

回复 14# Sasoritattoo

你这里用全局变量tmp不是一个鲁棒的办法,容易出问题。原因一样,全局变量可能导致崩溃。

我不理解你为什么说直接调用函数会多次执行,这是怎么回事呢?为什么哑员参数传递不成?

你可以试试传函数参数的方法。代码如下:

class Protocol(Object):

LON = 'lon'

LAT = 'lat'

ID = 'id'

def _LAT_LON(tmp):

try:

value = float(tmp)

except:

error = Error.ERROR_LAT_LON

return False, error, tmp

return True, '', value

def _ID(tmp,others="None"):

pass

protocol_dict = {

Protocol.LON:(_LON_LAT,[])

Protocol.LAT:(_LON_LAT,[])

Protocol.ID:(_ID,["others"]) # 定义函数,和哑员参数

}

def check_protocol(arg, args):

value = args.get(arg)

func = protocol_dict[arg][0]

others = protocol_dict[arg][1]

if func is not '':

return func(value,*others) # 函数调用,加哑员

else:

return True, '', value

复制代码

16楼:Sasoritattoo

回复 15# Hadron74

使用tmp是不得已而用之,的确是很容易出问题的,也不符合OO原则,多亏了你提供的这个办法,

效果很好,多谢。

至于你说“我不理解你为什么说直接调用函数会多次执行?”其实是我使用eval()方法执行,不转成str的话,我在函数后加了(),所以被执行了一遍。最根本原因还是我对python有些特性不熟悉。

看了你的代码,也明白了,原来函数作为一个对象保存起来是这样保存的。再谢。

17楼:Sasoritattoo

回复 10# timespace

回复 9# Hadron74

回复 8# yjphhw

上面那样感觉已经不错了,但是实际使用起来,还是比较麻烦,甚至还不如我最开始写的更简洁明了呢,后来想了一下,发现还有一个方法可以继续优化,并且看起来非常的让人舒畅,这就是我理想中的code了。现贴之如下:

check_format()函数是这样定义的:

def check_format(arg, value):

func = protocol_dict[arg][0]

if func is '':

raise CheckFormatException(0)

is_ok, error, result = func(value)

if not is_ok:

raise CheckFormException(error)

return result

复制代码
如果format信息不符合要求,直接甩出CheckFormatException错误,此错误里面包含错误码,如果正常就返回转型后的数据。

新建了一个protocol类,里面放着那些协议参数名,如下:

class Protocol(Object):

LON = 'lon'

LAT = 'lat'

ID = 'id'

class CheckFormatException(Exception)

def __init__(self, error_code):

Exception.__init__(self, error_code)

self.error = error_code

def _LAT_LON(value):

try:

value = float(value)

except:

error = Error.ERROR_LAT_LON

return False, error, value

return True, '', value

def _ID(id):

try:

id = ObjectId(id)

except:

error = Error.ERROR_ID

return False, error, id

return True, '', id

protocol_dict = {

Protocol.LON: (_LAT_LON, []), #一不留神又写错了,_LAT_LON写成_LAT_LON()了,多谢楼下斧正

Protocol.LAT: (_LAT_LON, []),

Protocol.ID: (_ID, [])

}

复制代码
在视图View层,你只需这样即可:

@app.route('/test/')

def test():

forms = request.forms

try:

lon = check_format(LON, forms.get(LON))

lat = check_format(LAT, forms.get(LAT))

id = check_format(ID, forms.get(ID))

exception CheckFormatException, e:

return e.error



#lon and lat尽情使用吧,类型已经转换过来了

复制代码
所有的都是这么简单,给check_format()传递一个参数名,一个value

这里要多谢大家给予的帮助,尤其是@Hadron74兄弟,谢谢。

本帖完,谢谢观赏。

18楼:Hadron74

本帖最后由 Hadron74 于 2013-11-12 23:26 编辑

回复 17# Sasoritattoo

我也是理想主义者,看了楼主的代码,觉得有很多可取的地方,但是try块嵌套用得太多了,而且,像一个float函数都得重写一个函数,太冗余了。我试着改写了你的代码:

def check_format(arg, value):

func = protocol_dict[arg][0]

others = protol_dict[arg][1]

try:

result = func(value,*others)

except:

raise CheckFormException(protocol_dict[arg][2]) # 在这里根据不同arg抛出错误码,在字典中定义。

return result

复制代码

class Protocol(Object): #个人觉得你这一层类的设计没有必要,可以精简掉

LON = 'lon'

LAT = 'lat'

ID = 'id'

class CheckFormatException(Exception)

def __init__(self, error_code):

Exception.__init__(self, error_code)

self.error = error_code #这个设计不错,学习了

protocol_dict = {

Protocol.LON: (float, [],ERROR_float), # 这里直接用转换函数,没有必要再嵌套一层,而且注意函数对象后面不能有(),你的代码有误

Protocol.LAT: (float, [],ERROR_float),

Protocol.ID: (ObjectID, [],ERROR_ID)

}

复制代码
其他代码一样,是不是更简洁一点呢?

19楼:Sasoritattoo

回复 18# Hadron74

的确更简洁,这一部分也确实有些冗余,不过,这毕竟是一个demo,实际项目中可不只是做类型转换,还有比如检测文本是否符合长度,不能太少也不能太多,而且每种文本的长度限制是不一样的。

比如,检查文本是否符合长度的函数:

def _check_text_length(t, min_len, max_len):

if min_len <= ken(t) <= max_len:

return True, '', t

return False, '', t

关于协议类去掉的考虑,如果去掉的话,整个协议文本散落在整个项目中,到时候改起来非常不好改,必须全局唯一性,所以,存在是必要的。

其实,实际中我原本是每个协议对应一个check_format()方法,demo就是随便举的例子,但是你说这里有冗余,也的确,想了一下,现在改掉了冗余。

参考了你上面的做法,不过仍然是使用了自定义的函数,只不过分为_check_format_float(v), _check_format_int(v), _check_format_text(t, min_len, max_len),为其分了几个大类,错误码就找你说得写在了protocol_dict[]中了。我的协议挺多的,这样一精简,少了一大半代码,看起来也清爽多了。Hadron74兄,劳烦看看是否有比我的更好方法?欢迎交流斧正

20楼:Hadron74

回复 19# Sasoritattoo

如果只维护一个字典表,程序的代码量,会好很多。我也没有什么可说的了。有一个技巧可能提高你可变哑员的编程。

就是用一个列表(诸如前面说的)的可变哑员,用一个字典用于可改变顺序的哑员。



def func(a,b,c,d=None,e=1):

print a,b,c,d,e

arg=[1.5,"a",1] # 定义一个哑员变量表

kwarg={"d":None,"e":2} #定义一个字典哑员表

func_dict={"func":(func,arg,kwarg)} #把函数对象放在字典里

func_dict["func"][0](*func_dict["func"][1],**func_dict["func"][2]) #函数调用

复制代码
具体函数哑员及调用的例子,参考我翻译的文稿:http://bbs.sciencenet.cn/blog-565112-523776.html

21楼:Sasoritattoo

回复 20# Hadron74

受教了,谢谢!

已经加了哑元键参数作为默认值。

如果没猜错的话,@Hadron74兄竟然是跨生物信息学界和编程界的博士后,而且自己业余时间翻译了一本python教材书,非常的令人倾佩!

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐