这两天整服务器代码遇到如题所说问题,不得已上了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教材书,非常的令人倾佩! |