原文发表在我的博客主页,转载请注明出处!

建议二十八:区别对待可变对象和不可变对象

python中一切皆对象,每一个对象都有一个唯一的标识符(id())、类型(type())以及值,对象根据其值能否修改分为可变对象和不可变对象,其中数字、字符串、元组属于不可变对象,字典以及列表、字节数组属于可变对象。

来看一段程序:

class Student(object):
def __init__(self,name,course=[]):
self.name = name
self.course = course def addcourse(self,coursename):
self.course.append(coursename) def printcourse(self):
for item in self.course:
print item xl = Student('xl')
xl.addcourse('computer')
xl.addcourse('automation')
print xl.name + "'s course:"
xl.printcourse()
print "~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
cyj = Student('cyj')
cyj.addcourse('software')
cyj.addcourse('NLP')
print cyj.name + "'s course:"
cyj.printcourse()

运行结果会让初学者大吃一惊:

xl's course:
computer
automation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cyj's course:
computer
automation
software
NLP

通过查看xl和cyj的course变量的id,发现他们的值是一样的,即指向内存中的同一块地址,但是xl和cyj却是两个不同的对象。在实例化这两个对象的时候,这两个对象被分配了不同的内存空间,并且调用init()函数进行初始化,但由于init()函数的第二个参数是个默认参数,默认参数在函数调用的时候仅仅被评估一次,以后都会使用第一次评估的结果,因此实际上对象空间里面course所指向的是list的地址,这时我们在将可变对象作为默认参数的时候要警惕的,对可变对象的更改会直接影响原对象,可以用如下方式解决:

    def __init__(self,name,course=None):
self.name = name
if course is None:course = []
self.course = course

对于不可变对象来说,当我们对其进行相关操作的时候,python实际上仍然保存原来的值,重新创建一个新的对象。当有两个对象同时指向一个字符串对象的时候,对其中一个对象的操作并不会影响另一个对象。比如:

str1 = "write pythonic code"
str2 = str1
str1 = str1[-4:]
print id(str1)
print id(str2)
print str1
print str2

建议二十九:和{}:一致性容器初始化形式

列表是一个很有用的数据结构,由于其灵活性在实际应用中被广泛使用。对于列表来说,列表解析十分常用。

列表解析的语法如下,它迭代iterable中的每一个元素,当条件满足的时候便根据表达式expr计算的内容生成一个元素并放入新的列表中,依次类推,最终返回整个列表。

[expr for iter_item in iterable if cond_expr]

列表解析的使用非常灵活:

  • 支持多重嵌套,如果需要生成一个二维列表可以使用列表解析嵌套的方式
nested_list = [['Hello', 'World'],['Goodbye', 'World']]
nested_list = [[ele.upper() for ele in word] for word in nested_list]
  • 支持多重迭代
[(a,b) for a in ['1', '2', '3', '4'] for b in ['a', 'b', 'c', 'd'] if a != b]
  • 表达式可以是简单表达式,也可以是复杂表达式,甚至是函数
def f(v):
if v%2 == 0:
v = v ** 2
else:
v = v + 1
return v
print [f(v) for v in [1,2,3,-1] if v > 0]
print [v ** 2 if v %2 == 0 else v + 1 for v in [1,2,3,-1] if v > 0]
  • iterable可以是任意可迭代对象
fp = open('wdf.py','r')
res = [i for i in fp if 'weixin' in i]
print res

为什么要推荐在需要生成列表的时候使用列表解析呢?

  • 使用列表解析更为直观清晰,代码更为简洁
  • 列表解析的效率更高,但是对于大数据处理,列表解析并不是一个最佳选择,过多的内存消耗可能会导致MemoryError

除了列表可以使用列表解析的语法之外,其他内置的数据结构也支持,如下:

#generator
(expr for iter_item in iterable if cond_expr)
#set
{expr for iter_item in iterable if cond_expr}
#dict
{expr1: expr2 for iter_item in iterable if cond_expr}

建议三十:记住函数传参既不是传值也不是传引用

以往关于python中函数传参数有三种观点:

  • 传值
  • 传引用
  • 可变对象传引用,不可变对象传值

这些理解都是有些偏差的,python中的赋值与我们所理解的C/C++等语言的赋值的意思并不一样。以如下语句为例来看C/C++和python是如何运作的

a = 5, b= a, b = 7

C/C++中当执行b=a的时候,在内存中申请一块内存并将a的值复制到该内存中,当执行b=7之后是将b对应的值从5修改到7

python中赋值并不是复制,b=a操作使得ba引用同一对象,而b=7则是将b指向对象7

