python 基础语法
一、数据类型和变量
1、在需要在字符中使用特殊字符时,python
用反斜杠(\)
转义字符。下表是开发中一些常用的转义字符:
转义字符 | 描述 |
---|---|
\ (在行尾时) |
续行符 |
\\ |
反斜杠符号 |
\' |
单引号 |
\" |
双引号 |
\n |
换行 |
\t |
横向制表符 |
2、如果字符串里面有很多字符都需要转义,就需要加很多\
,为了简化,Python 还允许用r''
表示''
内部的字符串默认不转义,参考如下代码:
|
|
3、如果字符串内部有很多换行,用\n
写在一行里不好阅读,为了简化,Python 允许用'''...'''
的格式表示多行内容。
4、在python
中,在 Python 中,布尔值的首字母是大写的(True
,False
)
5、在python3
中,有两种除法:
一种除法是/
,其计算结果是浮点数,即使是两个整数恰好整除,结果也是浮点数:
|
|
还有一种除法是//
,称为地板除,两个整数的除法仍然是整数:
|
|
这一点和python2
的语法有差异。
二、使用 list
1、list
是一种有序的集合,可以随时添加和删除其中的元素。
2、用len()
函数可以获得list
元素的个数:
|
|
3、list
可以用负数做索引,例如-1
做索引,可以直接获取最后一个元素:
|
|
4、append
可以往 list 中追加元素到末尾。
5、也可以把元素插入到指定的位置,比如索引号为 1 的位置:
|
|
6、pop
会删除 list 末尾的元素,如果要删除指定位置的元素,可以用pop(i)
方法,其中i
是索引位置。
7、如果要把某个元素替换成别的元素,可以直接赋值给对应的索引位置:
|
|
三、使用 tuple
1、tuple
和list
非常类似,但是tuple
一旦初始化就不能修改。
2、如果要定义一个空的tuple
,可以写成()
;但是,要定义一个只有 1 个元素的tuple
,如果你这么定义:
|
|
定义的不是tuple
,是1
这个数!这是因为括号()
既可以表示tuple
,又可以表示数学公式中的小括号,这就产生了歧义,因此,Python
规定,这种情况下,按小括号进行计算,计算结果自然是1
。
所以,只有 1 个元素的tuple
定义时必须加一个逗号,
,来消除歧义:
|
|
四、循环
1、如果要计算 1-100 的整数之和,从 1 写到 100 有点困难,幸好Python
提供一个range()
函数,可以生成一个整数序列,再通过list()
函数可以转换为list
。比如range(5)
生成的序列是从 0 开始小于 5 的整数:
|
|
如果要计算 0 ~ 100 的和,可以参考如下代码:
|
|
五、使用 dict
1、Python
内置了字典:dict
的支持,dict
全称dictionary
,在其他语言中也称为map
,使用键-值(key-value)存储,具有极快的查找速度。
2、如果要删除dict
中的一个key
,可以用pop(key)
方法,对应的value
也会从dict
中删除。
3、和 list 比较,dict 有以下几个特点:
- 查找和插入的速度极快,不会随着 key 的增加而变慢;
- 需要占用大量的内存,内存浪费多。
而 list 相反:
- 查找和插入的时间随着元素的增加而增加;
- 占用空间小,浪费内存很少。
所以,dict 是用空间来换取时间的一种方法。
六、使用 set
1、set 中不可以放入可变对象,例如,下面代码会出错:
|
|
这一点和 JS 里的 Set 有出入,在 JS 中,引用类型对象也可以放入 Set 对象中。
七、函数
1、python 内置了很多有用的函数,我们可以直接从 pyhton 的官方网站中查看到。
2、如果想定义一个什么事也不做的空函数,可以用pass
语句:
|
|
3、python 的函数可以返回多个值,但其实就是一个tuple
。
4、默认参数
python 的函数传的参数数量需要与函数定义参数的数量一致,如果不一致,就会报错,参考如下代码:
|
|
为了避免这种情况,可以使用默认参数,参考如下代码:
|
|
需要注意的是,设置默认参数时,必选参数在前,默认参数在后,否则 Python 的解释器会报错。另外,当默认参数的默认值是一个引用变量时,我们会很有可能掉进坑里,参考如下代码:
|
|
当正常调用时,结果似乎不错:
|
|
当使用默认参数调用时,一开始结果也是对的:
|
|
但是,再次调用add_end()
时,结果就不对了:
|
|
很多初学者很疑惑,默认参数是[],但是函数似乎每次都“记住了”上次添加了’END’后的 list。
原因解释如下: Python 函数在定义的时候,默认参数 L 的值就被计算出来了,即[],因为默认参数 L 也是一个变量,它指向对象[],每次调用该函数,如果改变了 L 的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。
所以,定义默认参数要牢记一点:默认参数必须指向不变对象!
在 JS 的 ES6 语法中,为函数设置默认参数时,不会出现以上 Python 中遇到的问题,在这一点上,JS 和 Python 有差异
要修改上面的例子,我们可以用 None 这个不变对象来实现:
|
|
现在,无论调用多少次,都不会有问题:
|
|
5、可变参数
定义可变参数和定义一个list
或tuple
参数相比,仅仅在参数前面加了一个号。在函数内部,参数numbers
接收到的是一个tuple
,因此,函数代码完全不变。Python
还允许我们在list
或tuple
前面加一个号,把list
或tuple
的元素变成可变参数传进去。
6、关键字参数
可变参数允许我们传入 0 个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple
。而关键字参数允许我们可以传入 0 个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict
。参考如下代码:
|
|
函数person
除了必选参数name
和age
外,还接受关键字参数kw
。在调用该函数时,可以只传入必选参数:
|
|
也可以传入任意个数的关键字参数:
|
|
和可变参数类似,也可以先组装出一个 dict,然后,把该 dict 转换为关键字参数传进去:
|
|
7、尾递归 在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。
这个时候可以使用尾递归来优化算法,王二认为,尾递归的核心要素在于函数return
出来的东西不能对其父作用域有依赖关系
八、切片
Python 提供了切片(Slice
)操作符,帮我们完成取一个list
或tuple
的部分元素的操作。
Slice
的常见用法参考如下代码:
|
|
九、迭代
1、默认情况下,dict
迭代的是key
。如果要迭代value
,可以用for value in d.values()
,如果要同时迭代key
和value
,可以用for k, v in d.items()
。
2、可以通过collections
模块的Iterable
类型再通过isinstance
方法判断一个对象是否是可迭代对象,参考如下代码:
|
|
3、如果要对list
实现下标循环,可以用Python
内置的enumerate
函数可以把一个list
变成索引-元素对,参考如下代码:
|
|
4、上面的for
循环里,同时引用了两个变量,在Python
里是很常见的,参考如下代码:
|
|
十、列表生成式
1、在**python 基础语法小结(一)**里,我们了解到用list(range(5))
可以生成的序列从 0 开始小于 5 的整数:
|
|
但如果要生成[0x0, 1x1, 2x2, 3x3, 4x4]
就会有些麻烦,需要用for
循环:
|
|
但是循环太繁琐,而列表生成式则可以用一行语句代替循环生成上面的list
:
|
|
2、for
循环后面还可以加上if
判断,这样我们就可以筛选出仅偶数的平方:
|
|
3、还可以使用两层循环,可以生成全排列:
|
|
4、列表生成式还可以使用两个变量来生成 list:
|
|
十一、生成器
1、通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含 100 万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的 list,从而节省大量的空间。在 Python 中,这种一边循环一边计算的机制,称为生成器:generator
。
2、要创建一个generator
,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]
改成()
,就创建了一个generator
:
|
|
3、另外一个创建generator
的方法是在一个函数定义中包含yield
关键字,例如下面一个
计算斐波拉契数列(Fibonacci)的函数:
|
|
只需要将其中的print(b)
改为yield b
,那么fib
函数就变成generator
:
|
|
十二、迭代器
1、凡是可作用于 for 循环的对象都是Iterable
类型;
例如集合数据类型,list
、tuple
、dict
、set
、str
等;
还有generator
,包括生成器和带yield
的 generator function。
2、凡是可作用于next()
函数的对象都是Iterator
类型,它们表示一个惰性计算的序列;
3、集合数据类型如list
、dict
、str
等是Iterable
但不是Iterator
,不过可以通过iter()
函数获得一个Iterator
对象。而generator
既是Iterable
类型也是Iterator
类型。
3、王二认为,可以将Iterable
类型比喻为本科生,Iterator
类型比喻为硕士生,硕士生一定是本科生,但本科生不一定是研究生,本科生(Iterable
)可以通过考研(iter()
)变为研究生(Iterator
)。
十三、高阶函数
1、map
map()
函数接收两个参数,一个是函数,一个是Iterable
,map
将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator
返回。
2、reduce
reduce
把一个函数作用在一个序列[x1, x2, x3, ...]
上,这个函数必须接收两个参数,reduce
把结果继续和序列的下一个元素做累积计算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
3、filter
filter()
也接收一个函数和一个序列。和map()
不同的是,filter()
把传入的函数依次作用于每个元素,然后根据返回值是True
还是False
决定保留还是丢弃该元素。
例如,在一个list
中,删掉偶数,只保留奇数,可以这么写:
|
|
需要注意的是,以上返回的序列是一个新的序列,原来的data
序列不变。
4、用filter
求素数
计算素数的一个方法是埃氏筛法,它的算法理解起来非常简单:
首先,列出从 2 开始的所有自然数,构造一个序列:
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, …
取序列的第一个数 2,它一定是素数,然后用 2 把序列的 2 的倍数筛掉:
3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, …
取新序列的第一个数 3,它一定是素数,然后用 3 把序列的 3 的倍数筛掉:
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, …
取新序列的第一个数 5,然后用 5 把序列的 5 的倍数筛掉:
7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, …
不断筛下去,就可以得到所有的素数。
用 Python 来实现这个算法,可以先构造一个从 3 开始的奇数序列:
|
|
注意这是一个生成器,并且是一个无限序列。
然后定义一个筛选函数:
|
|
最后,定义一个生成器,不断返回下一个素数:
|
|
这个生成器先返回第一个素数 2,然后,利用filter()
不断产生筛选后的新的序列。
由于primes()
也是一个无限序列,所以调用时需要设置一个退出循环的条件:
|
|
注意到Iterator
是惰性计算的序列,所以我们可以用 Python 表示“全体自然数”,“全体素数”这样的序列,而代码非常简洁。
5、 sorted
sorted()
函数也是一个高阶函数,它还可以接收一个key
函数来实现自定义的排序,接收一个reverse
函数来实现反向排序,参考如下代码:
|
|
十四、返回函数
1、高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
2、闭包 如果返回的函数依赖其父作用域,那么就形成了闭包。
3、另外需要注意的是,返回的函数并不会立刻执行,而是直到调用了 f()才执行。参考如下代码:
|
|
我们发现,返回的结果都为9
,原因就在于返回的函数引用了其父作用域的变量i
,但它并非立刻执行。当for
循环完成时,变量i
已经变成了3
,因此最终结果为9
。
如果要解决此问题,再创建一个函数,用该函数的参数绑定循环变量当前的值,脱离对父作用域的依赖,参考如下代码:
|
|
十五、匿名函数
在 Python 中,对匿名函数提供了一些支持,下面的f(x)
可以写成匿名函数lambda x: x * x
|
|
十六、装饰器
1、在java
中有个设计模式叫装饰者模式,可以扩充类的功能。在python
中,要想实现类似的功能,可以使用“装饰器”(Decorator)。
2、参考如下简单的代码:
|
|
简单来说。把@log
放到now()
函数的定义处,相当于执行了语句:
now = log(now)
3、如果decorator
本身需要传入参数,那就需要编写一个返回decorator
的高阶函数,写出来会更复杂。比如,要自定义log
的文本:
|
|
4、以上两种decorator
的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有__name__
等属性,但你去看经过 decorator 装饰之后的函数,它们的__name__
已经从原来的'now'
变成了'hala'
:
|
|
因为返回的那个hala()
函数名字就是'hala'
,所以,需要把原始函数的__name__
等属性复制到hala()
函数中,否则,有些依赖函数签名的代码执行就会出错。
不需要编写hala.__name__ = func.__name__
这样的代码,Python 内置的functools.wraps
就是干这个事的,所以,一个完整的decorator
的写法如下:
|
|
或者针对带参数的 decorator:
|
|
十七、偏函数
1、Python 的functools
模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。要注意,这里的偏函数和数学意义上的偏函数不一样。
2、使用场景
int()
函数可以把字符串转换为整数,当仅传入字符串时,int()
函数默认按十进制转换:
|
|
int()
函数还提供额外的 base 参数,默认值为10
。如果传入base
参数,就可以做 N 进制的转换:
|
|
假设要转换大量的二进制字符串,每次都传入int(x, base=2)
非常麻烦,于是,我们想到,可以定义一个int2()
的函数,默认把base=2
传进去:
|
|
这样,我们转换二进制就非常方便了:
|
|
我们使用偏函数也能解决上述问题,参考如下代码:
|
|
所以,简单总结functools.partial
的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。
注意到上面的新的 int2 函数,仅仅是把 base 参数重新设定默认值为 2,但也可以在函数调用时传入其他值:
|
|
实际上,在创建偏函数时,可以接收函数对象、*args 和**kw 这 3 个参数,当传入:
|
|
实际上固定了int()
函数的关键字参数base
,也就是:
|
|
相当于:
|
|
当传入:
|
|
实际上会把10
作为*args
的一部分自动加到左边,也就是:
|
|
相当于:
|
|
结果为10
。
十八、类和实例
1、在 Python 中,定义类通过 class 关键字来实现:
|
|
定义好了 Student 类,就可以根据 Student 类创建出 Student 的实例,创建实例通过类名+()实现:
|
|
十九、__init__
方法
通过定义一个特殊的__init__
方法,在创建实例的时候,我们可以把一些类的属性绑定上去,参考如下代码:
|
|
这时候我们的的学生就有了自己的名字与分数:
|
|
需要注意的是,__init__
方法的第一个参数永远是self
,表示创建的实例本身,因此,在__init__
方法内部,就可以把各种属性绑定到self
,因为self
就指向创建的实例本身。
二十、私有变量
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__
,在Python
中,实例的变量名如果以__
开头,就变成了一个私有变量(private
),只有内部可以访问,外部不能访问,所以,我们把Student
类改一改:
|
|
改完后,对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量.__name
和实例变量.__score
了:
|
|
那就一定不能从外部访问了吗?其实也不是。不能直接访问__name
是因为Python
解释器对外把__name
变量改成了_Student__name
,所以,仍然可以通过_Student__name
来访问__name
变量,但是最好别这么干。
二十一、继承
在上述的类Student
中 :
|
|
我们注意到Stuedent
中紧跟着一对括号,括号中是object
,那么Student
就是从object
上继承下来的。
如果想定义两个或多个父类那该怎么办呢?也很简单,写上两个就好了:
|
|
二十二、MixIn
在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich
继承自Bird
。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Dog
除了继承自Animal
外,再同时继承Runnable
。这种设计通常称之为MixIn
。
MixIn
的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn
的功能,而不是设计多层次的复杂的继承关系。
个人认为MixIn
的概念类似于Java
中一个类可以添加多个接口这个概念。
二十三、判断对象类型
我们可以使用type()
、isinstance()
来判断对象的类型,他们写起来就像这样:
|
|
二十四、使用 dir()
dir()
这个函数算是惊艳到王二了,它返回一个包含字符串的list
,获得一个传入对象的所有属性和方法:
|
|
当然,我们还可以通过getattr()
、setattr()
以及hasattr()
来直接操作一个对象的状态。
二十五、实例属性和类属性
在编码的时候,不要把实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性,参考如下代码:
|
|
二十六、使用__slots__
如果我们想要限制实例的属性,可以使用__slots__
,参考如下代码:
|
|
由于'score'
没有被放到__slots__
中,所以不能绑定score
属性,试图绑定score
将得到AttributeError
的错误。
使用__slots__
要注意,__slots__
定义的属性仅对当前类实例起作用,对继承的子类是不起作用的,除非在子类中也定义__slots__
,这样,子类实例允许定义的属性就是自身的__slots__
加上父类的__slots__
。
二十七、使用@property
Python
内置的@property
装饰器可以把一个方法变成属性调用,参考如下代码:
|
|
@property
的实现比较复杂,我们先考察如何使用。把一个getter
方法变成属性,只需要加上@property
就可以了,此时,@property
本身又创建了另一个装饰器@score.setter
,负责把一个setter
方法变成属性赋值,于是,我们就拥有一个可控的属性操作:
|
|
如果只定义 getter 方法,不定义 setter 方法,那它就是一个只读属性。