目录

python 基础语法

一、数据类型和变量

1、在需要在字符中使用特殊字符时,python用反斜杠(\)转义字符。下表是开发中一些常用的转义字符:

转义字符 描述
\(在行尾时) 续行符
\\ 反斜杠符号
\' 单引号
\" 双引号
\n 换行
\t 横向制表符

2、如果字符串里面有很多字符都需要转义,就需要加很多\,为了简化,Python 还允许用r''表示''内部的字符串默认不转义,参考如下代码:

1
2
3
4
>>> print('\\\t\\')
\       \
>>> print(r'\\\t\\')
\\\t\\

3、如果字符串内部有很多换行,用\n写在一行里不好阅读,为了简化,Python 允许用'''...'''的格式表示多行内容。

4、在python中,在 Python 中,布尔值的首字母是大写的(TrueFalse

5、在python3中,有两种除法:

一种除法是/,其计算结果是浮点数,即使是两个整数恰好整除,结果也是浮点数:

1
2
3
4
5
>>> 10 / 3
3.3333333333333335

>>> 9 / 3
3.0

还有一种除法是//,称为地板除,两个整数的除法仍然是整数:

1
2
>>> 10 // 3
3

这一点和python2的语法有差异。

二、使用 list

1、list是一种有序的集合,可以随时添加和删除其中的元素。

2、用len()函数可以获得list元素的个数:

1
2
3
>>> classmates = ['Michael', 'Bob', 'Tracy']
>>> len(classmates)
3

3、list可以用负数做索引,例如-1做索引,可以直接获取最后一个元素:

1
2
>>> classmates[-1]
'Tracy'

4、append可以往 list 中追加元素到末尾。

5、也可以把元素插入到指定的位置,比如索引号为 1 的位置:

1
2
3
4
>>> classmates = ['Michael', 'Bob', 'Tracy']
>>> classmates.insert(1, 'Jack')
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy']

6、pop 会删除 list 末尾的元素,如果要删除指定位置的元素,可以用pop(i)方法,其中i是索引位置。

7、如果要把某个元素替换成别的元素,可以直接赋值给对应的索引位置:

1
2
3
>>> classmates[1] = 'Sarah'
>>> classmates
['Michael', 'Sarah', 'Tracy']

三、使用 tuple

1、tuplelist非常类似,但是tuple一旦初始化就不能修改。

2、如果要定义一个空的tuple,可以写成();但是,要定义一个只有 1 个元素的tuple,如果你这么定义:

1
2
3
>>> t = (1)
>>> t
1

定义的不是tuple,是1这个数!这是因为括号()既可以表示tuple,又可以表示数学公式中的小括号,这就产生了歧义,因此,Python规定,这种情况下,按小括号进行计算,计算结果自然是1。 所以,只有 1 个元素的tuple定义时必须加一个逗号,,来消除歧义:

1
2
3
>>> t = (1,)
>>> t
(1,)

四、循环

1、如果要计算 1-100 的整数之和,从 1 写到 100 有点困难,幸好Python提供一个range()函数,可以生成一个整数序列,再通过list()函数可以转换为list。比如range(5)生成的序列是从 0 开始小于 5 的整数:

1
2
>>> list(range(5))
[0, 1, 2, 3, 4]

如果要计算 0 ~ 100 的和,可以参考如下代码:

1
2
3
4
sum = 0
for x in range(101):
    sum = sum + x
print(sum)

五、使用 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 中不可以放入可变对象,例如,下面代码会出错:

1
2
3
4
>>> set([1, 1, 2, 2, [3], 3])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

这一点和 JS 里的 Set 有出入,在 JS 中,引用类型对象也可以放入 Set 对象中。

七、函数

1、python 内置了很多有用的函数,我们可以直接从 pyhton 的官方网站中查看到。

2、如果想定义一个什么事也不做的空函数,可以用pass语句:

1
2
def nop():
    pass

3、python 的函数可以返回多个值,但其实就是一个tuple

4、默认参数

python 的函数传的参数数量需要与函数定义参数的数量一致,如果不一致,就会报错,参考如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
def power(x, n):
    s = 1
    while n > 0:
        n = n - 1
        s = s * x
    return s

>>> power(5, 3)
125

>>> power(5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: power() missing 1 required positional argument: 'n'

为了避免这种情况,可以使用默认参数,参考如下代码:

1
2
3
4
5
6
def power(x, n=2):
    s = 1
    while n > 0:
        n = n - 1
        s = s * x
    return s

需要注意的是,设置默认参数时,必选参数在前,默认参数在后,否则 Python 的解释器会报错。另外,当默认参数的默认值是一个引用变量时,我们会很有可能掉进坑里,参考如下代码:

1
2
3
def add_end(L=[]):
    L.append('END')
    return L

当正常调用时,结果似乎不错:

1
2
3
4
>>> add_end([1, 2, 3])
[1, 2, 3, 'END']
>>> add_end(['x', 'y', 'z'])
['x', 'y', 'z', 'END']

当使用默认参数调用时,一开始结果也是对的:

1
2
>>> add_end()
['END']

但是,再次调用add_end()时,结果就不对了:

1
2
3
4
>>> add_end()
['END', 'END']
>>> add_end()
['END', 'END', 'END']

很多初学者很疑惑,默认参数是[],但是函数似乎每次都“记住了”上次添加了’END’后的 list。

原因解释如下: Python 函数在定义的时候,默认参数 L 的值就被计算出来了,即[],因为默认参数 L 也是一个变量,它指向对象[],每次调用该函数,如果改变了 L 的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。

所以,定义默认参数要牢记一点:默认参数必须指向不变对象!

在 JS 的 ES6 语法中,为函数设置默认参数时,不会出现以上 Python 中遇到的问题,在这一点上,JS 和 Python 有差异

要修改上面的例子,我们可以用 None 这个不变对象来实现:

1
2
3
4
5
def add_end(L=None):
    if L is None:
        L = []
    L.append('END')
    return L

现在,无论调用多少次,都不会有问题:

1
2
3
4
>>> add_end()
['END']
>>> add_end()
['END']

5、可变参数 定义可变参数和定义一个listtuple参数相比,仅仅在参数前面加了一个号。在函数内部,参数numbers接收到的是一个tuple,因此,函数代码完全不变。Python还允许我们在listtuple前面加一个号,把listtuple的元素变成可变参数传进去。

6、关键字参数 可变参数允许我们传入 0 个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许我们可以传入 0 个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。参考如下代码:

1
2
def person(name, age, **kw):
    print('name:', name, 'age:', age, 'other:', kw)

函数person除了必选参数nameage外,还接受关键字参数kw。在调用该函数时,可以只传入必选参数:

1
2
>>> person('Michael', 30)
name: Michael age: 30 other: {}

也可以传入任意个数的关键字参数:

1
2
3
4
>>> person('Bob', 35, city='Beijing')
name: Bob age: 35 other: {'city': 'Beijing'}
>>> person('Adam', 45, gender='M', job='Engineer')
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}

和可变参数类似,也可以先组装出一个 dict,然后,把该 dict 转换为关键字参数传进去:

1
2
3
>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}

