我叫“Snake”,在我佛如来的指引下,我开始了一段取经之路。猴哥的称号是“斗战圣佛”,我的称号是“Python”。一条蛇的取经故事,我称之为《蛇经》。
进入话题前,先考虑点基本的。虽然对世界本质的认识与否,并不会影响到你对吃饭的态度,但会改变你对饭的态度。或许你会认为你吃的是“物质”,是“能量”,是“弦”等等,肯定不会在每次吃完后,只有一句“真香”。
我们比较熟悉的高级语言有c,java还有我们正在说的python。是否注意到,c和java中,定义变量这般如此:
int c = 1;
而python中是如此这般:
c = 1
“当然注意到啦~ python不用定义变量类型!”
“Why?”
“............”
有时的沉默是智慧,但大多数是因为无知。首先,我们知道“=”被称作赋值号。简单说就是右边的值给左边的变量,我们只需要弄清楚一个道理,上面的“.............”就可以变成“bla,bla,bla......”。
变量里面存储的是什么?
对于c和java,变量存储的就是赋值号右面的值,定义变量时要指明类型是因为右面的值的类型不同,内存中需要留的空间大小就不同。换而言之,不同类型的值,占得空间大小不一样,变量指明类型,是为了给变量的值留空儿。
python的不同之处在于,变量里不存值,而是存值的引用,或者说是值的地址。(我认为理解成值的指针也是没有什么问题的,认识不妥,欢迎评论指出。)这也就是为什么python变量不用指明类型的原因,因为它不存值得内容,只是值的地址。
变量存储的都是地址,类型上也就没有了区分,也就无需区别对待,那类型的定义也就可有可无了。(代码层面上,而不是可读性上。)
python中变量的这种实现方式称为变量的引用语义,而c和java中变量的实现方式称为变量的值语义。
拿个栗子过来~,就c来说(值语义),执行两条代码。
图片右边为存储空间,首先int a = 0,在内存里给a找个int类型大小的空间(我们假设它占一个格),之后的赋值操作,就是把0写进a留出来的int大小的空间里。
下条语句,a = 1。把a空间里的值修改为1。
这里提一点,计算机中对某块存储空间的修改是“覆盖”,而不是我们固有思维认为的那样,先把当前内容删除,再把当前更新的值写入。Why?
我们知道,计算机中只有0和1,物理层面的数据存储同样是只用高电位和低电位。当我需要修改一片存储空间的内容时,其实是把那片空间的电位修改掉。完全可以直接从指定空间开始,把每个存储空间的电位改成当前的更新内容。把指定区域的电位先都置于零,在从头来遍,把当前内容写入的操作,实属多此一举。
我们有时会不小心删除掉U盘里的东西,偶尔可以通过一些还原软件恢复。原理呢?
原理就是上面那个,你删除操作后,实际的物理数据并没有删除掉。形象点,你删除了100-200位置之间的数据,我只是做了个标记。标记100-200的空间可用,这时候如有新的数据入驻,这块区域就会被覆盖掉,换成新数据。而不是你点完删除后,我把所有数据清零,来数据后,再写入新数据。因为上面说了,实属多此一举。
这也是为什么那些恢复软件告诉你,如果U盘不小心删除了东西,立马停止其他的操作,然后进行恢复。就是怕你往里放新的数据覆盖掉啊,覆盖掉就是真的莫得了,彻底的恢复不出来了(你有备份除外~)。
引用语义:
按0的类型存储0,变量a存储0的地址(或者说引用)。下条语句a = 1,存储下1,a存储的地址变成1的地址。如果换成指针理解,就是a由指向0变为了指向1。不用担心0这些不在被“指向”的数据会浪费内存,python的垃圾回收机制会自动处理掉的。
(其实这里的描述并不准确,变量是内存及其地址的抽象,变量本身也需要在内存中安排地址。用批判的的态度理解上述叙述。)
看了世间百态,再回来唠我们的家事,关于可变参数另辟新路的问题。
先来看这么两行代码:
def f(a, b=1):
return a+b
b = 100
print(f(1))
print(f(2))
print(f(3))
结果是什么?很侮辱智商的代码,函数的默认参数b是形式参数,和函数外的b无关。每次调用f函数,将参数a加一返回:
一个毫无悬念的结果。
再来,稍加修改。
def f(a, b=[]):
b.append(a)
return b
print(f(1))
print(f(2))
print(f(3))
结果是什么?
[1]
[2]
[3]
你的答案如果是上面这个的话,这个坑你是踩定了。
每次调用函数f的时候,形参b还记录着上一步的操作。Why?
先说个网站:
http://www.pythontutor.com/visualize.html#mode=edit
可以将程序运行过程中,存储情况图形化展现出来。
函数定义后:
执行完f(1)
执行f(2)
f(3)的执行过程就相同了,不予展示,自己动手试下。
结合着我们开篇说的变量表达形式,尝试着理解下这个问题。
形参b存储的是开始空list的地址,而当我们b.append()的时候,其实是对b所指地址的list进行了append。下次调用时,b存储的地址还是那个list,但是此list非彼list,因为list中已经有个一个元素1。
此种情况为默认参数为可变对象时的情况,当默认参数为不可变对象时,用同样的方式理解也是没有问题的。开始b=1的那个例子我们过掉,在序列对象中,有一个是不可变类型的——tuple。
如果将默认参数换成tuple会怎样?
def f(a, b=(0, )):
b += (a, )
return b
print(f(1))
print(f(2))
print(f(3))
结果应该都想到了,很正常的情况。
看下变量的存储情况:
和list就很不相同了,tuple属于不可变对象,给tuple b赋值 b + a的时候,因为tuple不可变的原因,并没有在tuple b后面直接添加tuple a的内容,而是创建了一个新的tuple。
每次调用,都会生成一个新的tuple,自然前后的操作就不会影响了。
内部机制的理解上,已经变得很简单了吧。
可变形参另辟新路的事,我们就先说到这里。这次还有一位有趣的人物等着露面呢——tab。
写代码的时候,你经常用它缩进,可以说是好不熟悉。但你真的了解它吗?
tab(tabulator key),制表键或者制表符。基本的用法是绘制无表框表格。一般情况下等于八个空格的长度,制表符的宽度(size)是一个可配置属性,在各种编辑器中会有不同的优化,比如在python的IDLE中,就是4个空格的长度。
注意,坑来了,制表符的占宽加上制表符前的词长度是制表符宽度(size)的整数倍。
解释先放下不谈,看个例子。在txt文件中,tab制表符的宽度是8个空格长度。
在txt中按个tab再在后面打个“叉”,当做标记。下一行用一堆字母来看看,tab的宽度是多少。
8个‘a’的长度(别问这里为啥不用空格展示,空格你看的见吗?)。
我贴个这种图,说tab占宽8个空格长度,你信吗?
在python的IDLE中,你可以用同样的方式测试下tab占宽,是4个空格长度。
别急,马上解释上面的那句话。我们用代码,往txt里写这么个字符串:'This\tis\ta\tbook\t.'。
也就是将“This is a book.”输入,空格换成tab看看效果。
样子有点怪,别急,我们一点点看。用同样的办法,我们手动在下一行输点什么东西,看tab的占宽。
有字母的地方我们用‘x’,tab的占宽处用‘a’。
先看第一个“This”,词长4个字节,后tab占宽4个。我们前面说到,在txt中tab制表符宽度是8(size)。
再看这句话:
制表符的占宽加上制表符前的词长度是制表符宽度(size)的整数倍。
下个词“is”词长为2,为了满足上述,tab的占宽就应该是8-2=6。
同理,“a”长1,tab占宽7。
“book”和“This”同,tab占宽4。
如果换成中文会是怎样?
一样的,知道每个汉字占两个字节长度即可。
如果,词长度超过了8呢?
输入9个字母后,打个tab。
7个a的长度,因为2 x 8 - 9 = 7。无需解释,再看这句话:
制表符的占宽加上制表符前的词长度是制表符宽度(size)的整数倍。
为什么要把tab单一拿出来谈谈呢。是因为编程中我们通常用tab进行缩进来使代码变得美观大方,但是tab制表符宽度在不同编辑器中是不一样的。也因此,为了我们4个字符长度的缩进标准,应当尽量用4个空格来代替tab缩进。
说到编码风格,我们不妨来谈谈。
1.使用4空格缩进,而非tab。(tab是制表符,而不是缩进符,虽然很多软件支持tab缩进,这是因为编程软件本身对tab键做了优化处理,将tab转换成了4或8个空格。但是注意,并不是所有的编程软件都做了这样的工作。)
2.每行不要太长,使用折行,确保每行不超过79个字符。
3.空行分割函数和类,以及函数中的大块代码。
比如:
4.最大可能的让注释独占一行。
5.使用文档字符串。
问题来了,什么是文档字符串。嗯,我们知道,Python里面我们无需定义变量类型,有优点,但是也有劣处。比如,可读性差,一个函数的参数,我们并不能确定形参是什么类型,文档字符串就是一种说明函数功能的注释。
文档字符串怎么写呢?documentation string定义为用来解释接口的字符串,就是帮助文档。其中包含函数基础信息,功能简介,形参类型。
在使用风格方面的注意:
必须在函数首行。
使用三引号多行注释
第一行一般概述函数主要功能,第二行空,第三行详细描述。
一个并不一定准确的例子:
6.空格放在操作符两边,及逗号后面,但是括号内侧除外。
7.自定义类名使用驼峰命名法。比如一个函数的功能是找到最大值,我们可以这样命名类:
def findMaxValue(a, b):
pass
好吧,打完收工,结束了。