Python踩坑日记——可变形参辟新路,制表tab蕴新意

本文深入探讨Python编程语言的独特之处,包括变量的引用语义、默认参数的陷阱以及正确的编码风格。通过实例解析,读者将理解Python如何高效地处理变量和数据,避免常见错误,提升代码质量。

我叫“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

 

 

好吧,打完收工,结束了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值