Python编程技巧和需要注意的地方——Pro

目录

前言

1.多个大数字相加

2.同时访问多个相互关联的列表

3.访问列表的同时输出对应下标

4.熟练使用*和_

5.关于类的get和set方法

6.熟练使用help和dir方法

7.什么时候使用set或dict而不是list?

8.默认参数需要用list时需谨慎

9.运行代码尽量使用main函数

11. 正确创建二(多)维列表

12.完结撒花​​​​​​​

前言

        这篇文章应该是这个系列最后一篇了,部分内容较前两篇来说算比较进阶吧,没看过前两篇的小伙伴可以先看看前两篇哦,说不定也会对你有帮助呢。传送门:

python的一些小技巧和知识点(一)

python的一些小技巧和知识点(二)

        正文开始,本篇主要记录一些写代码时可能有用的技巧和可能会出错的一些情况。

1.多个大数字相加

        当程序里需要用到很大的数字时,可以用下划线_来分隔每3位数字,这样能方便人阅读,而程序会忽略这些下划线,得到正确结果,例如一个亿级数和一个百万级数相加时,就可以用下划线来分隔,帮助我们更好的阅读。例如:

num1 = 23_333_333_333
num2 = 23_333_333                    
result = num1 + num2
print(result)
# 23356666666

        输出结果没有下划线分隔符,如果我们想要输出也便于我们阅读,那就可以用格式化的输出,来让输出也更好看,方便我们调试:

print(f'{total:,}')
# 23,356,666,666
print(f'{result:_}')
# 23_356_666_666

2.同时访问多个相互关联的列表

        设想一下,有多个列表,它们的内容相互联系,你想在一个循环里每次同时遍历并列的几个内容,或者叫它们item,例如下面这几个列表:

heros = ['Peter Parker', 'Tony Stark', 'Steven Rogers', 'Natasha', 'Clint Barton', 'Stephen Strange']
make_up_names = ['Spider Man', 'Iron Man', 'Captain American', 'Black Widow', 'Hawk Eye', 'Dr. Strange']
    

         现在,我们想在一个循环里依次遍历他们,输出名字和对应的英雄称号,如果你对C很熟悉的话,你可能首先想到的时用一个下标index来记录位置,每次用这个下标来访问元素,就像这样:

index = 0
for index in range(len(heros)):
    print('{} is {}'.format(heros[index], make_up_names[index]))
'''
Peter Parker is Spider Man
Tony Stark is Iron Man
Steven Rogers is Captain American
Natasha is Black Widow
Clint Barton is Hawk Eye
Stephen Strange is Dr. Strange
'''

         虽然这样也能实现功能,但看起来还是略显麻烦,还记得前面文章里提到的zip吗?当遇到这种情况时,它能帮助你更加优雅的访问,就像这样:

for hero, name in zip(heros, make_up_names):
    print('{} is {}'.format(hero, name))

'''
Peter Parker is Spider Man
Tony Stark is Iron Man
Steven Rogers is Captain American
Natasha is Black Widow
Clint Barton is Hawk Eye
Stephen Strange is Dr. Strange
'''

        优雅的同时,效率也更高,当需要访问的列表更多的时候,也可以再接着往zip里加,还是上面这个例子,假如我们再多一个列表记录他们每个角色所拥有的个人电影数,访问名字的同时输出他们的电影数。

individual_movies = [9, 3, 3, 1, 0, 2]
for hero, name, movie in zip(heros, make_up_names, individual_movies):
    print('{} is {}, he/she has {} individual movies'.format(hero, name, movie))