因此,对于python函数参数既不是传值也不是传引用,应该是传对象或者说传对象的引用。函数参数在传递的过程中将整个对象传入,对可变对象的修改在函数外部以及内部都可见,调用者和被调用者之间共享这个对象,而对于不可变对象,由于并不能真正被修改,因此,修改往往是通过生成一个新对象然后赋值来实现的。


建议三十一:慎用变长参数

python支持可变长度的参数列表,可以通过在函数定义的时候使用args和**kwargs这两个特殊语法来实现。

使用
args来实现可变参数列表:*args用于接收一个包装为元组形式的参数列表来传递非关键字参数,参数个数可以任意:

def sumf(*args):
res = 0
for x in args[:]:
res += x
return res print sumf(2,3,4)
print sumf(1,2,3,4,5)

使用**kwargs接收字典形式的关键字参数列表,其中字典的键值分别表示不可变参数的参数名和值:

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')

当普通参数,默认参数,和上述两种参数同时存在的时候,会优先给普通参数和默认参数赋值,为什么要慎用可变长参数呢?

  • 使用过于灵活,是代码不够清晰
  • 如果一个函数的参数列表很长,虽然可以通过使用*args和**kwargs来简化函数的定义,但通常意味着这个函数可以有更好的实现方式,应该被重构。
  • 可变长参数适合在下列情况下使用:
  • 为函数添加一个装饰器
  • 如果参数的数目不确定,可以考虑使用变长参数
  • 用来实现函数的多态或者在继承情况下子类需要调用父类的某些方法的时候

参数三十二:深入理解str()和repr()的区别

这两个方法都可以将python中的对象转换为字符串,他们的使用以及输出都非常相似,区别呢?

  • 两者之间的目标不同:str()主要面向用户,其目的是可读性,返回形式为用户友好性和可读性都较强的字符串类型,而repr()面向python解释器,或者说开发人员,其目的是准确性,其返回值表示python解释器的内部函数,常用作debug
  • 在解释器中直接输入a时默认调用repr()函数,而print a则调用str()函数
  • repr()的返回值一般可以用eval()函数来还原
obj == eval(repr(obj))
  • 这两个方法分别调用内建的__str____repr__()方法,一般来说在类中都应该定义后者,而前者方法则为可选,如果没有,默认使用后者的结果来返回对象的字符串表示形式

建议三十三:分清staticmethod和classmethod的使用场景

python中的静态方法(staticmethod)和类方法(classmethod)都依赖于装饰器来实现,用法如下:

#staticmethod
class C(object):
@staticmethod
def f(arg1, arg2, ...):
#classmethod
class C(object):
@classmethod
def f(arg1, arg2, ...):

静态方法和类方法都可以通过类名.方法名或者实例.方法名的形式来访问。其中静态方法没有常规方法的特殊行为,如绑定、非绑定、隐式参数等规则,而类方法的调用使用类本身作为其隐含参数,但调用本身并不需要显示提供该参数。

那为什么需要静态方法和类方法呢?假设有水果类Fruit,它用属性total表示总量,用set()来设置重量,print_total()方法来打印水果数量。类Apple和类Orange继承自Fruit,现需要分别跟踪不同类型的水果的总量,实现方法汇总:

  • 利用普通的实例方法来实现:在Apple和Orange类中分别定义类变量total,然后覆盖基类的set()和print_total()方法
  • 使用类方法实现
class Fruit(object):
total = 0
def print_total(cls):
print cls.total
@classmethod
def set(cls, value):
cls.total = value
class Apple(Fruit):
pass
class Orange(Fruit):
pass
app1 = Apple()
app1.set(200)
app2 = Apple()
org1 = Orange()
org1.set(300)
org2 = Orange()
app1.print_total()
org1.print_total()

简单分析可知,针对不同种类的水果对象调用set()方法的时候隐形传入的参数为该对象所对应的类,在调用set()的过程中动态生成了对应的类的类变量。

静态方法一般适用于既不跟特定的实例相关也不跟特定的类相关的方法。他存在于类中,较之外部函数能够更加有效的将代码组织起来,从而使相关代码的垂直距离更近,提高代码的可维护性。

参考:编写高质量代码--改善python程序的91个建议

