python学习(1) —常容易忽视的错误

一、整数比较中的“is”和“==”

  • is 比较的是两个整数对象的id值是否相等,也就是比较两个引用是否代表了内存中同一个地址。
  • == 比较的是两个整数对象的内容是否相等,使用时其实是调用了对象的**__ eq__()**方法。

撸代码:

def main():
	x = y = -1
	while True:
		x += 1
		y += 1
		if x is y:
			print('%d is %d' % (x, y))
		else:
			print('Attention! %d is not %d' % (x, y))
			break
			
	x = y = 0
	while True:
		x -= 1
		y -= 1
		if x is y:
			print('%d is %d' % (x, y))
		else:
			print('Attention! %d is not %d' % (x, y))
			break


if __name__ == '__main__':
	main()

在这里插入图片描述
原因:
Python出于对性能的考虑所做的一项优化。对于整数对象,Python把一些频繁使用的整数对象缓存起来,保存到一个叫small_ints的链表中,在Python的整个生命周期内,任何需要引用这些整数对象的地方,都不再重新创建新的对象,而是直接引用缓存中的对象。Python把频繁使用的整数对象的值定在**[-5, 256]这个区间,如果需要这个范围的整数,就直接从small_ints中获取引用而不是临时创建新的对象**。因为大于256或小于-5的整数不在该范围之内,所以就算两个整数的值是一样,但它们是不同的对象。

代码2:

a = 257
def main():
	b = 257  # 第6行
	c = 257  # 第7行
	print(b is c)  # True
	print(a is b)  # False
	print(a is c)  # False


if __name__ == "__main__":
	main()

在这里插入图片描述
原因:
看上去a、b和c的值都是一样的,但是is运算的结果却不一样。为什么会出现这样的结果,首先我们来说说Python程序中的代码块。所谓代码块是程序的一个最小的基本执行单位,一个模块文件、一个函数体、一个类、交互式命令中的单行代码都叫做一个代码块。上面的代码由两个代码块构成,a = 257是一个代码块,main函数是另外一个代码块。Python内部为了进一步提高性能,凡是在一个代码块中创建的整数对象,如果值不在small_ints缓存范围之内,但在同一个代码块中已经存在一个值与其相同的整数对象了,那么就直接引用该对象,否则创建一个新的对象出来,**所以 b is c返回了True,而a和b,c不在同一个代码块中,虽然值都是257,但却是两个不同的对象,is运算的结果自然是False了。

二、嵌套列表
1、首先介绍Python中enumerate用法

  • enumerate()是python的内置函数,enumerate在字典上是枚举、列举的意思
  • enumerate参数为可遍历/可迭代的对象(如列表、字符串)
  • enumerate多用于在for循环中得到计数,利用它可以同时获得索引和值,即需要index和value值的时候可以使用enumerate

enumerate()返回的是一个enumerate对象
在这里插入图片描述
例如:已知lst = [1,2,3,4,5,6],要求对应索引输出:

lst=[1,2,3,4,5,6]
for index, value in enumerate(lst):

    print('%s,%s'%(index,value))

在这里插入图片描述
index和value可以换成其他字母表示,你没必要非得是这两个。

要求索引值从1开始而不是默认的0开始:(同理可以从任意数值开始索引)

lst = [1,2,3,4,5,6]
for index,value in enumerate(lst,1):

    print("%s,%s"%(index,value))

在这里插入图片描述
用enumerate()函数统计文件的列数:

list1 = ["这", "是", "一个", "测试"]
count=0
for dex, line in enumerate(list1):
    print(dex,line)
    count+=1
print(count)

在这里插入图片描述
2、嵌套列表常见错误
希望录入3个学生3门课程的成绩,于是定义了一个有5个元素的列表,而列表中的每个元素又是一个由3个元素构成的列表,这样一个列表的列表刚好跟一个表格是一致的,相当于有3行3列,接下来我们通过嵌套的for-in循环输入每个学生3门课程的成绩。

names = ['关羽', '张飞', '赵云']
subjs = ['语文', '数学', '英语']
scores = [[0] * 3] * 3        #建立【【0,0,0】,[0,0,0】,[0,0,0]】三行三列
for row, name in enumerate(names):
    print('请输入%s的成绩' % name)
    for col, subj in enumerate(subjs):
        scores[row][col] = float(input(subj + ': '))
        print(scores)
