- 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章 性能剖析与优化
建议33:慎用变长参数
Python支持可变长度的参数列表,可以通过在函数定义的时候使用*args和**kwargs这两个特殊语法来实现(args和kwargs可以替换成任意你喜欢的变量名)。先来看两个可变长参数使用的例子。
1)使用*args来实现可变参数列表:*args用于接受一个包装为元组形式的参数列表来传递非关键字参数,参数个数可以任意,如下例所示。
def SumFun(*args): result = 0 for x in args[0:]: result += x return result print SumFun(2,4) print SumFun(1,2,3,4,5) print SumFun()
2)使用**kwargs接受字典形式的关键字参数列表,其中字典的键值对分别表示不可变参数的参数名和值。如下例中apple表示参数名,而fruit为其对应的value,可以是一个或者多个键值对。
def category_table(**kwargs): for name, value in kwargs.items(): print '{0} is a kind of {1}'.format(name, value) category_table(apple = 'fruit', carrot = 'vegetable',Python = 'programming language') category_table(BMW = 'Car')
如果一个函数中同时定义了普通参数、默认参数,以及上述两种形式的可变参数,那么使用情况又是怎样的呢?来看一个简单的问题。
def set_axis(x,y, xlabel="x", ylabel="y", *args, **kwargs): pass
对于上面的函数下面几种调用方式哪些是不正确的呢?
set_axis(2,3, "test1", "test2","test3", my_kwarg="test3") ① set_axis(2,3, my_kwarg="test3") set_axis(2,3, "test1",my_kwarg="test3") ② set_axis("test1", "test2", xlabel="new_x",ylabel = "new_y", my_kwarg="test3") set_axis(2,"test1", "test2",ylabel = "new_y", my_kwarg="test3")
答案是:上面的所有调用方式都是合法的!实际上在4种不同形式的参数同时存在的情况下,会首先满足普通参数,然后是默认参数。如果剩余的参数个数能够覆盖所有的默认参数,则默认参数会使用传递时候的值,如标注①处的函数在调用的时候xlabel和ylabel的值分别为“test1”和“test2”;如果剩余参数个数不够,则尽最大可能满足默认参数的值,标注②中xlabel值为“test1”,而ylabel则使用默认参数y。除此之外其余的参数除了键值对以外所有的参数都将作为args的可变参数,kwargs则与键值对对应。那么,为什么要慎用可变长度参数呢?原因如下:
1)使用过于灵活。上面的示例set_axis()随随便便就能写出好几种不同形式的调用方式。在混合普通参数或者默认参数的情况下,变长参数意味着这个函数的签名不够清晰,存在多种调用方式。如果调用者需要花费过多的时间来研究你的方法该如何调用,显然这并不是一种值得提倡的方式,即使你认为它很炫,很高端大气上档次,但在团队合作开发的项目中,千万不要有这种想法,团队开发不是你的个人竞技场,代码编写从某种程度上说也是一种沟通,清晰准确是一个很重要的指标。另外变长参数可能会破坏程序的健壮性。
2)如果一个函数的参数列表很长,虽然可以通过使用*args和**kwargs来简化函数的定义,但通常这意味着这个函数可以有更好的实现方式,应该被重构。前面的例子中SumFun(*args)如果改为def Sum(seq),在函数调用之前将需要传递的参数保存在一个序列中,如seq = (1,2,3,4,5,6,7),再将序列seq作为参数传入Sum中也是一个不错的选择。同样如果使用**kwargs的目的仅仅是为了传入一个字典,这将是一个非常糟糕的选择。
3)可变长参数适合在下列情况下使用(不仅限于以下场景):
为函数添加一个装饰器。
>>> def mydecorator(fun): ... def new(*args,**kwargs): ... # ... ... return fun(*args,**kwargs) ... return new ... >>>
如果参数的数目不确定,可以考虑使用变长参数。如配置文件test.cfg内容如下:
[Defaults] name = test version = 1.0 platform = windows func(**kwargs) 用于读取一些配置文件中的值并进行全局变量初始化。 from ConfigParser import ConfigParser conf = ConfigParser() conf.read('test.cfg') conf_dict = dict(conf.items('Defaults')) def func(**kwargs): kwargs.update(conf_dict) global name name = kwargs.get('name') global version version = kwargs.get('version') global platform platform = kwargs.get('platform')
用来实现函数的多态或者在继承情况下子类需要调用父类的某些方法的时候。
>>> class A(object): ... def somefun(self,p1,p2): ... pass ... >>> class B(A): ... def myfun(self,p3,*args,**kwargs): ... super(B,self).somefun(*args,**kwargs) ... >>>
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论