- Preface 前言
- 第1章 引论
- 第2章 编程惯用法
- 第3章 基础语法
- 建议19:有节制地使用 from…import 语句
- 建议20:优先使用 absolute import 来导入模块
- 建议21:i+=1 不等于 ++i
- 建议22:使用 with 自动关闭资源
- 建议23:使用 else 子句简化循环(异常处理)
- 建议24:遵循异常处理的几点基本原则
- 建议25:避免 finally 中可能发生的陷阱
- 建议26:深入理解 None 正确判断对象是否为空
- 建议27:连接字符串应优先使用 join 而不是 +
- 建议28:格式化字符串时尽量使用 .format 方式而不是 %
- 建议29:区别对待可变对象和不可变对象
- 建议30:[]、() 和 {}:一致的容器初始化形式
- 建议31:记住函数传参既不是传值也不是传引用
- 建议32:警惕默认参数潜在的问题
- 建议33:慎用变长参数
- 建议34:深入理解 str() 和 repr() 的区别
- 建议35:分清 staticmethod 和 classmethod 的适用场景
- 第4章 库
- 建议36:掌握字符串的基本用法
- 建议37:按需选择 sort() 或者 sorted()
- 建议38:使用 copy 模块深拷贝对象
- 建议39:使用 Counter 进行计数统计
- 建议40:深入掌握 ConfigParser
- 建议41:使用 argparse 处理命令行参数
- 建议42:使用 pandas 处理大型 CSV 文件
- 建议43:一般情况使用 ElementTree 解析 XML
- 建议44:理解模块 pickle 优劣
- 建议45:序列化的另一个不错的选择 JSON
- 建议46:使用 traceback 获取栈信息
- 建议47:使用 logging 记录日志信息
- 建议48:使用 threading 模块编写多线程程序
- 建议49:使用 Queue 使多线程编程更安全
- 第5章 设计模式
- 第6章 内部机制
- 建议54:理解 built-in objects
- 建议55:init() 不是构造方法
- 建议56:理解名字查找机制
- 建议57:为什么需要 self 参数
- 建议58:理解 MRO 与多继承
- 建议59:理解描述符机制
- 建议60:区别 getattr() 和 getattribute() 方法
- 建议61:使用更为安全的 property
- 建议62:掌握 metaclass
- 建议63:熟悉 Python 对象协议
- 建议64:利用操作符重载实现中缀语法
- 建议65:熟悉 Python 的迭代器协议
- 建议66:熟悉 Python 的生成器
- 建议67:基于生成器的协程及 greenlet
- 建议68:理解 GIL 的局限性
- 建议69:对象的管理与垃圾回收
- 第7章 使用工具辅助项目开发
- 第8章 性能剖析与优化
建议6:编写函数的4个原则
函数能够带来最大化的代码重用和最小化的代码冗余。精心设计的函数不仅可以提高程序的健壮性,还可以增强可读性、减少维护成本。先来看以下示例代码:
def SendContent(ServerAdr,PagePath,StartLine,EndLine,sender, receiver,smtpserver,username,password): http = httplib.HTTP(ServerAdr) http.putrequest('GET', PagePath) http.putheader('Accept', 'text/html') http.putheader('Accept', 'text/plain') http.endheaders() httpcode, httpmsg, headers = http.getreply() if httpcode != 200:raise "Could not get document: Check URL and Path." doc = http.getfile() data = doc.read() doc.close() lstr=data.splitlines() j=0 for i in lstr: j=j+1 if i.strip() == StartLine: slice_start=j #find slice start elif i.strip() == EndLine: slice_end=j #find slice end subject = "Contented get from the web" msg = MIMEText(string.join(lstr[slice_start:slice_end]),'plain','utf-8') msg['Subject'] = Header(subject, 'utf-8') smtp = smtplib.SMTP() smtp.connect(smtpserver) smtp.login(username, password) smtp.sendmail(sender, receiver, msg.as_string()) smtp.quit()
函数SendContent主要的作用是抓取网页中固定的内容,然后将其发送给用户。代码本身并不复杂,但足以说明一些问题。读者可以先思考一下:到底有什么不是之处?能否进一步改进?怎样才能做到一目了然呢?一般来说函数设计有以下基本原则可以参考:
原则1 函数设计要尽量短小,嵌套层次不宜过深。所谓短小,就是跟前面所提到的一样尽量避免过长函数,因为这样不需要上下拉动滚动条就能获得整体感观,而不是来回翻动屏幕去寻找某个变量或者某条逻辑判断等。函数中需要用到if、elif、while、for等循环语句的地方,尽量不要嵌套过深,最好能控制在3层以内。相信很多人有过这样的经历:为了弄清楚哪段代码属于内部嵌套,哪段属于中间层次的嵌套,哪段属于更外一层的嵌套所花费的时间比读代码细节所用时间更多。
原则2 函数申明应该做到合理、简单、易于使用。除了函数名能够正确反映其大体功能外,参数的设计也应该简洁明了,参数个数不宜太多。参数太多带来的弊端是:调用者需要花费更多的时间去理解每个参数的意思,测试人员需要花费更多的精力来设计测试用例,以确保参数的组合能够有合理的输出,这使覆盖测试的难度大大增加。因此函数参数设计最好经过深思熟虑。
原则3 函数参数设计应该考虑向下兼容。实际工作中我们可能面临这样的情况:随着需求的变更和版本的升级,在前一个版本中设计的函数可能需要进行一定的修改才能满足这个版本的要求。因此在设计过程中除了着眼当前的需求还得考虑向下兼容。如以下示例:
>>> def readfile(filename): ①函数实现的第一版本 ... print "file read completed" ... >>> readfile("test.txt") file read completed >>> >>> import logging >>> >>> def readfile(filename,logger): ②函数实现的第二版本 ... print "file read completed" ... >>> readfile("test.txt") Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: readfile() takes exactly 2 arguments (1 given)
上面的代码是相同功能的函数不同版本的实现,唯一不同的是在更高级的版本中为了便于内部维护加入了日志处理,但这样的变动却导致程序中函数调用的接口发生了改变。这并不是最佳设计,更好的方法是通过加入默认参数来避免这种退化,做到向下兼容。上例可以将第一行代码修改为:
def readfile(filename,logger=logger.info):
原则4 一个函数只做一件事,尽量保证函数语句粒度的一致性。如本节开头所示代码中就有3个不同的任务:获取网页内容、查找指定网页内容、发送邮件。要保证一个函数只做一件事,就要尽量保证抽象层级的一致性,所有的语句尽量在一个粒度上。如上例既有http.getfile()这样较高层级抽象的语句,也有细粒度的字符处理语句。同时在一个函数中处理多件事情也不利于代码的重用,在上例中,如果程序中还有类似发送邮件的需求,必然造成代码的冗余。
最后,根据以上几点原则,上面对本节最开始处的代码进行修改,来看看修改后的代码是不是可读性要好一些。
def GetContent(ServerAdr,PagePath): http = httplib.HTTP(ServerAdr) http.putrequest('GET', PagePath) http.putheader('Accept', 'text/html') http.putheader('Accept', 'text/plain') http.endheaders() httpcode, httpmsg, headers = http.getreply() if httpcode != 200: raise "Could not get document: Check URL and Path." doc = http.getfile() data = doc.read() # read file doc.close() return data def ExtractData(inputstring, start_line, end_line): lstr=inputstring.splitlines() #split j=0 #set counter to zero for i in lstr: j=j+1 if i.strip() == start_line: slice_start=j #find slice start elif i.strip() == end_line: slice_end=j #find slice end return lstr[slice_start:slice_end] #return slice def SendEmail(sender,receiver,smtpserver,username,password,content): subject = "Contented get from the web" msg = MIMEText(content,'plain','utf-8') msg['Subject'] = Header(subject, 'utf-8') smtp = smtplib.SMTP() smtp.connect(smtpserver) smtp.login(username, password) smtp.sendmail(sender, receiver, msg.as_string()) smtp.quit()
Python中函数设计的好习惯还包括:不要在函数中定义可变对象作为默认值,使用异常替换返回错误,保证通过单元测试等。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论