用于记录Python的一些内置函数,格式变化,细节要点等,更多偏向基础背后的代码原理,比较同一意思的各种写法,选取更优的方案。
事实上,Python入门简单,但深入难。Python的熟练往往是方法的选择、代码的简化(时间、内存上),以及实操时的应用。
print()语法格式:
print(*objects, sep=' ', end='\n', file=sys.stdout)
参数的具体含义如下:
objects -- 表示输出的对象。输出多个对象时,需要用 , (逗号)分隔。
sep -- 用来间隔多个对象(间隔符),如 sep='\t'
end -- 用来设定以什么结尾。默认值是换行符 \n,我们可以换成其他字符。
file -- 要写入的文件对象。
格式控制符和转换说明符可以%分隔,但%在Python中会被format淘汰
%的一些用法与C语言类似,详解
//只记些常用的
>>> x=100000
>>> y=7.531
>>> print('x={:,},y={:.2f}'.format(x,y))
x=100,000,y=7.53
//千位分隔符,保留2位小数
>>> print('{:b}'.format(20))
10100
//十进制转二进制
format用法太多了,附一篇博客,format用法
import
简单来说就是引用模块,from也可以应用模块,但更多情况主要用import
'''注意:如果仅仅是简单的操作,不包含引入文档信息或多个模块,可以用下面这种全部引入'''
from math import *
pi
cos(pi)
>>>3.141592653589793
>>>-1.0
'''from之后可以不用加前缀'''
import 和 from … import 模块的变量、方法引用差异
上面的这篇博客讲得很直接,from语句有破坏命名空间的潜质。
上面有很多概念,帮助理解。刚入门可以用用from,但之后还是用import吧,老实加前前缀吧。
import numpy as np #起别名
list1 = [1, 2, 3, 4, 5]
arr = np.array(list1) #加前缀
三目运算符
以前学C,C++时,老师经常吹C/C++的 “ 判断 ? 真 : 假 ” 的独特性,我寻思为什么独特?哦,因为它“短”。来看看Python的。
常见格式:
为真时的结果 if 判断条件 else 为假时的结果(注意,没有冒号)
a=5
print(6 if a>3 else 1)
>>>6
这种表达式也具有惰性求值的特点,和C中的&&,||类似
Python三目运算符的例子
如果我们自己编写条件运算,使用if-else或者是列表表达式这样的语句,效率低下。所以有更好的np.where。
事实上,C/C++中的“ ? : ”是设计者单独设计的,短小不仅在长度,还在时间复杂度,而Python不会浪费空间设这个,它可以引入numpy模块,np.where可以解决多维问题。
numpy包之后总结,这里强调一点,不推荐用Python中简陋的三目运算符,用np.where
循环结构
不难发现与C不同的几个点:
1、代码缩进、代替、括号
2、else if 缩写成 elif
3、for和while循环中的else子句:
教材解释:如果循环因为条件表达式不成立或序列遍历结束而自然结束时,则执行else结构中的语句,如果循环是因为执行了break语句而导致循环提前结束,则不执行else结构中的语句。
while 条件表达式 :
循环体
[else:
else子句代码块]
for 取值 in 序列或迭代对象 :
循环体
[else:
else子句代码块]
其实不难理解,就是这层循环,先抛去else语句,如果else上面所有的代码都遍历(其中内循环也要遍历完,所以可以用break打断的内循环也跳过else子句)且还要继续(所有可以用一个continue加在else前,就可以永远跳过else),就遍历else子句。在这个理解下,else子句往往时作为补充的。
函数
基础格式
def 函数名([参数列表]):
'''范围注释''' #行注释
函数体
1、无声明类型,无指定返回类型
2、即便无变量,空括号、冒号不能省
3、如果要设默认值参数,必须出现在最右端(即任何一个默认值参数右边不能再出现
非默认值参数,函数调用时可以省略默认值参数)
简单应用大致有类:
1、将函数绑定到不同的名称
2、将函数作为参数
3、将函数作为返回值
4、函数嵌套
其中函数嵌套中内外函数之间的概念问题,给个例子
>>> def fun1():
print('函数1')
def fun2():
print('函数2')
return fun2
>>> m=fun1()
函数1
>>> m()
函数2
>>> fun1()()
函数1
函数2
>>> print(m)
<function fun1.<locals>.fun2 at 0x0000028A76D6C040>
>>> print(m())
函数2
None
#当内函数是在执行外函数时定义的,所以每层定义函数的地址不同
>>> x=fun1()
>>> x
<function fun1.<locals>.fun2 at 0x0000028A76D6C1F0>
>>> y=fun1()
>>> y
<function fun1.<locals>.fun2 at 0x0000028A76D6C280>
同时,提一下变量的范围
1、形参和实参的区别与出现与C类似
2、外函数里面的变量和内函数里面的变量是有区别的,作用范围不一样。内函数也可以使用外函数的变量,但是如果想要在内部函数修改外部函数变量的值,就要使用关键字nonlocal
(暗示一波C的指针,^ _ ^)
至于函数嵌套的一些高阶应用,就更多了,分析些浅显点的应用:链接
lambda
一种表达式、匿名函数lambda,处理简单问题,返回值却是函数内存地址(相当于return)。
看似简单,但坑很多,不熟练的话不推荐使用,因为网上的很多讲解包括教材书都是Python以前的版本,边看边写边编译,问题随之出现。对现在来说已经过时(且错误)了。(都用def编写大程序,谁用这种小的)
找到一些lambda受害者,分享一下他们的博客:
那些年,我们遇到过的python lambda匿名函数的坑!!!!!!
lamda的默认作用域包含整个冒号后面的语句
lambda语句中,冒号前是参数,可以有多个,用逗号隔开,冒号右边的返回值。
'''------------------------------------'''
l=map(lambda x:x*x,[y for y in range(10)])
print(l)
>>> <map object at 0x000001FDF9EAFF40>
1、用list序列解包
print(list(l))
>>> [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
'''------------------------------------'''
x=[]
for i in range(10):
x.append(lambda n=i:n**2)
x[2]
>>> <function <lambda> at 0x000001BBB7F2A8B0>
2、返回函数,用空括号解
x[2]()
>>> 4
3、上述方法本质还是用默认值参数来引入外部变量,所以自己赋值改变结果
x[2](5)
>>> 25
'''------------------------------------'''
4、参数有时也可以省
x=lambda:'hello world'
print(x())
>>> hello world
'''------------------------------------'''
5、样例挺多的,待改进
形参与实参、可变对象和不可变对象(前提)
深复制和浅复制的原理和应用(重点)
讲真,这块对没有C语言基础的人来说挺难理解的,形参与实参在C中有涉及(C是面向过程,而C++是面向对象),特别是形参与实参,不再多说明。
对象,我想起了面向对象和面向过程的两种主流的计算机的思维方式。分享三篇博客,帮助理解。
理解一(比较长)
理解二
理解三
其实我们大学ACM或教材上的例子主要都是面向过程,因为涉及算法。面向对象Python很多,低门槛,但深度还是要回归算法,至于算法,我现在接触的大多(不,应该是全部)是面向过程的。过程是里子,对象是面子,只从表面理解,这个比喻有点贴切的。
上面的三种概念,看似繁琐,其实都只有一个本质:内存指向(地址)
从地址这个角度又可以扯一大堆,这里跳过,直接给出结论:
1、可变对象:对象存放在地址中的值不会被改变(所谓的改变是创建了一块新的地址并把新的对象的值放在新地址中原来的对象并没有发生变化)
2、不可变对象:在改变变量值的时候,它在内存空间中的ID会发生变化,即新创建的对象被关联到原来的变量名,旧对象被丢弃,垃圾回收器会在适当的时机回收这些对象。
int str float tuple 都属于不可变对象 其中tuple有些特殊
dict set list 属于可变对象
3、对不可变对象来说,形参与实参指向同一个地址,形参值改变,实参无影响。但参数是可变对象, 实参和形参就是同步变化的
这也是与C不同的,C没有可变对象,之后的C++11有可变对象。
浅复制和深复制还是以存储的地址作为本质,结合上面的总结,有多种情况
一、直接赋值。
相当于其他语言的起别名,地址,值完全一样,指向同一个对象,改变任意一个都可以改变其他
>>> a=[1,2,['x','y']]
>>> a
[1, 2, ['x', 'y']]
>>> b=a
>>> a.append(3)
>>> a
[1, 2, ['x', 'y'], 3]
>>> b
[1, 2, ['x', 'y'], 3]
>>> b[1]=1
>>> b
[1, 1, ['x', 'y'], 3]
>>> a
[1, 1, ['x', 'y'], 3]
>>> id(a)==id(b)
True
>>> a is b
True
#id() 函数就是返回对象的内存地址;is 是比较两个变量的对象引用是否指向同一个对象
#不要与 == 相混,== 只是看值是否相等
二、浅复制,常见的有 列表的乘 ‘ * ’ ,切片操作的浅复制,copy库的copy函数等等
>>> b=[1,['x']]*3
>>> b
[1, ['x'], 1, ['x'], 1, ['x']]
list属于可变对象,会影响到
>>> b[1].append(4)
>>> b
[1, ['x', 4], 1, ['x', 4], 1, ['x', 4]]
下面这操作,本质是重定义了,
>>> b[3]=['y',5]
>>> b
[1, ['x', 4], 1, ['y', 5], 1, ['x', 4]]
重定义独立,其他不影响,依旧指向同一个引用
>>> b[1].append(6)
>>> b
[1, ['x', 4, 6], 1, ['y', 5], 1, ['x', 4, 6]]
再来看一个例子
>>> first = {'name':'rocky','lanaguage':['python','c++','java']}
>>> second = first.copy()
>>> second
{'name': 'rocky', 'lanaguage': ['python', 'c++', 'java']}
>>> id(first)
31036280L
>>> id(second)
38786728L
==>结论一:浅复制,总的地址是不同的
>>> second['lanaguage'].remove('java')
>>> second
{'name': 'rocky', 'lanaguage': ['python', 'c++']}
>>> first
{'name': 'rocky', 'lanaguage': ['python', 'c++']}
==>结论二:浅复制可以影响可变对象
>>> second['name'] = 'leey'
>>> second
{'name': 'leey', 'lanaguage': ['python', 'c++']}
>>> first
{'name': 'rocky', 'lanaguage': ['python', 'c++']}
==>结论三:重定义不影响其他
>>> id(first['name'])
38829152L
>>> id(second['name'])
38817544L
==>结论四:重定义完全改变,就独立出来了
>>> id(first['lanaguage'])
38754120L
>>> id(second['lanaguage'])
38754120L
==>结论四:浅复制,复制的部分地址是相同的,即指向同一个引用
我所理解的重定义,术语就是“可变对象的引用赋值”。
“可变对象的引用赋值”的原理:可变对象保存的并不是真正的对象数据,而是对象的引用。当对可变对象进行赋值时,只是将可变对象中保存的引用指向了新的对象。
那不可变对象呢?两个结论。
(1)不可变对象在赋值时显然会开辟新空间。
(2)深、浅拷贝对不可变对象拷贝时,不开辟新空间,相当于赋值操作。
三、深复制,常见的有 copy库的deepcopy等等
深复制才是真正意义上的复制,完全创建一个值一样,但地址完全不同的对象
不可变原组 和 可变列表的深拷贝,浅拷贝区别
>>> import copy as cp
>>> a=(1,2,3)
>>> b=cp.copy(a)
>>> c=cp.deepcopy(a)
>>> b==c
True
>>> id(b)==id(c)
True
>>> b is c
True
>>> b is a
True
>>> a=[1,2,3]
>>> b=cp.copy(a)
>>> c=cp.deepcopy(a)
>>> b==c
True
>>> id(b)==id(c)
False
>>> b is a
False
>>> c is a
False
分析python深拷贝和浅拷贝的区别的博客有很多,分享一点。
python深拷贝和浅拷贝的区别
python深拷贝和浅拷贝,原理,应用(这篇文章很全)
浅复制和深复制的原理图
深复制虽然不会出差错,但计算机要讲解效率,Python平时都是用浅复制,总之,原理还是在Python怎么存储,地址是很重要的。在可变对象浅复制时,我们要提高警惕。
切片
1、3个特征:
(1)切片操作不是列表特有的,python中的有序序列都支持切片,如字符串,元组。
(2)切片的返回结果类型和切片对象类型一致。
(3)切片是一种浅拷贝
2、定义
sname[start:end:step]
1、 左闭右开
2、 start是切片初始位置,如果不指定,默认为0;
end是切片的截止位置,如果不指定,则默认为序列的长度,
step是切片的步长,默认为1。
3、 可以使用负向索引(python中有序序列都支持负向索引)
4、 :n代表n之前的;
::n代表间隔为n;
n:代表n之后的。
:整段复制
3、常见用法:截取、增加、删除、修改(重点)
4、注意事项
(1)默认浅复制(一维的可变对象或多维数组这类涉及修改就尽量用deepcopy)
(2)删除、修改长度要相等的情况:指定长度 或 不连续
(3)负向索引的方向、起末
(4)字符串截取一般都用切片