print(scores[0])
print(scores[1])

在这里插入图片描述
在这里插入图片描述

print(scores[0])
print(scores[1])

在这里插入图片描述
程序执行完后我们发现,每个学生3门课程的成绩是一模一样的,而且就是最后录入的那个学生的成绩。
我们首先要区分对象和对象的引用这两个概念,而要区分这两个概念,还得先说说内存中的栈和堆。我们经常会听人说起“堆栈”这个词,但实际上“堆”和“栈”是两个不同的概念。众所周知,一个程序运行时需要占用一些内存空间来存储数据和代码,那么这些内存从逻辑上又可以做进一步的划分。对底层语言(如C语言)有所了解的程序员大都知道,程序中可以使用的内存从逻辑上可以为五个部分,按照地址从高到低依次是:栈(stack)、堆(heap)、数据段(data segment)、只读数据段(static area)和代码段(code segment)。其中,栈用来存储局部、临时变量,以及函数调用时保存现场和恢复现场需要用到的数据,这部分内存在代码块开始执行时自动分配,代码块执行结束时自动释放,通常由编译器自动管理;堆的大小不固定,可以动态的分配和回收,因此如果程序中有大量的数据需要处理,这些数据通常都放在堆上,如果堆空间没有正确的被释放会引发内存泄露的问题,而像Python、Java等编程语言都使用了垃圾回收机制来实现自动化的内存管理(自动回收不再使用的堆空间)。所以下面的代码中,变量a并不是真正的对象,它是对象的引用,相当于记录了对象在堆空间的地址,通过这个地址我们可以访问到对应的对象;同理,变量b是列表容器的引用,它引用了堆空间上的列表容器,而列表容器中并没有保存真正的对象,它保存的也仅仅是对象的引用。

a = object()
b = ['apple', 'pitaya', 'grape']

知道了这一点,我们可以回过头看看刚才的程序,我们对列表进行[[0] * 3] * 3操作时,仅仅是将[0, 0, 0]这个列表的地址进行了复制,并没有创建新的列表对象,所以容器中虽然有3个元素,但是这3个元素引用了同一个列表对象,这一点可以通过id函数检查scores[0]和scores[1]的地址得到证实。所以正确的代码应该按照如下的方式进行修改。

names = ['关羽', '张飞', '赵云']
subjs = ['语文', '数学', '英语']
scores = [[]] * 3
for row, name in enumerate(names):
    print('请输入%s的成绩' % name)
    scores[row] = [0] * 3
    for col, subj in enumerate(subjs):
        scores[row][col] = float(input(subj + ': '))
        print(scores)
print(scores[0])
print(scores[1])     

在这里插入图片描述
在这里插入图片描述
三、访问修饰符
用Python做过面向对象编程的人都知道,Python的类提供了两种访问控制权限,一种是公开,一种是私有(在属性或方法前加上双下划线)。而用惯了Java或C#这类编程语言的人都知道,类中的属性(数据抽象)通常都是私有的,其目的是为了将数据保护起来;而类中的方法(行为抽象)通常都是公开的,因为方法是对象向外界提供的服务。但是Python并没有从语法层面确保私有成员的私密性,因为它只是对类中所谓的私有成员进行了命名的变换,如果知道命名的规则照样可以直接访问私有成员,请看下面的代码。

class Student(object):

    def __init__(self, name, age):
        self.__name = name
        self.__age = age

    def __str__(self):
        return self.__name + ': ' + str(self.__age)


stu = Student('小明', 38)
print(stu._Student__name)
print(stu._Student__age)

在这里插入图片描述
所以在Python中我们实在没有必要将类中的属性或方法用双下划线开头的命名处理成私有的成员,因为这并没有任何实际的意义。如果想对属性或方法进行保护,我们建议用单下划线开头的受保护成员,虽然它也不能真正保护这些属性或方法,但是它相当于给调用者一个暗示,让调用者知道这是不应该直接访问的属性或方法,而且这样做并不影响子类去继承这些东西。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值