7、尾递归 在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。

这个时候可以使用尾递归来优化算法,王二认为,尾递归的核心要素在于函数return出来的东西不能对其父作用域有依赖关系

八、切片

Python 提供了切片(Slice)操作符,帮我们完成取一个listtuple的部分元素的操作。 Slice的常见用法参考如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#先创建一个0-99的数列
>>> L = list(range(100))
>>> L
[0, 1, 2, 3, ..., 99]

#可以通过切片轻松取出某一段数列。比如前10个数
>>> L[:10]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

#后10个数
>>> L[-10:]
[90, 91, 92, 93, 94, 95, 96, 97, 98, 99]

#前11-20个数
>>> L[10:20]
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

#前10个数,每两个取一个
>>> L[:10:2]
[0, 2, 4, 6, 8]

#所有数,每5个取一个
>>> L[::5]
[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]

#甚至什么都不写,只写[:]就可以原样复制一个list
>>> L[:]
[0, 1, 2, 3, ..., 99]

#tuple也是一种list,唯一区别是tuple不可变。因此,tuple也可以用切片操作,只是操作的结果仍是tuple
>>> (0, 1, 2, 3, 4, 5)[:3]
(0, 1, 2)

#字符串'xxx'也可以看成是一种list,每个元素就是一个字符。因此,字符串也可以用切片操作,只是操作结果仍是字符串
>>> 'ABCDEFG'[:3]
'ABC'
>>> 'ABCDEFG'[::2]
'ACEG'

九、迭代

1、默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.values(),如果要同时迭代keyvalue,可以用for k, v in d.items()

2、可以通过collections模块的Iterable类型再通过isinstance方法判断一个对象是否是可迭代对象,参考如下代码:

