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

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

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. 快速解决mysql Lost connection to MySQL server at 'reading initial communication packet及can't connect to mysql server on 'localhost'

    今天在使用Navicat连一个远程mysql时,总是提示连接不成功,提示Lost connection to MySQL server at 'reading initial communicatio ...

  2. Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境搭建教程

    准备篇 一.环境说明: 操作系统:Windows Server 2012 R2 PHP版本:php 5.5.8 MySQL版本:MySQL5.6.15 二.相关软件下载: 1.PHP下载地址: htt ...

  3. strcat()函数常见问题

    strcat(char *_Destination,const char *_Source)函数的功能是将后一个字符串粘贴到前一个字符串的末尾 原型 char *strcat(char *_Desti ...

  4. linux命令后台运行

    有两种方式: 1. command & : 后台运行,你关掉终端会停止运行    2. nohup command & : 后台运行,你关掉终端也会继续运行 一. 简介     Lin ...

  5. matlab里.*和*的区别

    *:矩阵相乘 (cross) .*:矩阵你元素一对一相乘 (dot) 例子: >> a=[2 3];>> b=[4 5];>> a*b' ans = 23 > ...

  6. 三星嵌入式开发平台 三星Cortex-A9 4412 POP与SCP对比

    iTOP-4412核心板是迅为电子推出的一款高端四核核心板,其中分为POP封装与SCP封装,配备三星Exynos 4412四核处理器,主频为1.4GHz,内置16GB存储空间.该板设计小巧.配备三星自 ...

  7. Web安全--XSS现代WAF规则探测及绕过技术

    XSS现代WAF规则探测及绕过技术初始测试 1.使用无害的payload,类似<b>,<i>,<u>观察响应,判断应用程序是否被HTML编码,是否标签被过滤,是否过 ...

  8. gym101090 I Painting the natural numbers

    题目地址:http://codeforces.com/gym/101090 题目: The H&H company currently develops AI (artificial inte ...

  9. C++基础笔记(四)C++内存管理

    析构函数 * 析构函数在对象所占用内存释放时调用,通常用来释放相关的资源 * 析构函数就是一个特殊的类成员函数,它是构造函数相反 构造函数:对象在分配内存之后,立即调用 析构函数:对象在内存被释放之前 ...

  10. xshell5 启动显示 mfc110.dll msvcp110.dll 未找到问题 解决办法

    1. 安装 Visual C++ Redistributable for Visual Studio 2012 x86版本 注意: 一定要安装x86版本.(xshell5是32位的程序) 微软的官方下 ...