编写高质量代码--改善python程序的建议(六)的更多相关文章

  1. 编写高质量代码--改善python程序的建议(八)

    原文发表在我的博客主页,转载请注明出处! 建议四十一:一般情况下使用ElementTree解析XML python中解析XML文件最广为人知的两个模块是xml.dom.minidom和xml.sax, ...

  2. 编写高质量代码--改善python程序的建议(七)

    原文发表在我的博客主页,转载请注明出处! 建议三十四:掌握字符串的基本用法 编程有两件事,一件是处理数值,另一件是处理字符串,在商业应用编程来说,处理字符串的代码超过八成,所以需要重点掌握. 首先有个 ...

  3. 编写高质量代码–改善python程序的建议(五)

    原文发表在我的博客主页,转载请注明出处! 建议二十三:遵循异常处理的几点基本原则 python中常用的异常处理语法是try.except.else.finally,它们可以有多种组合,语法形式如下: ...

  4. 编写高质量代码--改善python程序的建议(四)

    原文发表在我的博客主页,转载请注明出处! 建议十八:有节制的使用from...import语句 python提供了三种方式引入外部模块: import语句 from...import... __imp ...

  5. 编写高质量代码--改善python程序的建议(三)

    原文发表在我的博客主页,转载请注明出处! 建议十三:警惕eval()的安全漏洞 相信经常处理文本数据的同学对eval()一定是欲罢不能,他的使用非常简单: eval("1+1==2" ...

  6. 编写高质量代码–改善python程序的建议(二)

    原文发表在我的博客主页,转载请注明出处! 建议七:利用assert语句来发现问题断言(assert)在很多语言中都存在,它主要为调试程序服务,能够快速方便地检查程序的异常或者发现不恰当的输入等,可防止 ...

  7. 编写高质量代码--改善python程序的建议(一)

    原文发表在我的博客主页,转载请注明出处! 初衷 python是一个入门十分容易的编程语言,但是想要写好python却是一件不容易的事情,如果不是专业使用python的人,只是将python作为一个脚本 ...

  8. 编写高质量代码改善python程序91个建议学习01

    编写高质量代码改善python程序91个建议学习 第一章 建议1:理解pythonic的相关概念 狭隘的理解:它是高级动态的脚本编程语言,拥有很多强大的库,是解释从上往下执行的 特点: 美胜丑,显胜隐 ...

  9. 编写高质量代码 改善Python程序的91个建议 (读后 小记)

    此书是自己好久之前买的,当时总觉得Python语言中有各种trick, 总是要自己猝不及防的掉入到陷阱之中, 看了一些资料后发现了这本书,感觉很是不错,不过可惜自己平时总是杂事太多,总是找不到整块的时 ...

随机推荐

  1. JavaScript闭包的底层运行机制

    转自:http://blog.leapoahead.com/2015/09/15/js-closure/ 我研究JavaScript闭包(closure)已经有一段时间了.我之前只是学会了如何使用它们 ...

  2. 【mysql】关于临时表

    mysql官方的介绍 In some cases, the server creates internal temporary tables while processing queries. Suc ...

  3. solrcloud 配置实践

    1.环境 3台虚拟机:192.168.26.129.192.168.26.131.192.168.26.132,使用命令sudo iptables -F 关闭防火墙 Solr: solr-6.1.0 ...

  4. 一次简单的MySQL数据库导入备份

    任务目的:把现网数据库(MySQL5.5,windows)中的内容导入到测试数据库(MySQL5.1,linux)中 1.由于对MySQL并不熟悉,一上来我先考虑方案是用现成的数据库管理工具来处理.我 ...

  5. CentOS6.3下安装VSFTP服务

    CentOS下安装FTP服务器: 第一步,检查服务器端是否已经安装FTP:[root@localhost centos]# rpm -q vsftpd 如果出现的是:[root@localhost c ...

  6. 【VB超简单入门】二、知识准备

    在开始编程之前,需要先熟悉一下各种操作和术语,以后学习编程才能得心应手. 首先最重要的操作当然就是-电脑的开机关机啦~(开个玩笑哈哈),必须掌握软件的安装和卸载,还有能编写批处理程序对平时的使用也是很 ...

  7. zookeeper适用场景:zookeeper解决了哪些问题

    问题导读:1.master挂机,传统做法备份必然是以前数据,该如何保证挂机数据与备份数据一致?2.分布式系统如何实现对同一资源的访问,保证数据的强一致性?3.集群中的worker挂了,传统做法是什么? ...

  8. μc/osⅡ简化版任务机制浅析

    去年玩过一阵单片机,也用过μc/osⅡ的系统,但是就理解内核而言,整个即时操作系统还是过于冗杂,很多的东西很不适合初学者来动手操作,多方查找我发现他的任务机制可以进行功能的进一步简化, 可以类似于任务 ...

  9. codeforces 484D D. Kindergarten(dp)

    题目链接: D. Kindergarten time limit per test 2 seconds memory limit per test 256 megabytes input standa ...

  10. GitHub Top 100的Android开源库

    摘要: 本项目主要对目前 GitHub 上排名前 100 的 Android 开源库进行简单的介绍, 至于排名完全是根据GitHub搜索Java语言选择「Best M... 本项目主要对目前 GitH ...