'''
Peter Parker is Spider Man, he/she has 9 individual movies
Tony Stark is Iron Man, he/she has 3 individual movies
Steven Rogers is Captain American, he/she has 3 individual movies
Natasha is Black Widow, he/she has 1 individual movies
Clint Barton is Hawk Eye, he/she has 0 individual movies
Stephen Strange is Dr. Strange, he/she has 2 individual movies
'''

         需要注意的是,zip函数只会取多个列表里最短的长度作为公共长度进行合并并返回,如果想要最长的长度或指定其他长度,得另外指定参数,且比较麻烦,这里就不说了(其实是我也没用过),而且返回的元素组成的也是一个元组tuple类型,再依次将元组里的内容赋给hero,name,movie这三个变量,这三个变量也只是指向元组内容的引用。还记得前面说的元组类型是不可变对象吗?因此在循环体里不能对元组本身进行修改,例如像下面这样的话就会报错:

for pair in zip(heros, make_up_names, individual_movies):
    pair[0] += 'a'

3.访问列表的同时输出对应下标

        还是上面的heros列表,现在我们想一行输出一个名字同时输出下标,同样,熟练C的小伙伴可能还是习惯用下标来访问,比如

for(int i = 0; i < length; i++){
    printf("balala\n");
}

         或者用python就是:

index = 0
for h in heros:
    print(index, h)
    index += 1
'''
0 Peter Parker
1 Tony Stark
2 Steven Rogers
3 Natasha
4 Clint Barton
5 Stephen Strange
'''

        这看起来不够“python”,有人可能会说,“同时输出下标和内容,这不是可以用pandas库的Series类型来完成吗?”别扛,你都会用pandas了,还看我这个文章干嘛。让我们用python的方式来解决这个问题:

for index, h in enumerate(heros):
    print(index, h)

        enumerate关键字将所给参数组合成一个带索引的序列,默认下标从0开始,可以指定索引开始的位置,

for index, h in enumerate(heros, start = 1):
    print(index, h)

4.熟练使用*和_

        聪明的你说不定已经发现,上面的zip操作有点像是将参数都打包(packing)然后再分给hero, name, movie这三个变量(unpacking),这就是python的机制之一,假如有一个元组,我们只需要用里面的元素,而不对元素本身进行修改,更甚,我们只需要其中的一部分。例如下面这个:

heros = ('Spider Man', 'Iron Man', 'Captain American', 'Super Man', 'Bat Man')

        可以看到前面3个都是漫威英雄,后面两个是DC英雄,现在我们在unpacking时只想要前3个,不要后两个,那就可以

spider, iron, cap, *_ = heros

         其中,*表示从这个位置到后面的都作为一个整体,下划线 表示我们不需要用到这个变量,如果我们需要前后的,不要中间的,那还可以这样:

spider, iron, *_, bat = heros
print(spider)
print(iron)
print(bat)
'''
Spider Man
Iron Man
Bat Man
'''

        总之,在这种情况下可以灵活运用*和_,可以很大程度的方便我们编程,使用_能让python在unpacking时不为对应变量分配额外空间,也就不用考虑垃圾回收等。而*还可以用到函数参数里,假如我们定义一个函数,已知至少需要用到一个参数,还需要多少其他参数我们暂时不知道,即函数可以容纳任意数量的参数,那么这时就可以用*来表示其他参数,python将会把多的参数解析为一个列表并传给函数体使用,例如:

def multiPara(a, *others):
    # do something with a
    print(others)

if __name__ == '__main__':
    a = 1
    multiPara(a, 2, 3, 4, 5)
    multiPara(a)

'''
[2, 3, 4, 5]
[]
'''

5.关于类的get和set方法

        熟悉C++/Java的小伙伴肯定对这两个语言的get和set方法不陌生,别忘了python也是一个面向对象的语言,它的类也有get和set方法,还是以上面的heros和make_up_names为例,假设我们要创建一个类,以名字作为属性,称号作为属性值,python的getattrsetattr就能帮助我们动态的创建类的属性,让我们先试试,先set一个蜘蛛侠:

class MarvelHeros:
    pass

if __name__ == '__main__':
    marverHeros = MarvelHeros()
    name = 'peter_parker'
    make_up = 'Spider Man'
    setattr(marverHeros, name, make_up)    # 方法一
    setattr(marverHeros, 'peter_parker', 'Spider Man')    # 方法二
    print(marverHeros.peter_parker)