1
2
3
4
5
6
7
>>> from collections import Iterable
>>> isinstance('abc', Iterable) # str是否可迭代
True
>>> isinstance([1,2,3], Iterable) # list是否可迭代
True
>>> isinstance(123, Iterable) # 整数是否可迭代
False

3、如果要对list实现下标循环,可以用Python内置的enumerate函数可以把一个list变成索引-元素对,参考如下代码:

1
2
3
4
5
6
>>> for i, value in enumerate(['A', 'B', 'C']):
...     print(i, value)
...
0 A
1 B
2 C

4、上面的for循环里,同时引用了两个变量,在Python里是很常见的,参考如下代码:

1
2
3
4
5
6
>>> for x, y in [(1, 1), (2, 4), (3, 9)]:
...     print(x, y)
...
1 1
2 4
3 9

十、列表生成式

1、在**python 基础语法小结(一)**里,我们了解到用list(range(5))可以生成的序列从 0 开始小于 5 的整数:

1
2
>>>list(range(5))
[ 0,1, 2, 3, 4]

但如果要生成[0x0, 1x1, 2x2, 3x3, 4x4]就会有些麻烦,需要用for循环:

1
2
3
4
5
6
>>> L = []
>>> for x in range(5):
...    L.append(x * x)
...
>>> L
[0, 1, 4, 9, 16]

但是循环太繁琐,而列表生成式则可以用一行语句代替循环生成上面的list

1
2
>>> [x * x for x in range(5)]
[0, 1, 4, 9, 16]

2、for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:

1
2
>>> [x * x for x in range(1, 11) if x % 2 == 0]
[4, 16, 36, 64, 100]

3、还可以使用两层循环,可以生成全排列:

