l 字符串:
转义字符\
可以转义很多字符,比如\n
表示换行,\t
表示制表符,字符\
本身也要转义,所以\\
表示的字符就是\
;
为了简化,Python还允许用r''
表示''
内部的字符串默认不转义
如果字符串内部有很多换行,用\n
写在一行里不好阅读,为了简化,Python允许用'''...'''
的格式表示多行内容
l 空值
空值是Python里一个特殊的值,用None
表示。None
不能理解为0
,因为0
是有意义的,而None
是一个特殊的空值。
l 变量
在Python中,等号=
是赋值语句,可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量
理解变量在计算机内存中的表示也非常重要,如写 a = 'ABC'时,Python解释器干了两件事情: 1、在内存中创建了一个'ABC'的字符串;2、在内存中创建了一个名为a的变量,并把它指向'ABC'。
a = 'ABC'
b = a
a = 'XYZ'
print(b) b的值是’ABC’
l 常量
所谓常量就是不能变的变量,比如常用的数学常数π就是一个常量。在Python中,通常用全部大写的变量名表示常量
l 小结
Python支持多种数据类型,在计算机内部,可以把任何数据都看成一个“对象”,而变量就是在程序中用来指向这些数据对象的,对变量赋值就是把数据和变量给关联起来。
对变量赋值x = y是把变量x指向真正的对象,该对象是变量y所指向的。随后对变量y的赋值不影响变量x的指向。
注意:Python的整数没有大小限制,而某些语言的整数根据其存储长度是有大小限制的,例如Java对32位整数的范围限制在-2147483648-2147483647。
Python的浮点数也没有大小限制,但是超出一定范围就直接表示为inf(无限大)。
l Python字符串
在最新的Python 3版本中,字符串是以Unicode编码的,也就是说,Python的字符串支持多语言。
print('包含中文的str')
对于单个字符的编码,Python提供了ord()
函数获取字符的整数表示,chr()
函数把编码转换为对应的字符
由于Python的字符串类型是str
,在内存中以Unicode表示,一个字符对应若干个字节。如果要在网络上传输,或者保存到磁盘上,就需要把str
变为以字节为单位的bytes
。
Python对bytes
类型的数据用带b
前缀的单引号或双引号表示
要注意区分'ABC'
和b'ABC'
,前者是str
,后者虽然内容显示得和前者一样,但bytes
的每个字符都只占用一个字节
以Unicode表示的str
通过encode()
方法可以编码为指定的bytes
,例如
反过来,如果我们从网络或磁盘上读取了字节流,那么读到的数据就是bytes
。要把bytes
变为str
,就需要用decode()
方法
l 格式化
%
运算符就是用来格式化字符串的。
l 小结
Python 3的字符串使用Unicode,直接支持多语言。
当str
和bytes
互相转换时,需要指定编码。最常用的编码是UTF-8
。Python当然也支持其他编码方式,比如把Unicode编码成GB2312
:
>>> '中文'.encode('gb2312')
b'\xd6\xd0\xce\xc4'
但这种方式纯属自找麻烦,如果没有特殊业务要求,请牢记仅使用UTF-8
编码。
l List:(可以使用list来模拟堆栈和队列)
Python内置的一种数据类型是列表:list。list是一种有序的集合,可以随时添加和删除其中的元素。
追加元素: List.append(‘xxx’)
插入元素:list.insert(1,’xxxx’)
删除元素:list.pop(i) 删除索引为i的元素。
替换元素: list(i)=’xxxx’
l Tuple:元组
tuple和list非常类似,但是tuple一旦初始化就不能修改,因为tuple不可变,所以代码更安全
看一个“可变的”tuple:t = ('a', 'b', ['A', 'B']) t[2][0]=’X’ t变为('a', 'b', ['X', 'B'])
变得是list的元素,tuple指向的list没有变
l 小结
list和tuple是Python内置的有序集合,一个可变,一个不可变。根据需要来选择使用它们。
Python内置了字典:dict的支持,dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度。
这种key-value存储方式,在放进去的时候,必须根据key算出value的存放位置,这样,取的时候才能根据key直接拿到value。
一个key只能对应一个value,如果key不存在,dict就会报错;
要避免key不存在的错误,有两种办法,一是通过in
判断key是否存在,(注意:返回None
的时候Python的交互环境不显示结果。)
二是通过dict提供的get()
方法,如果key不存在,可以返回None
,或者自己指定的value:
要删除一个key,用pop(key)
方法,对应的value也会从dict中删除:
dict是用空间来换取时间的一种方法。
dict的key必须是不可变对象。要保证hash的正确性,作为key的对象就不能变。在Python中,字符串、整数等都是不可变的,因此,可以放心地作为key。而list是可变的,就不能作为key
set和dict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key。
要创建一个set,需要提供一个list作为输入集合
set可以看成数学意义上的无序和无重复元素的集合,因此,两个set可以做数学意义上的交集、并集等操作
l 不可变对象
所以,对于不变对象来说,调用对象自身的任意方法,也不会改变该对象自身的内容。相反,这些方法会创建新的对象并返回,这样,就保证了不可变对象本身永远是不可变的。
l 小结
定义函数时,需要确定函数名和参数个数;
如果有必要,可以先对参数的数据类型做检查;
函数体内部可以用return
随时返回函数结果;
函数执行完毕也没有return
语句时,自动return None
。
函数可以同时返回多个值,但其实就是一个tuple。
Python的函数定义非常简单,但灵活度却非常大。除了正常定义的必选参数(位置参数)外,还可以使用默认参数、可变参数和关键字参数,使得函数定义出来的接口,不但能处理复杂的参数,还可以简化调用者的代码。
定义默认参数要牢记一点:默认参数必须指向不变对象!
可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个。
定义可变参数和定义一个list或tuple参数相比,仅仅在参数前面加了一个*
号。在函数内部,参数numbers
接收到的是一个tuple,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数:
所以Python允许你在list或tuple前面加一个*
号,把list或tuple的元素变成可变参数传进去。
def calc(*numbers):xxxxx
>>>nums = [1, 2, 3]
>>>calc(*nums)
可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:',kw)
>>>extra = {'city': 'Beijing', 'job': 'Engineer'}
>>>person('Jack', 24, **extra)
name: Jack age:24 other: {'city': 'Beijing', 'job': 'Engineer'}
**extra
表示把extra
这个dict的所有key-value用关键字参数传入到函数的**kw
参数,kw
将获得一个dict,注意kw
获得的dict是extra
的一份拷贝,对kw
的改动不会影响到函数外的extra
。
def person(name, age, *, city,job):
print(name, age, city, job)
和关键字参数**kw
不同,命名关键字参数需要一个特殊分隔符*
,*
后面的参数被视为命名关键字参数。
如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*
了:
defperson(name,age, *args, city, job):
print(name, age, args, city, job)
命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:
在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
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的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。
命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。
定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符*
,否则定义的将是位置参数。
L = list(range(100))
请写出:L[:10] L[2:10] L[:10:2] L[:] L[: : 5]
tuple也是一种list,唯一区别是tuple不可变。因此,tuple也可以用切片操作,只是操作的结果仍是tuple:
>>> (0, 1, 2, 3, 4, 5)[:3]
(0, 1, 2)
字符串'xxx'
也可以看成是一种list,每个元素就是一个字符。因此,字符串也可以用切片操作,只是操作结果仍是字符串:
>>> 'ABCDEFG'[::2]
'ACEG'
如果给定一个list或tuple,我们可以通过for
循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)。List, tuple, dict对象均可迭代。
默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.values()
,如果要同时迭代key和value,可以用for k, v in d.items()
。
如何判断一个对象是可迭代对象呢?方法是通过collections模块的Iterable类型判断:
fromcollections
importIterable
isinstance(
'abc', Iterable)
# str是否可迭代
如果要对list实现类似Java那样的下标循环怎么办?Python内置的(枚举)enumerate
函数可以把一个list变成索引-元素对,这样就可以在for
循环中同时迭代索引和元素本身:
fori, value
inenumerate([
'A',
'B',
'C']):
... print(i, value)
列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。如:list(range(1, 11))
For循环前,写元素表达式,For循环后,还可以加上if判断
>>>[x * x
forx
inrange(
1,
11)
ifx %
2==
0]
[
4,
16,
36,
64,
100]
还可以使用两层循环,可以生成全排列:
>>>[m + n
form
in'ABC'forn
in'XYZ']
[
'AX',
'AY',
'AZ',
'BX',
'BY',
'BZ',
'CX',
'CY',
'CZ']
import os # 导入os模块,模块的概念后面讲到
>>>[d for d in os.listdir('.')] # os.listdir可以列出文件和目录
for
循环其实可以同时使用两个甚至多个变量,比如dict
的items()
可以同时迭代key和value
>>>d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>>[k + '=' + v for k, v in d.items()]
['y=B', 'x=A','z=C']
最后把一个list中所有的字符串变成小写:
>>>L = ['Hello', 'World', 'IBM', 'Apple']
>>>[s.lower() for s in L] #小写 ///// [s.upper()for s in L] #大写
['hello','world', 'ibm', 'apple']
使用内建的isinstance
函数可以判断一个变量是不是字符串:isinstance(x, str)
如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
只要把一个列表生成式的[]
改成()
,就创建了一个generator
g = (x * x forx in range(10))
>>>g
可以通过next()
函数获得generator的下一个返回值。一次次调用next()太变态,正确的方法是使用for
循环,因为generator也是可迭代对象
generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for
循环无法实现的时候,还可以用函数来实现。如斐波那契数列。
要把fib
函数变成generator,只需要把print(b)
改为yield b
就可以了:
这就是定义generator的另一种方法。如果一个函数定义中包含yield
关键字,那么这个函数就不再是一个普通函数,而是一个generator:
这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return
语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()
的时候执行,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行。
凡是可作用于for
循环的对象都是Iterable
类型;
可以被next()
函数调用并不断返回下一个值的对象称为迭代器:Iterator
。它们表示一个惰性计算的序列;
生成器都是Iterator
对象,集合数据类型如list
、dict
、str
等是Iterable
但不是Iterator
,不过可以通过iter()
函数获得一个Iterator
对象。
Python的for
循环本质上就是通过不断调用next()
函数实现的,例如
函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。
l 函数本身也可以赋值给变量,即:变量可以指向函数。
l 那么函数名是什么呢?函数名其实就是指向函数的变量!
l 传入函数:既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
小结
把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。
所以,map()
作为高阶函数,事实上它把运算规则抽象了,因此,我们不但可以计算简单的f(x)=x2,还可以计算任意复杂的函数,比如,把这个list所有数字转为字符串:
>>> list(map(str, [1, 2, 3, 4, 5, 6,7, 8, 9]))
['1', '2', '3', '4', '5', '6', '7', '8', '9']
reduce
的用法。reduce
把一个函数作用在一个序列[x1, x2, x3, ...]
上,这个函数必须接收两个参数,reduce
把结果继续和序列的下一个元素做累积计算,其效果就是
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2),x3), x4)
如把序列[1, 3, 5, 7, 9]
变换成整数13579
>>> from functools import reduce
>>> def fn(x, y):
... returnx * 10 + y
...
>>> reduce(fn, [1, 3, 5, 7, 9])
13579
Python内建的filter()
函数用于过滤序列。
和map()
类似,filter()
也接收一个函数和一个序列。和map()
不同的是,filter()
把传入的函数依次作用于每个元素,然后根据返回值是True
还是False
决定保留还是丢弃该元素。
注意到filter()
函数返回的是一个Iterator
,也就是一个惰性序列,所以要强迫filter()
完成计算结果,需要用list()
函数获得所有结果并返回list。
Python内置的sorted()
函数就可以对list进行排序:此外,sorted()
函数也是一个高阶函数,它还可以接收一个key
函数来实现自定义的排序,例如按绝对值大小排序:
要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True
:
当我们调用lazy_sum()
时,返回的并不是求和结果,而是求和函数
def lazy_sum(*args):
defsum():
ax = 0
forn in args:
ax = ax + n
returnax
returnsum
当我们调用lazy_sum()
时,每次调用都会返回一个新的函数,即使传入相同的参数:
注意到返回的函数在其定义内部引用了局部变量args
,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。
另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()
才执行。
返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
关键字lambda
表示匿名函数,冒号前面的x
表示函数参数。
匿名函数有个限制,就是只能有一个表达式,不用写return
,返回值就是该表达式的结果。
用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:
#将匿名函数作为返回值
def build(x,y):
return lambda: x*x+y*y
q=build(5,8)()
print(q)
输出为89.
假设我们要增强now()
函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()
函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:
import functools
deflog(func):
@functools.wraps(func)
defwrapper(*args, **kw):
print(
'call %s():'% func.__name__)
return
func(*args, **kw)
return
wrapper
@log
def now():
print('2015-3-25')
只需记住在定义wrapper()
的前面加上@functools.wraps(func)
即可。
int()
函数可以把字符串转换为整数,当仅传入字符串时,int()
函数默认按十进制转换:还提供额外的base
参数,默认值为10
。如果传入base
参数,就可以做N进制的转换:
>>> int('12345', base=8)
5349
>>> int('12345', 16)
74565
假设要转换大量的二进制字符串,每次都传入int(x, base=2)
非常麻烦,于是,我们想到,可以定义一个int2()
的函数,默认把base=2
传进去
defint2(x, base=2):
return
int(x, base)
functools.partial
就是帮助我们创建一个偏函数的,不需要我们自己定义int2()
,可以直接使用下面的代码创建一个新的函数int2
:
>>> import functools
>>> int2 = functools.partial(int,base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85
简单总结functools.partial
的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。
创建偏函数时,实际上可以接收函数对象、*args
和**kw
这3个参数
当函数的参数个数太多,需要简化时,使用functools.partial
可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。
文件www.py
的模块名就是mycompany.web.www
,两个文件utils.py
的模块名分别是mycompany.utils
和mycompany.web.utils
。
自己创建模块时要注意命名,不能和Python自带的模块名称冲突。例如,系统自带了sys模块,自己的模块就不可命名为sys.py,否则将无法导入系统自带的sys模块。
使用sys
模块的第一步,就是导入该模块:
导入sys
模块后,我们就有了变量sys
指向该模块,利用sys
这个变量,就可以访问sys
模块的所有功能。
sys
模块有一个argv
变量,用list存储了命令行的所有参数。argv
至少有一个元素,因为第一个参数永远是该.py文件的名称,例如:
运行python3 hello.py
获得的sys.argv
就是['hello.py']
;
运行python3 hello.py Michael
获得的sys.argv
就是['hello.py','Michael]
。
if __name__=='__main__':
test()
__name__ 是当前模块名,当模块被直接运行时模块名为 __main__ 。这句话的意思就是,当模块被直接运行时,以下代码块将被运行,当模块是被导入时,代码块不被运行。
l 作用域
在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在Python中,是通过_
前缀来实现的。
类似_xxx
和__xxx
这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc
,__abc
等;
所以,面向对象的设计思想是抽象出Class,根据Class创建Instance。
面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法。
数据封装、继承和多态是面向对象的三大特点,我们后面会详细讲解。
class Student(object):
pass
class
后面紧接着是类名,即Student
,类名通常是大写开头的单词,紧接着是(object)
,表示该类是从哪个类继承下来的,继承的概念我们后面再讲,通常,如果没有合适的继承类,就使用object
类,这是所有类最终都会继承的类。
bart =Student() 创建实例是通过类名+()实现的
由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__
方法,在创建实例的时候,就把name
,score
等属性绑上去:
注意到__init__
方法的第一个参数永远是self
,表示创建的实例本身,因此,在__init__
方法内部,就可以把各种属性绑定到self
,因为self
就指向创建的实例本身。
面向对象编程的一个重要特点就是数据封装。在上面的Student
类中,每个实例就拥有各自的name
和score
这些数据。我们可以通过函数来访问这些数据,比如打印一个学生的成绩:
>>>bart.print_score()
BartSimpson: 59
方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据;
通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节。
print(lisa.name, lisa.get_grade())
print(bart.name, bart.get_grade())
和静态语言不同,Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同:
但是,从前面Student类的定义来看,外部代码还是可以自由地修改一个实例的name
、score
属性:
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__
,在Python中,实例的变量名如果以__
开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问
双下划线结尾变量:变量名类似__xxx__
的是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__
、__score__
这样的变量名
单下划线变量:比如_name
,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”
双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name
是因为Python解释器对外把__name
变量改成了_Student__name
,所以,仍然可以通过_Student__name
来访问__name
变量
https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001431865288798deef438d865e4c2985acff7e9fad15e3000
由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal
的子类时,只要确保run()
方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:
对扩展开放:允许新增Animal
子类;
对修改封闭:不需要修改依赖Animal
类型的run_twice()
等函数。
对于静态语言(例如Java)来说,如果需要传入Animal
类型,则传入的对象必须是Animal
类型或者它的子类,否则,将无法调用run()
方法。
对于Python这样的动态语言来说,则不一定需要传入Animal
类型。我们只需要保证传入的对象有一个run()
方法就可以了:
这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写。
动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。
当我们拿到一个对象的引用时,如何知道这个对象是什么类型、有哪些方法呢?
l 使用type()
l 使用isinstance()
总是优先使用isinstance()判断类型,可以将指定类型及其子类“一网打尽”。
l 使用dir():获得一个对象的所有属性和方法,可以使用dir()
函数,它返回一个包含字符串的list,类似__xxx__
的属性和方法在Python中都是有特殊用途的,比如__len__
方法返回长度。剩下的都是普通属性或方法,
通过内置的一系列函数,我们可以对任意一个Python对象进行剖析,拿到其内部的数据。要注意的是,只有在不知道对象信息的时候,我们才会去获取对象信息。
在编写程序的时候,千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性