'''
Spider Man
'''

        一个一个设置太麻烦了,还记得前面说的zip方法吗,我们先用zip方法创建一个字典,名字为键,称号为值,然后一次性设置所有英雄,然后再读他们的称号:

marvelHeros = MarvelHeros()
marvel = dict(zip(heros, make_up_names))
for key, value in marvel.items():
    setattr(marvelHeros, key, value)

for key in marvel.keys():
    print(key, 'is', getattr(marvelHeros, key))

'''
Peter Parker is Spider Man
Tony Stark is Iron Man
Steven Rogers is Captain American
Natasha is Black Widow
Clint Barton is Hawk Eye
Stephen Strange is Dr. Strange
'''

6.熟练使用help和dir方法

        相信大多数小伙伴遇到一些新库的时候肯定都会比较困惑,不知道怎么使用,这时候除了百度以外,还可以使用help和方法和dir方法,help方法会列出想要了解的那个库的所有用法等信息,其中还有每个方法的使用例子,dir方法会列出这个库可用的所有方法名,比如非常好用的进度条库tqdm:

from tqdm import tqdm
help(tqdm)
dir(tqdm)
'''
这里会列出很多,读者自己可以试一试
'''

7.什么时候使用set或dict而不是list?

        list肯定是大家一开始用的最多的数据结构,但有时候往往list会是制约代码性能的时候,考虑下面这段代码:

from time import perf_counter
set_nums = set([x for x in range(int(1e6))])
list_nums = [x for x in range(int(1e6))]

start = perf_counter()
for i in range(int(1e6)):        # 注意这里的范围
    res.append(i in set_nums)
end = perf_counter() - start
print("set, cost:", end)
# set, cost: 0.1499949999997625

start = perf_counter()
for i in range(int(1e4)):        # 注意这里的范围
    res.append(i in list_nums)
end = perf_counter() - start
print("list, cost:", end)
# list, cost: 0.47885179999866523

        注释里的输出是以我的机器为例,可以看到,在列表里查询时,查询的数量相比在集合里查询少了两个数量级,而所花费的时间比集合里多了将近0.3秒,感兴趣的同学可以试试将列表的数量级也换成和集合的一样,看看耗时会是多少。造成这一现象的原因是,对于集合set类型,python会为其维护一个哈希表,以确保所有元素唯一,因此查询时的时间复杂度就是O(1),而对于列表,因为没有维护哈希表,每次都是遍历的去找,因此其时间复杂度是O(N),在这个例子中,两个循环的时间复杂度是O(N)O(N^{2}),显然集合要更快,因此,下次遇到类似情况时,尽量选择集合/字典,而不是列表。

8.默认参数需要用list时需谨慎

        又双叒叕是list,上回书说到,list是可变对象类型,而某个变量使用到list时只是这个列表的引用,如果有一个函数,你需要一个列表作为它的默认参数,就像下面这段代码这样:

def testListAsPara(nums, default_para = []):
    for n in nums:
        default_para.append(n)
    
    return default_para

if __name__ == '__main__':
    print(testListAsPara([1, 2, 3, 4]))
    print(testListAsPara([1, 2, 3, 4]))

        运行上面这段代码,会输出什么?两行1,2,3,4?其实是这样:

[1, 2, 3, 4]
[1, 2, 3, 4, 1, 2, 3, 4]

         为什么呢?前面说到,列表时可变对象类型,python在加载这个函数到内存时,就会为default_para分配一部分空间,而default_para只不过是一个指向这块地址空间的引用(指针),之后所有的修改都会在这块内存空间上进行,所以第二次调用时,default_para还是指向的一开始分配的那块空间,在那基础上又接着append。于是这个函数正确的写法应该是下面这样:

def testListAsPara(nums, default_para = None):
    if isinstance(default_para, type(None)):
        default_para = []
    for n in nums:
        default_para.append(n)

    return default_para

9.运行代码尽量使用main函数

        鉴于python又被称为脚本语言,很多同学可能初学的时候直接进去就开始写,就像我上面有几块代码块那样,或者在一个函数下面直接写,然后直接在命令行里python 文件名.py 这样运行,或者直接点击开发环境的运行按钮,这样的写法是个不好的习惯,比如下面这个example1.py文件:

import numpy as np

def example():
    for i in range(3):
        arrs = np.array([i] * 5)
        print(arrs)
        print("created a array")

example()

        虽然这样直接运行不会有问题,但是设想这个py里是你自己写的某个将来会用到的库,而python在执行import这个库时,会执行这个py文件里的代码,以确保所有的内容都被import进来,于是,你在下面的测试代码就会被执行,不信的话你可以自己试试,在example1.py所在目录下import example函数,看看会发生什么。

from example1 import example

        正确的做法应该是像上面几点中我所写的使用main来进行局部的测试,重写example,像下面这样,此时再试试看,你发现了什么?

import numpy as np

def example():
    for i in range(3):
        arrs = np.array([i] * 5)
        print(arrs)
        print("created a array")

if __name__ == '__main__':
    example()

11. 正确创建二(多)维列表

        怎么快速又正确的创建一个二维列表?在别的语言比如C语言里,我们需要首先创建一个指针的指针,再为这个指针的指针创建一个指针数组,再为这个指针数组分别分配内存空间,很麻烦,而且指针对新手也不友好,更别提指针的指针了。比如像下面这段代码,生成一个4行5列的全1矩阵:

int i, j, row = 4, col = 5;
int **array = (int**)malloc(row * sizeof(int*));
for(i = 0; i < row; i++){
    array[i] = (int*)malloc(col * sizeof(int));
    for(j = 0; j < col; j++){
        array[i][j] = 1;
    }
}
for(i = 0; i < row; i++){
    for(j = 0; j < col; j++){
        printf("%d ", array[i][j]);
    }
    printf("\n");
}

        再比如,使用C++的话,需要用new来分配内存,虽然比malloc要简单,但也没简单多少。

        那在python里,怎么创建一个二维列表呢?例如我们要生成一个4行5列的二维列表,如果你对列表的生成式比较熟悉的话, 可能你会这样:

a = [[1] * 4] * 5
print(a)
'''
[[1, 1, 1, 1], 
 [1, 1, 1, 1], 
 [1, 1, 1, 1], 
 [1, 1, 1, 1], 
 [1, 1, 1, 1]]
'''

        从结果上看起来确实起到了效果,但是我们再试下修改其中一个元素,比如将a[1][1]修改为10,看看会发生什么:

a[1][1] = 10
print(a)
'''
[[1, 10, 1, 1], 
[1, 10, 1, 1], 
[1, 10, 1, 1], 
[1, 10, 1, 1], 
[1, 10, 1, 1]]
'''

       可以看到每一行的第二个元素都被修改了,这是因为如果我们用前面那种方法来生成一个二维列表的话,python解释器只是会把每一行进行一个浅拷贝,每一行都共享同一块内存,可以用id方法来验证一下:

         Python编程技巧和需要注意的地方——Pro_第1张图片

        不出所料,正是共享内存的。于是我们需要换种方式来生成,像下面的语句:

b = [['x'] * 4 for _ in range(5)]

Python编程技巧和需要注意的地方——Pro_第2张图片

 

         这样就能任意修改了。

 

 ​​​​

12.完结撒花

       --11.08更新,又增加了一条。

        到这里,这个系列差不多该结束了,想多叨叨几句,又好像叨叨不出来,写这篇花了我5个小时左右,如果对你有帮助的话,点个赞再走吧,谢谢老板!

你可能感兴趣的:(python,好东西,学习笔记,经验分享,python,程序人生,开发语言)