1
2
>>> [m + n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']

4、列表生成式还可以使用两个变量来生成 list:

1
2
3
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> [k + '=' + v for k, v in d.items()]
['y=B', 'x=A', 'z=C']

十一、生成器

1、通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含 100 万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的 list,从而节省大量的空间。在 Python 中,这种一边循环一边计算的机制,称为生成器:generator

2、要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> G = (x * x for x in range(10))
>>> G
<generator object <genexpr> at 0x000001D760F85BF8>

#而且用for迭代时,使用上去也没有差异

>>> for i in L:
...     print(i)
0
1
4
9
16
25
36
49
64
81

>>> for i in G:
...     print(i)
0
1
4
9
16
25
36
49
64
81

3、另外一个创建generator的方法是在一个函数定义中包含yield关键字,例如下面一个 计算斐波拉契数列(Fibonacci)的函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b
        n = n + 1
    return 'done'

>>> fib(6)
1
1
2
3
5
8
'done'

只需要将其中的print(b)改为yield b,那么fib函数就变成generator

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return 'done'

>>> f = fib(6)
>>> f
<generator object fib at 0x000001D760F85CA8>

>>> for i in f:
...     print(i)
1
1
2
3
5
8

十二、迭代器

1、凡是可作用于 for 循环的对象都是Iterable类型; 例如集合数据类型,listtupledictsetstr等; 还有generator,包括生成器和带yield的 generator function。

2、凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;

3、集合数据类型如listdictstr等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。而generator既是Iterable类型也是Iterator类型。

3、王二认为,可以将Iterable类型比喻为本科生,Iterator类型比喻为硕士生,硕士生一定是本科生,但本科生不一定是研究生,本科生(Iterable)可以通过考研(iter())变为研究生(Iterator)。

十三、高阶函数

1、map map()函数接收两个参数,一个是函数,一个是Iterablemap将传入的函数依次作用到序列的每个元素,并把结果作为新的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中,删掉偶数,只保留奇数,可以这么写:

1
2
3
4
5
data = [1, 2, 4, 5, 6, 9, 10, 15]
def is_odd(n):
    return n % 2 == 1
list(filter(is_odd, data))
# 结果: [1, 5, 9, 15]

需要注意的是,以上返回的序列是一个新的序列,原来的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 开始的奇数序列:

1
2
3
4
5
def _odd_iter():
    n = 1
    while True:
        n = n + 2
        yield n

注意这是一个生成器,并且是一个无限序列。

然后定义一个筛选函数:

1
2
def _not_divisible(n):
    return lambda x: x % n > 0

最后,定义一个生成器,不断返回下一个素数:

1
2
3
4
5
6
7
def primes():
    yield 2
    it = _odd_iter() # 初始序列
    while True:
        n = next(it) # 返回序列的第一个数
        yield n
        it = filter(_not_divisible(n), it) # 构造新序列

这个生成器先返回第一个素数 2,然后,利用filter()不断产生筛选后的新的序列。

由于primes()也是一个无限序列,所以调用时需要设置一个退出循环的条件:

1
2
3
4
5
6
# 打印1000以内的素数:
for n in primes():
    if n < 1000:
        print(n)
    else:
        break

注意到Iterator是惰性计算的序列,所以我们可以用 Python 表示“全体自然数”,“全体素数”这样的序列,而代码非常简洁。

5、 sorted sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,接收一个reverse函数来实现反向排序,参考如下代码:

1
2
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']

十四、返回函数

1、高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。

2、闭包 如果返回的函数依赖其父作用域,那么就形成了闭包。

3、另外需要注意的是,返回的函数并不会立刻执行,而是直到调用了 f()才执行。参考如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
def count():
    fs = []
    for i in range(1, 4):
        def f():
             return i*i
        fs.append(f)
    return fs

f1, f2, f3 = count()

>>> f1()
9
>>> f2()
9
>>> f3()
9

我们发现,返回的结果都为9,原因就在于返回的函数引用了其父作用域的变量i,但它并非立刻执行。当for循环完成时,变量i已经变成了3,因此最终结果为9

如果要解决此问题,再创建一个函数,用该函数的参数绑定循环变量当前的值,脱离对父作用域的依赖,参考如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
def count():
    def f(j):
        def g():
            return j*j
        return g
    fs = []
    for i in range(1, 4):
        fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
    return fs

>>> f1, f2, f3 = count()
>>> f1()
1
>>> f2()
4
>>> f3()
9

十五、匿名函数

在 Python 中,对匿名函数提供了一些支持,下面的f(x)可以写成匿名函数lambda x: x * x

1
2
def f(x):
    return x * x

十六、装饰器

1、在java中有个设计模式叫装饰者模式,可以扩充类的功能。在python中,要想实现类似的功能,可以使用“装饰器”(Decorator)。

2、参考如下简单的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
def log(func):
    def hala():
        print('在这里打印日志')
        return func()
    return hala

@log
def now():
    print('2015-3-25')

now()

#在这里打印日志
#2015-3-25

简单来说。把@log放到now()函数的定义处,相当于执行了语句:

now = log(now)

3、如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
def log(text):
    def decorator(func):
        def hala():
            print('在这里打印日志'+text)
            return func()
        return hala
    return decorator

@log('execute')
def now():
    print('2015-3-25')

now()

#在这里打印日志execute
#2015-3-25

4、以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有__name__等属性,但你去看经过 decorator 装饰之后的函数,它们的__name__已经从原来的'now'变成了'hala'

1
2
>>> now.__name__
'wrapper'

因为返回的那个hala()函数名字就是'hala',所以,需要把原始函数的__name__等属性复制到hala()函数中,否则,有些依赖函数签名的代码执行就会出错。

不需要编写hala.__name__ = func.__name__这样的代码,Python 内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import functools

def log(func):
    @functools.wraps(func)
    def hala():
        print('在这里打印日志')
        return func()
    return hala

@log
def now():
    print('2015-3-25')

或者针对带参数的 decorator:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import functools

def log(text):
    def decorator(func):
        @functools.wraps(func)
        def hala():
            print('在这里打印日志'+text)
            return func()
        return hala
    return decorator

@log('execute')
def now():
    print('2015-3-25')

十七、偏函数

1、Python 的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。要注意,这里的偏函数和数学意义上的偏函数不一样。

2、使用场景 int()函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换:

1
2
>>> int('12345')
12345

int()函数还提供额外的 base 参数,默认值为10。如果传入base参数,就可以做 N 进制的转换:

1
2
3
4
>>> int('12345', base=8)
5349
>>> int('12345', 16)
74565

假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去:

1
2
def int2(x, base=2):
    return int(x, base)

这样,我们转换二进制就非常方便了:

1
2
3
4
>>> int2('1000000')
64
>>> int2('1010101')
85

我们使用偏函数也能解决上述问题,参考如下代码:

1
2
3
4
5
6
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85

所以,简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。

注意到上面的新的 int2 函数,仅仅是把 base 参数重新设定默认值为 2,但也可以在函数调用时传入其他值:

1
2
>>> int2('1000000', base=10)
1000000

实际上,在创建偏函数时,可以接收函数对象、*args 和**kw 这 3 个参数,当传入:

1
int2 = functools.partial(int, base=2)

实际上固定了int()函数的关键字参数base,也就是:

1
int2('10010')

相当于:

1
2
kw = { 'base': 2 }
int('10010', **kw)

当传入:

1
max2 = functools.partial(max, 10)

实际上会把10作为*args的一部分自动加到左边,也就是:

1
max2(5, 6, 7)

相当于:

1
2
args = (10, 5, 6, 7)
max(*args)

结果为10

十八、类和实例

1、在 Python 中,定义类通过 class 关键字来实现:

1
2
class Student(object):
    pass

定义好了 Student 类,就可以根据 Student 类创建出 Student 的实例,创建实例通过类名+()实现:

1
2
3
>>> bart = Student()
>>> bart
<__main__.Student object at 0x000001FECADEE128>

十九、__init__方法

通过定义一个特殊的__init__方法,在创建实例的时候,我们可以把一些类的属性绑定上去,参考如下代码:

1
2
3
4
5
class Student(object):

    def __init__(self, name, score):
        self.name = name
        self.score = score

这时候我们的的学生就有了自己的名字与分数:

1
2
3
4
5
>>> bart = Student('Bart Simpson', 59)
>>> bart.name
'Bart Simpson'
>>> bart.score
59

需要注意的是,__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。

二十、私有变量

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,所以,我们把Student类改一改:

1
2
3
4
5
6
7
8
class Student(object):

    def __init__(self, name, score):
        self.__name = name
        self.__score = score

    def print_score(self):
        print('%s: %s' % (self.__name, self.__score))

改完后,对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量.__name实例变量.__score了:

1
2
3
4
5
>>> bart = Student('Bart Simpson', 98)
>>> bart.__name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'

那就一定不能从外部访问了吗?其实也不是。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量,但是最好别这么干。

二十一、继承

在上述的类Student中 :

1
2
class Student(object):
    pass

我们注意到Stuedent中紧跟着一对括号,括号中是object,那么Student就是从object上继承下来的。

如果想定义两个或多个父类那该怎么办呢?也很简单,写上两个就好了:

1
2
3
4
5
6
7
8
9
class Animal(object):
    pass

class Runnable(object):
    def run(self):
        print('Running...')

class Dog(Animal, Runnable):
    pass

二十二、MixIn

在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich继承自Bird。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Dog除了继承自Animal外,再同时继承Runnable。这种设计通常称之为MixIn

MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。

个人认为MixIn的概念类似于Java中一个类可以添加多个接口这个概念。

二十三、判断对象类型

我们可以使用type()isinstance()来判断对象的类型,他们写起来就像这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
>>> type(123)==type(456)
True
>>> type(123)==int
True
>>> type('abc')==type('123')
True
>>> type('abc')==str
True
>>> type('abc')==type(123)
False

>>> isinstance('abc', str)
True
>>> isinstance(123, int)
True

二十四、使用 dir()

dir()这个函数算是惊艳到王二了,它返回一个包含字符串的list,获得一个传入对象的所有属性和方法:

1
2
>>> dir('ABC')
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

当然,我们还可以通过getattr()setattr()以及hasattr()来直接操作一个对象的状态。

二十五、实例属性和类属性

在编码的时候,不要把实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性,参考如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
>>> class Student(object):
...     name = 'Student'
...
>>> s = Student() # 创建实例s
>>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
Student
>>> print(Student.name) # 打印类的name属性
Student
>>> s.name = 'Michael' # 给实例绑定name属性
>>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
Michael
>>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
Student
>>> del s.name # 如果删除实例的name属性
>>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
Student

二十六、使用__slots__

如果我们想要限制实例的属性,可以使用__slots__,参考如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Student(object):
    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

#然后,我们试试
>>> s = Student() # 创建新的实例
>>> s.name = 'Michael' # 绑定属性'name'
>>> s.age = 25 # 绑定属性'age'
>>> s.score = 99 # 绑定属性'score'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'

由于'score'没有被放到__slots__中,所以不能绑定score属性,试图绑定score将得到AttributeError的错误。

使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的,除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__

二十七、使用@property

Python内置的@property装饰器可以把一个方法变成属性调用,参考如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Student(object):

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value

@property的实现比较复杂,我们先考察如何使用。把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作:

1
2
3
4
5
6
7
8
>>> s = Student()
>>> s.score = 60 # OK,实际转化为s.set_score(60)
>>> s.score # OK,实际转化为s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
  ...
ValueError: score must between 0 ~ 100!

如果只定义 getter 方法,不定义 setter 方法,那它就是一个只读属性。