Python学习
学习自廖雪峰的博客网站python篇
Pycharm内容
ctrl+d
复制当前行,ctrl+y
删除当前行,shift+enter
换行(当前行任意位置均可换行),ctrl+/
批量注释或批量取消注释,Tab
键和shift+Tab
键完成批量缩进和取消缩进,ctrl+f
查找,ctrl+r
替换,ctrl+'+'/'-'
折叠或者展开代码或ctrl+shift+'+'/'-'
全部折叠或展开代码。设置->KeyMap设置快捷键。#TODO 记录要做的事情。
小Tips
转义字符——除了用\
外,还可以用r' '
代表单引号内的内容不转义,特殊的%%
表示%
。
多行写代码——用'''...'''
的格式表示多行字符,并且还能在前面加上r
一起使用表示内部内容不转义。
python除法——/
默认是结果为浮点数,而//
则默认会去掉小数部分保留整数。
字符编码——ASCII码(单字节)->Unicode码(通常双字节)->utf-8编码(可变长编码),计算机中编码:内存统一使用Unicode编码,需要保存到硬盘或者需要传输时则转为utf-8,用记事本编辑的时候,从文件读取的UTF-8字符被转换为Unicode字符到内存里,编辑完成后,保存的时候再把Unicode转换为UTF-8保存到文件,浏览网页的时候,服务器会把动态生成的Unicode内容转换为UTF-8再传输到浏览器。
格式化输出——利用%
来实现,和c语言类似。例如print('hello %s %d' % ('world',4))
就会输出hello world 4
如果只有一个格式化输出,则不用添加括号。在%d
前面添加数字代表空格数或者%f
前加.
和数字控制小数位数,若不确定要格式化输出的内容可以用%s
代替,会将所有内容转换为字符串输出。还有一种格式化方法是使用format()
方法,它会用传入的参数依次替换字符串内的占位符{0}
、{1}
……,不过这种方式写起来比%要麻烦得多,如'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125)
,输出Hello, 小明, 成绩提升了 17.1%
。
条件判断——可用elif
表示else if
,同时如果if
后面只有一个变量就和C语言类似。
IO——input()
的返回类型为str
,可用int()
转换为整数型,float()
转换为浮点数类型。
不可变对象——对于不变对象来说,调用对象自身的任意方法,也不会改变该对象自身的内容。相反,这些方法会创建新的对象并返回,这样,就保证了不可变对象本身永远是不可变的。
全局变量——在函数中想要修改全局遍历,如果是可变类型,则可以直接进行修改,如果是不可变类型,则需要使用global声明才可修改。
Python基础
字符串
python3以unicode编码,即支持多种语言的字符串。
对于单个字符(英文或中文),可用ord()
和chr()
函数分别读取字符的整数表示和把编码转换为字符。还可以用十六进制表示其他语言如:'\u4e2d\u6587'
等价于中文
。
对于bytes
类型的数据用带b
前缀的单引号或双引号表示,如x = b'ABC'
,bytes
类型的数据每个字符占用一个字节。用unicode编码的字符串可以通过encode()
函数编码为指定的bytes
,内部参数为ascii或者utf-8
等其他编码方式,反过来就是使用decode()
函数,如果bytes
中只有一小部分无效的字节,可以传入errors='ignore'
。纯英文的str
可以用ASCII
编码为bytes
,内容是一样的,含有中文的str
可以用UTF-8
编码为bytes
。
对于str
字符串可用len()
函数计算字符数,如果换为bytes
类型,则计算字节数。为了避免乱码问题,我们应该坚持使用utf-8
来对str
和bytes
进行转换。
list
Python内置的数据类型——列表list,用[]
表示。有序的集合,len()
获取长度,可用下标访问元素(从0开始的),也可以使用-1
作为下标来直接访问最后一个元素,以此类推用-2
,-3
等来获取倒数第几个元素。可使用append()方法
在列表最后添加元素,或者使用insert(int index, Object obj)方法
来添加元素到指定位置;使用pop()方法
删除列表末尾的元素或者添加参数index
来指定下标;如果需要替换可以直接通过str[i]="..."
来实现,并且list
内部的各个元素的数据类型也是可以不相同的,比如可以就是list
类型。
tuple
Python内置的另一种有序列表类型——元组,用()
表示。和list
不同,tuple
一旦初始化就不能更改,也没有append(),insert()这样的方法,但是可以通过下标访问元素,不过不能替换,也更加安全,还有一个需要注意的就是如果定义的时候只有一个元素需要加上一个,
来避免歧义,t=(1,)
,因为如果不加,t
就是一个值为1的整型变量了。不过,如果tuple
中存在list
类型的元素,这个就list
中的元素就可以改变了,从而可以理解为tuple
也是”可变“的。
循环
首先是for..in
循环,依次把list
,tuple
中的元素打印出来,例如for x in [1,2,3,4,5]:
;或者使用range(x)
表示从0开始到小于x的整数序列for x in range(5):
,还可以使用list()
将range()
转换为list类型。
dict
Python内置的字典类,其他语言也叫map
,采用键值对存储方式存储。可通过d['key']
查询对应的值,还可通过该方法放入键值对。一个key
对应一个value
,若对一个关键字放入多个值,则后面放入的会把前面放入的冲掉。如果key
值不存在,字典会报错,为了避免,可使用'key' in d
来查看key是否存在于字典中,若不存在则返回False
;或者使用get()方法
,如果不存在会返回None
,或者返回自己指定的value
,还可以使用pop('key')方法
删除键值对。最重要的是key
对象必须是不可变对象。
set
和dict类似,也是一组key的集合,只是不存储value值,也同样不能重复。通过add()
和remove()
方法实现添加或者删除key,set可以看作数学上的集合,可以进行∩、∪等操作。并且要创建一个set,需要提供一个list作为输入集合,s = set([1, 2, 3])
函数
常用函数
max()
——可以从传入任意个参数,返回最大的那个。str()
,int()
,bool()
等——数据类型转换函数。hex()
函数——把一个整数转换成十六进制表示的字符串。
函数名实际是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”。即a = abs
,然后可以用a()
来实现abs()
的功能。
如果想要保证传入函数的参数类型是正确的,可用isinstance()方法
判断是否参数类型满足你所定义的参数类型。
函数是可以返回多个值的,可以用多个变量来接收,也可以用一个,因为返回的实际上是一个tuple
默认参数
def f(x,n=2)
这里就给n设置了一个默认参数,如果调用函数f()
时只传入了一个参数x,则会默认传入一个等于2的n进函数,这样就比较方便,例如f(5)
实际上就是f(5,2)
。不过设置默认参数的时候也有几个需要注意的地方:第一是必选参数在前,默认参数在后,第二是默认参数必须指向不变对象。如果指向一个list,则每次调用时都会改变list里的元素从而导致函数无法发挥作用。可见,默认参数降低了函数调用的难度,而一旦需要更复杂的调用时,又可以传递更多的参数来实现。无论是简单调用还是复杂调用,函数只需要定义一个。
在Python函数中,还可以定义可变参数。顾名思义,可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个。定义可变参数和定义一个list或tuple参数相比,仅仅在参数前面加了一个*
号。在函数内部,参数numbers
接收到的是一个tuple,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数:def calc(numbers)
,def calc(*numbers)
前者调用需要传入list或tuple,calc([1,2,3])
而后者则不需要,calc(1,2,3)
。同时可变参数还可传入一个list或tuple进函数,不过需要添加*
号,例如nums = [1,2,3]
,calc(*nums)
即可,*nums
表示把nums
这个list的所有元素作为可变参数传进去。这种写法相当有用,而且很常见。
关键字参数
关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。示例如下:
1 | def person(name, age, **kw): |
**extra
表示把extra
这个dict的所有key-value用关键字参数传入到函数的**kw
参数,kw
将获得一个dict,注意kw
获得的dict是extra
的一份拷贝,对kw
的改动不会影响到函数外的extra
。
命名关键字参数
1 | # 如果要限制关键字参数的名字,就可以用命名关键字参数 |
参数组合
在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。比如定义一个函数,包含上述若干种参数:
1 | def f1(a, b, c=0, *args, **kw): |
在函数调用的时候,Python解释器自动按照参数位置和参数名把对应的参数传进去。
1 | 1, 2) f1( |
最神奇的是通过一个tuple和dict,你也可以调用上述函数:
1 | 1, 2, 3, 4) args = ( |
所以,对于任意函数,都可以通过类似func(*args, **kw)
的形式调用它,无论它的参数是如何定义的。
参数小结
Python的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。
默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误!
要注意定义可变参数和关键字参数的语法:*args
是可变参数,args接收的是一个tuple;**kw
是关键字参数,kw接收的是一个dict。
以及调用函数时如何传入可变参数和关键字参数的语法:
可变参数既可以直接传入:func(1, 2, 3)
,又可以先组装list或tuple,再通过*args
传入:func(*(1, 2, 3))
;
关键字参数既可以直接传入:func(a=1, b=2)
,又可以先组装dict,再通过**kw
传入:func(**{'a': 1, 'b': 2})
。
使用*args
和**kw
是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。
命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。
定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符*
,否则定义的将是位置参数。
递归
尾递归优化——尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。python未提供该优化。
1 | # 上面的fact(n)函数由于return n * fact(n - 1)引入了乘法表达式,所以就不是尾递归了。要改成尾递归方式,需要多一点代码,主要是要把每一步的乘积传入到递归函数中: |
高级特性
切片
正向切片——L[0:3]
,从索引0开始取到索引3为止且不包含索引为3的元素,如果第一个索引为0,还可以省略,即L[:3]
。
倒数切片——L[-10:-1]
,即取从倒数第十到倒数第一的元素且不包含最后一个元素,如果想取最后一个元素,则L[-10:]
即可。
设置间隔——L[0:10:2]
,即取前十个数且每两个数取一个,或者L[::5]
,取所有,每5个取一个。如果只写:
则复制原有的list
1 | # 测试 |
迭代
只要是可迭代对象,就可以通过for
进行迭代,用dict
举例
1 | for key in d: |
列表生成式
用来生成list
,例list(range(1,11))
,并且可以和if,for
配合使用。
1 | [x * x for x in range(1, 11)] # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] |
可见,在一个列表生成式中,for
前面的if ... else
是表达式,而for
后面的if
是过滤条件,不能带else
。
生成器
generator
一边循环,一边计算,可以把列表生成式的[]
改为()
即可,可以用next(g)
来打印生成器的下一个值,在没有元素时会抛出异常。也可以通过for
来迭代从而避免异常。定义generator的另一种方法,如果一个函数定义中包含yield
关键字,那么这个函数就不再是一个普通函数,而是一个generator。
生成器每次停止是在遇到yield
关键字停止,下一次开始也同样从这里开始。调用generator时,需要先生成一个generator对象,o=odd() # odd是一个generator
,同样使用for来迭代:for n in odd()
,但是使用for语句会拿不到generatorreturn
语句的值,需要使用错误捕获来获得。
1 | def triangles(): |
迭代器
isinstance([], Iterable)
判断是否是Iterable对象。可以被next()
函数调用并不断返回下一个值的对象称为迭代器:Iterator
。生成器都是Iterator
对象,但list
、dict
、str
虽然是Iterable
,却不是Iterator
。把list
、dict
、str
等Iterable
变成Iterator
可以使用iter()
函数。Iterator
的计算是惰性的,只有在需要返回下一个数据时它才会计算。所以list等就不是Iterator
。
函数式编程
map/reduce
map()
函数接受两个参数,一个是函数,一个Iterable
,map
将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator
返回。
1 | def f(x): |
reduce()
也接受两个参数,一个是函数,一个Iterable
,reduce
把一个函数作用在一个序列[x1, x2, x3, ...]
上,这个函数必须接收两个参数,reduce
把结果继续和序列的下一个元素做累积计算。
1 | # reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4) |
filter
filter()
函数用于过滤序列。和map()
类似,filter()
也接收一个函数和一个序列。和map()
不同的是,filter()
把传入的函数依次作用于每个元素,然后根据返回值是True
还是False
决定保留还是丢弃该元素。且filter()
返回的是Iterator
,惰性序列,需要使用list()
来生成。
1 | # 用于生成素数 |
sorted
Python内置的sorted()
函数就可以直接对list进行排序(默认从小到大),此外,sorted()
函数也是一个高阶函数,它还可以接收一个key
函数来实现自定义的排序。key
指定的函数将作用于list
的每一个元素上,并根据key
函数返回的结果进行排序。要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True
。
1 | sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True) |
返回函数
1 | def lazy_sum(*args): |
返回的是一个函数,并且是在调用返回的函数f
时,才会真正开始计算。且每次调用lazy_sum()
返回的都是不同的函数,即使函数相同。
闭包——我们在函数lazy_sum
中又定义了函数sum
,并且,内部函数sum
可以引用外部函数lazy_sum
的参数和局部变量,当lazy_sum
返回函数sum
时,相关参数和变量都保存在返回的函数中的程序结构,就称为闭包。需要注意,返回的函数不是立即执行,而是在函数调用时才会执行。所以返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
1 | # 每次返回递增整数的函数 |
匿名函数
lambda
关键字表示匿名函数,匿名函数有个限制,就是只能有一个表达式,不用写return
,返回值就是该表达式的结果。用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数。
1 | lambda x: x * x # def f(x): return x * x |
装饰器
函数有一个_name_
属性,可以通过该属性拿到函数的名字。在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。即不改变函数定义,增加功能。装饰器接受函数作为参数,且使用装饰器时,要借助Python的@语法,把decorator置于函数的定义处。
1 | # 装饰器 |
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。
1 | import functools |
在oop设计模式中还有一些应用。
偏函数
functools.partial
帮助我们创建一个偏函数,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。但是创建完后,也仍然可以传递参数来改变功能。
1 | import functools |
模块
使用模块
1 | #!/usr/bin/env python3 |
第1行和第2行是标准注释,第1行注释可以让这个hello.py
文件直接在Unix/Linux/Mac上运行,第2行注释表示.py文件本身使用标准UTF-8编码。第4行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释。第6行使用__author__
变量把作者写进去。
sys
模块中的agrv
变量,即命令行参数,第一个参数是该.py
文件的名称,argv
是一个list
。
当我们在命令行运行模块文件时,Python解释器把一个特殊变量__name__
置为__main__
,而如果在其他地方导入该模块时,if
判断将失败,因此,这种if
测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试。
模块的作用域——在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在Python中,是通过_
前缀来实现的。类似_xxx
和__xxx
这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc
,__abc
等;
1 | # example |
模块搜索路径
当我们试图加载一个模块时,Python会在指定的路径下搜索对应的.py文件,如果找不到,就会报错。默认情况下,Python解释器会搜索当前目录、所有已安装的内置模块和第三方模块,搜索路径存放在sys
模块的path
变量中,如果我们要添加自己的搜索目录,有两种方法。
1 | # 一是直接修改sys.path,添加要搜索的目录: |
面向对象编程
类和实例
1 | class Student(Object): |
定义类,用class
关键字,Object
即该类的父类。创建实例即类名+()
,还可以自由地给一个实例变量绑定属性,比如,给实例Jack
绑定一个name
属性。__init__方法
可以理解为构造方法,可以绑定需要的属性。且__init__方法
的第一个参数永远为self
,表示创建的实例本身。和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self
,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。
访问限制
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__
,在Python中,实例的变量名如果以__
开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。
1 | class Student(Object): |
如果想要访问相应的私有变量,可以通过getter
和setter
方法来获得和修改私有变量。还有需要注意的是,在Python中,变量名类似__xxx__
的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__
、__score__
这样的变量名。
有些时候,你会看到以一个下划线开头的实例变量名,比如_name
,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。
双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name
是因为Python解释器对外把__name
变量改成了_Student__name
,所以,仍然可以通过_Student__name
来访问__name
变量:
1 | Jack._Student__name # jack |
最后注意下面的这种错误写法:
1 | bart = Student('Bart Simpson', 59) |
表面上看,外部代码“成功”地设置了__name
变量,但实际上这个__name
变量和class内部的__name
变量不是一个变量!内部的__name
变量已经被Python解释器自动改成了_Student__name
,而外部代码给bart
新增了一个__name
变量。
继承和多态
1 | class Animal(object): |
在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来就不行。
多态的开闭原则:1)对扩展开放:允许新增Animal
子类 2)对修改封闭:不需要修改依赖Animal
类型的run_twice()
等函数。
静态语言和动态语言——对于静态语言(例如Java)来说,如果需要传入Animal
类型,则传入的对象必须是Animal
类型或者它的子类,否则,将无法调用run()
方法。对于Python这样的动态语言来说,则不一定需要传入Animal
类型。我们只需要保证传入的对象有一个run()
方法就可以了
1 | class Timer(object): |
这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
Python的“file-like object“就是一种鸭子类型。对真正的文件对象,它有一个read()
方法,返回其内容。但是,许多对象,只要有read()
方法,都被视为“file-like object“。许多函数接收的参数就是“file-like object“,你不一定要传入真正的文件对象,完全可以传入任何实现了read()
方法的对象。
获取对象信息
判断对象类型,使用type()
函数
1 | type(123) # <class 'int'> |
可以在if
语句中判断两个变量的类型是否相等
1 | type(123)==type(456) # True |
还可以使用isinstance()
来判断类的继承等关系,而不建议使用type()
。
1 | # object -> Animal -> Dog -> Husky |
如果要获得一个对象的所有属性和方法,可以使用dir()
函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法:
1 | dir('ABC') |
类似__xxx__
的属性和方法在Python中都是有特殊用途的,比如__len__
方法返回长度。在Python中,如果你调用len()
函数试图获取一个对象的长度,实际上,在len()
函数内部,它自动去调用该对象的__len__()
方法,所以,下面的代码是等价的:
1 | len('ABC') # 3 |
同样如果是我们自己写的类,如果也想用len(myObj)
的话,也可以自己写一个__len__()
方法。还有就是lower()
普通方法。
仅仅把属性和方法列出来是不够的,配合getattr()
、setattr()
以及hasattr()
,我们可以直接操作一个对象的状态:
1 | class MyObject(object): |
紧接着,可以测试该对象的属性:
1 | hasattr(obj, 'x') # 有属性'x'吗? True |
如果试图获取不存在的属性,会抛出AttributeError的错误:
1 | getattr(obj, 'z') # 获取属性'z' |
还可以传入一个default参数,如果属性不存在,就返回默认值:
1 | getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404 |
也可以获得对象的方法:
1 | hasattr(obj, 'power') # 有属性'power'吗? True |
通过内置的一系列函数,我们可以对任意一个Python对象进行剖析,拿到其内部的数据。要注意的是,只有在不知道对象信息的时候,我们才会去获取对象信息。如果可以直接使用类名.属性的形式来进行。
一个正确的用法的例子如下:
1 | def readImage(fp): |
实例属性和类属性
实例属性归属于类本身,但是类的实例也可以像访问属性一样访问,同时还可以通过类名.类属性名来访问。
需要注意的是,千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。