Python 3

Posted by Tong on August 25, 2019

Basics

标识符

  1. 第一个字符必须是字母表中字母或下划线 _ 。
  2. 标识符的其他的部分由字母、数字和下划线组成。
  3. 标识符对大小写敏感。
  4. 不可以是python中的关键字,如False、True、None、class等。

赋值

x = y = z = 1           # 多重赋值,True
x, y, z = 1, 3, 'hello' # 多元赋值,True
x += 1                  # 增量赋值,True
y = (x = x + 1)         # False, 等号右边不能时赋值语句

min = x if x > y else y # 注意写法

name

class Person:
    def __init__(self):
        pass
    def getAge(self):
        print(__name__) # __main__

p = Person()
p.getAge()
print(p.getAge.__name__) # getAge

解释型语言 (非独立,效率低)

解释型语言和编译型语言的定义: 计算机不能直接理解高级语言,只能直接理解机器语言,所以必须要把高级语言翻译成机器语言,计算机才能执行高级语言编写的程序。 翻译的方式有两种,一个是编译,一个是解释。两种方式只是翻译的时间不同。

解释型语言的定义: 解释型语言的程序不需要编译,在运行程序的时候才翻译,每个语句都是执行的时候才翻译。这样解释性语言每执行一次就需要逐行翻译一次,效率比较低。 现代解释性语言通常把源程序编译成中间代码,然后用解释器把中间代码一条条翻译成目标机器代码,一条条执行。

编译型语言的定义: 编译型语言写的程序在被执行之前,需要一个专门的编译过程,把程序编译成为机器语言的文件,比如exe文件,以后要运行的话就不用重新翻译了,直接使用编译的结果就行了(exe文件),因为翻译只做了一次,运行时不需要翻译,所以编译型语言的程序执行效率高。

pyc文件

  1. pyc文件就是Python的字节码文件。

  2. pyc文件只有在文件被当成模块导入时才会生成。好处是,当我们多次运行程序时,不需要重新对该模块进行解释。(主文件一般不会被其他模块导入,所以主文件一般不会生成pyc文件。)

  3. 在生成pyc文件的同时,写入了一个Long型的变量,用于记录最近修改的时间。每次载入前都先检查一下py文件和pyc文件的最后修改日期,如果不一致则会生成一个新的pyc文件。

变量作用域

b = 3

def f1(a):
    print(a) # 1
    print(b) # 3, b可以顺利打印

f1(1)
b = 3

def f1(a):
    print(a) # 1
    print(b) # UnboundLocalError: local variable 'b' referenced before assignment. 因为Python会先尝试从本地环境获取b
    b = 6

f1(1)
b = 3

def f1(a):
    global b
    print(a) # 1
    print(b) # 3
    b = 6

f1(1)
print(b) # 6
a = [1, 'Tong', 'April']
for element in a:
    print(element)
# 1
# Tong
# April

# element是全局变量,我们还能使用
print(element + ' is learning sth!') # April is learning sth!

数值及其运算

复数

a = 1 + 2j
b = 1 + 2J

print(a)            # (1+2j)
print(b)            # (1+2j)
print(a == b)       # True
print(a.imag)       # 2.0
print(a.real)       # 1.0
print(type(a.imag)) # <class 'float'>
print(a > b)        # TypeError: '>' not supported between instances of 'complex' and 'complex'

Bool为False的值

a = None
b = 0
c = 0.0
d = 0.0 + 0.0j
e = ""
f = [] # empty list is False
g = () # empty tuple is False
h = {} # empty dict

if a or b or c or d or e or f or g:
    print("True")
else:
    print("False")  # False will be our output

逻辑运算符

a = []
b = 'Wang'
c = 'Ling'
d = {}

# a and b
# if a is False, return a
# else return b
print(a and b) # []
print(b and c) # Ling
print(b and d) # {}

# a or b
# if a is True, return a
# else return b
print(a or b)  # Wang
print(b or c)  # Wang
print(a or d)  # {}


print(not a) # True
print(not b) # False

比较大小

1. 复数不支持比较大小

2. 类似元组、字符串、列表这类格式,在进行两者之间的比较时,先从第一个元素开始比较 ASCII 码值大小,如果相等,则依次向后比较,如果全部相等,则比较数量大小。

3. ASCII 码值大小:
   3.1 数字:
       0-9: 48-57

   3.2 字母
       A-Z: 65-90
       a-z: 97-122

4. Python2 支持数字与字符串之间的比较,而 Python3 则不支持。

5. 连续比较:`'a' < 'b' < 'c'`表示 `'a' < 'b' and 'b' < 'c'`

6. `==``!=`的机制与排序符号( `>` `<` `>=` `<=`)稍有不同,具体而言是选择反向方法的逻辑不通。`==``!=`从不抛出错误,因为Python会比较对象的ID,做最后一搏。而排序符号可能最后会抛出`TypeError`
a = [1, 2, 3]
b = (1, 2, 3)
print(a == b)# False
print(a > b) # TypeError: '>' not supported between instances of 'list' and 'tuple'

a += b vs. a = a + b

  1. +=背后的特殊方法是__iadd__。但是如果一个类没有实现这个方法的话,Python会调用__add__

  2. 对于可变序列(e.g. list, bytearray, array.array),a += b会使a原地改动。但是如果a没有实现__iadd__的话,a += b就等价于a = a + b:首先计算a + b,得到一个新的对象,然后赋值给a

l = [1, 2, 3]
print(id(l))    # 2625702223176
l *= 2
print(id(l))    # 2625702223176

t = (1, 2, 3)
print(id(t))    # 2625707199512
t *= 2
print(id(t))    # 2625706262248
  1. 不要把可变元素放在元组里面,要不然会发生奇怪的事情。

  2. 增量赋值不是一个原子操作。有可能在抛出异常的同时完成了操作。

t = (1, 2, [30, 40])
t[2] += [50, 60]

到底会发生什么情况?
a) t变成(1, 2, [30, 40, 50, 60])
b) 因为tuple不支持对它的元素赋值,所以抛出TypeError异常
c) 以上都不是
d) a) 和 b) 都是对的

上述问题选d)。

String

结尾

Python中的字符串并 不是 像C/C++一样以’\0’结尾,而是一个固定长度的字符数组。

join()

str.join(sequence) - 用于将序列中的元素以指定的字符连接生成一个新的字符串。

s1 = "-"
s2 = ""
seq = ("r", "u", "n", "o", "o", "b") # 字符串序列
print (s1.join( seq )) # r-u-n-o-o-b
print (s2.join( seq )) # runoob

startswith() / endswith()

string.startswith(str, beg, end)
string.endswith(str, beg, end)

string: 被检测的字符串
str:    指定的字符或者子字符串(可以使用元组,会逐一匹配)
beg:    设置字符串检测的起始位置(可选,从左数起)
end:    设置字符串检测的结束位置(可选,从左数起)
s = 'Wang Yi Hui'
print(s.startswith('Wang'))     # True (beg,end为可选输入)
print(s.startswith('Yi', 5))    # True
print(s.startswith('Yi', 5, 6)) # False (搜索位置不包含end)
print(s.startswith('Yi', 5, 7)) # True
print(s.startswith('Yi', 6))    # False

print(s.endswith('Hui', 8))     # True
print(s.endswith('Hui', 9))     # False
print(s.endswith('Hui', 1))     # True, beg只要在'Hui'出现及出现之前即可
print(s.endswith('Hui'))        # True
print(s.endswith('Hui', 8, 10)) # False
print(s.endswith('Hui', 8, 11)) # True

encode / decode

s = '王怡惠'
print(len(s))               # 3

b = s.encode('utf8')        # 使用UTF-8把str对象编码成bytes对象
print(b)                    # b'\xe7\x8e\x8b\xe6\x80\xa1\xe6\x83\xa0'
                            # bytes变量以b开头
print(len(b))               # 9

print(b.decode('utf8'))     # 王怡惠
                            # # 使用UTF-8把bytes对象解码成str对象

bytes / bytearray

wang = bytes('王怡惠', encoding='utf_8')
print(wang)                 # b'\xe7\x8e\x8b\xe6\x80\xa1\xe6\x83\xa0'
print(wang[0])              # 231, 每个元素都是range(256)的整数
print(wang[:1])             # b'\xe7', 切片后对象类型不变

wang_arr = bytearray(wang)
print(wang_arr)             # bytearray(b'\xe7\x8e\x8b\xe6\x80\xa1\xe6\x83\xa0')
print(wang[-1:])            # b'\xa0', 切片后对象类型不变

re (正则表达式)

基本规则

  1. \d可以匹配一个数字

  2. \w可以匹配一个字母或数字

  3. \s可以匹配一个空格(或者Tab等空白符)

  4. .可以匹配任意字符

  5. *表示匹配任意个字符

  6. +表示至少一个字符

  7. ?表示0或1个字符

  8. {n}表示n个字符

  9. {n, m}表示n-m个字符

  10. 对于特殊字符,要用\转义,例如要想匹配-,我们得写成\-

  11. []可以用来表示范围
    • [0-9a-zA-Z\_]可以匹配一个数字,字母或者下划线
    • [0-9a-zA-Z\_]+可以匹配 至少 一个数字,字母或者下划线组成的字符串,例如a100
    • [0-9a-zA-Z\_]*可以匹配由 任意个 数字,字母或者下划线组成的字符串
    • [0-9a-zA-Z\_]{0, 19}可以匹配长度为0到19的由数字,字母或者下划线组成的字符串。
  12. A|B表示匹配A或者B,所以(P|p)ython可以匹配Python或者python

  13. ^表示行的开头,^\d表示必须以数字开头

  14. $表示行的结束,\d$表示必须以数字结束

  15. ^py$表示整行匹配,所以我们只能匹配py

  16. 可以使用r前缀,就不用考虑转义问题了
s1 = 'ABC\\-001'
s2 = r'ABC\-001'
print(s1 == s2) # True

match()

match()成功匹配的话,返回一个Match对象,否则返回None

import re

# re.Match object; span=(0, 9), match='010-12345'>
print(re.match('\d{3}\-\d{3,8}$', '010-12345'))

# None
print(re.match('\d{3}\-\d{3,8}$', '010 12345'))

split()

import re

print('a b  c'.split(' '))                   # ['a', 'b', '', 'c']
print(re.split(r'[\s]+', 'a b  c'))          # ['a', 'b', 'c']
print(re.split(r'[\s\,]+', 'a,,b,  c'))      # ['a', 'b', 'c']
print(re.split(r'[\s\,\;]+', 'a,, b;;;  c')) # ['a', 'b', 'c']

group()

()表示要提取的分组。比如:^(\d{3})-(\d{3,8})$表示两个分组,可以直接从匹配的字符串提取出区号和本地号码。

import re

m = re.match(r'^(\d{3})-(\d{3,8})$', '010-12345')
print(m.group(0)) # 010-12345
print(m.group(1)) # 010
print(m.group(2)) # 12345

贪婪匹配

正则匹配默认时贪婪匹配。加个?就可以采用非贪婪匹配。

import re

print(re.match(r'^(\d+)(0*)$', '102300').groups())  # ('102300', '')
print(re.match(r'^(\d+?)(0*)$', '102300').groups()) # ('1023', '00')

可选标志

  1. re.I表示对大小写不敏感

  2. re.L表示做本地化识别 (local-aware) 匹配

  3. re.M表示多行匹配,影响^$

  4. re.S表示使.匹配包括换行在内的所有字符

  5. re.U根据Unicode字符集解析字符。这个标志影响 \w, \W, \b, \B.

  6. re.X该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。

import re

str1 = "Python's features"
str2 = re.match( r'(.*)on(.*?) .*', str1, re.M|re.I)
print(str2.group(1)) # Pyth

List

Delete

bicycles = ['trek', 'canodale', 'redline', 'specialized']

del bicycles[0]
print(bicycles)           # ['canodale', 'redline', 'specialized']

print(bicycles.pop())     # specialized

print(bicycles.pop(0))    # canodale

bicycles.remove('redline')
print(bicycles)           # []

Sort

  1. nums.sort() 改变原数组的排列顺序,sorted(nums)不改变。

  2. 两个可选参数:1)reverse, e.g. reverse=True, 2) key, e.g. key=len

nums = [2, 1, 4, 3]

print(sorted(nums)) # [1, 2, 3, 4]

print(nums)         # [2, 1, 4, 3]


nums.sort()        # 返回None
print(nums)        # [1, 2, 3, 4]

Copy

nums = [1]
nums_1 = nums[:] # 创建了一个新的数组nums_1
nums_2 = nums    # nums_2就是原数组nums的另一个名称,地址相同
print(nums_1)    # [1]
print(nums_2)    # [1]


nums.append(100)
print(nums_1)    # [1]
print(nums_2)    # [1, 100]
Shallow Copy vs. Deep Copy
import copy

# 对于简单的object, 例如数字, list,
# 浅拷贝和深拷贝没区别
# 注意:此时,对原元素的修改不会引起拷贝后元素的改变
a = 1000
b = copy.copy(a)
c = copy.deepcopy(a)
print(a is b)         # True
print(b is c)         # True

a = 1
print(b)              # 1000
print(c) # 1000

d = [1, 2, 3]
e = copy.copy(d)
f = copy.deepcopy(d)
print(d is e)         # False
print(e is f)         # False

d.append(4)
print(e)              # [1, 2, 3]
print(f)              # [1, 2, 3]


g = {1 : 1, 2 : 2}
h = copy.copy(g)
i = copy.deepcopy(g)
print(e is f)         # False

g[1] = 5
print(h)              # {1: 1, 2: 2}
print(i)              # {1: 1, 2: 2}

import copy

# 对于复杂的object, 例如list套list
# 浅拷贝和深拷贝有区别
# 浅拷贝只会复制list里的第一层,我们仍然能通过改变浅拷贝后的元素来修改原数组
# 深拷贝会完全复制每一层,这个深拷贝后的数组和原来的没有任何关系
a = [1, 2, [3, 4]]
b = copy.copy(a)
c = copy.deepcopy(a)
print(a is b) # False
print(b is c) # False

# 对第一层的元素进行修改,毫无影响
a[0] = 1000
print(b) # [1, 2, [3, 4]]
print(c) # [1, 2, [3, 4]]


# 对第二层的元素进行修改,浅拷贝的元素会受到影响, 即使index可能发生改变
a[2].append(100)
print(b) # [1, 2, [3, 4, 100]]
print(c) # [1, 2, [3, 4]]


del a[1]
print(a)        # [1000, [3, 4, 100]]
a[1].append(200)
print(a)        # [1000, [3, 4, 100, 200]]
print(b)        # [1, 2, [3, 4, 100, 200]]
print(c)        # [1, 2, [3, 4]]
a = [1]
b = a * 3       # 这其实也是一种浅复制
print(b)        # b: [1, 1, 1]
b.append(2)
print(b)        # b: [1, 1, 1, 2]
print(a)        # a: [1]

a = [[1]]
b = a * 3       # b里面的元素其实是3个引用
print(b)        # b: [[1], [1], [1]]
b[0].append(2)  
print(b)        # b: [[1, 2], [1, 2], [1, 2]]
print(a)        # a: [[1, 2]]

append() vs. extend()

numbers = [1, 2, 3, 4]

numbers.append([5,6,7,8])
print(numbers)                  # [1, 2, 3, 4, [5, 6, 7, 8]]
print(len(numbers))             # 5

numbers.extend([9, 10, 11, 12])
print(numbers)                  # [1, 2, 3, 4, [5, 6, 7, 8], 9, 10, 11, 12]
print(len(numbers))             # 9

__add__ vs. __iadd__

a = [1, 2, 3]
b = (4,)
a += b    # 相对于__add__, __iadd__对第二个操作数更宽容
print(a)  # [1, 2, 3, 4]
c = (5,)
a = a + c # TypeError: can only concatenate list (not "tuple") to list

数组(array)

如果要存放1000万个浮点数的话,arraylist效率要高得多,因为数组在贝后存的并不是float对象,而是数字的机器翻译,也就是字节表述。

Init (需要类型码)

Type code      C Type     Minimum size in bytes
     'b'    signed integer           1
     'B'    unsigned integer         1
     'u'    Unicode character        2
     'h'    signed integer           2
     'H'    unsigned integer         2
     'i'    signed integer           2
     'I'    unsigned integer         2
     'l'    signed integer           4
     'L'    unsigned integer         4
     'q'    signed integer           8
     'Q'    unsigned integer         8
     'f'    floating point           4
     'd'    floating point           8
from array import array
from random import random

floats = array('d', (random() for i in range(10**7)))
print(floats[-1]) # 0.18344280742187247

文件读取和存储

from array import array
from random import random

floats = array('d', (random() for i in range(10**7)))

fp = open('floats.bin', 'wb')
floats.tofile(fp)
fp.close()

floats2 = array('d')
fp = open('floats.bin', 'rb')
floats2.fromfile(fp, 10**7)
fp.close()

print(floats == floats) # True

排序

从Python 3.4开始,我们要使用a = array.array(a.typecode, sorted(a))

双向队列 (deque)

如果需要频繁对序列做先进先出的操作,deque的速度应该会更快。

from collections import deque

dq = deque(range(10), maxlen=10) # maxlen一旦设定,就不能修改了
print(dq)                        # deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)

dq.rotate(3)                     # 向右转3
print(dq)                        # deque([7, 8, 9, 0, 1, 2, 3, 4, 5, 6], maxlen=10)

dq.rotate(-4)
print(dq)                        # deque([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], maxlen=10)

dq.appendleft(-1)
print(dq)                        # deque([-1, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)

dq.extend([11, 22, 33])          # deque([3, 4, 5, 6, 7, 8, 9, 11, 22, 33], maxlen=10)
print(dq)

dq.extendleft([10, 20, 30, 40])  # 一个个添加到左边
print(dq)                        # deque([40, 30, 20, 10, 3, 4, 5, 6, 7, 8], maxlen=10)

print(dq.pop())                  # 8
print(dq.popleft())              # 40

切片 (slice)

读取 (seq[start:stop:step])

s = 'bicycle'
print(s[::3])  # bye
print(s[::-2]) # eccb
print(s[::-1]) # elcycib

赋值

l = list(range(10))
print(l)             # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

l[2:5] = [20, 30]    # 原数组的三个元素替换成了两个元素
print(l)             # [0, 1, 20, 30, 5, 6, 7, 8, 9]

del l[5:7]
print(l)             # [0, 1, 20, 30, 5, 8, 9]

l[3::2] = [11, 22]
print(l)             # [0, 1, 20, 11, 5, 22, 9]

l[2:5] = [100]       # True
print(l)             # [0, 1, 100, 22, 9]

l[2:5] = 100         # Error: 赋值语句右侧必须是个可迭代对象

元组 (tuple, 可以用index访问)

里面元素不可变,它本身可以变化。

Init

dimensions = (200, 50)
print(dimensions[0])
dimensions = (400, 100) # correct

a = (1)   # 这只是个int,不是tuple
b = (1, ) # 单个元素的时候,我们要加上个逗号,这样才能表示tuple

访问

a = (1, 2, 3)
print(a[1:-1]) # (2, ) print elements from index 1 to the last (except the last)
print(a[1:-2]) # ()    print elements from index 1 to the second last(except the second last)

b = a * 3      # 虽然tuple不能修改,但我们这里是创建了个新的tuple
print(b)       # (1, 2, 3, 1, 2, 3, 1, 2, 3)

c = list(a)
print(c)       # [1, 2, 3]

拆包

coordinates = (30, 40)

x, y = coordinates
print(x, y)                     # 30 40

x, y = y, x                     # 交换两个变量的值
print(x, y)                     # 40 30


 # 使用*号可以把一个可迭代对象拆开作为函数的参数
print(divmod(20, 8))            # (2, 4)
t = (20, 8)
print(divmod(*t))               # (2, 4)

quotient, remainder = divmod(*t)
print(quotient, remainder)      # 2 4


# 使用*args获取不确定数量的参数
a, b, *rest = range(5)
print(a, b, rest)               # 0 1 [2, 3, 4]

a, *rest, b = range(5)
print(a, rest, b)               # 0 [1, 2, 3] 4

函数声明

在Python 3之前,元组可以作为形参放在函数声明中,例如def fn(a, (b, c), d):。然而Python 3不再支持这种格式。

具名元组 (namedtuple)

from collections import namedtuple

City = namedtuple('City', 'name country population coordinates')
tokyo = City('Tokyo', 'JP', 36.933, (35.68, 139.69))
print(tokyo) # City(name='Tokyo', country='JP', population=36.933, coordinates=(35.68, 139.69))

print(tokyo.population)  # 36.933
print(tokyo.coordinates) # (35.68, 139.69)

print(City._fields)      # ('name', 'country', 'population', 'coordinates')

LatLong = namedtuple('LatLong', 'lat long')
data = ('Delhi', 'IN', 21.935, LatLong(28.61, 77.21))
delhi = City._make(data) # 等价于 delhi = City.(*data)
print(delhi)             # City(name='Delhi', country='IN', population=21.935, coordinates=LatLong(lat=28.61, long=77.21))

print(delhi._asdict())          # 把具名元组以collections.OrderedDict的形式返回
# OrderedDict([('name', 'Delhi'), ('country', 'IN'), ('population', 21.935), ('coordinates', LatLong(lat=28.61, long=77.21))])

字典 (dict, 无序)

字典的键(可散列)

  1. 只有 可散列的 的数据类型才能用作映射的键。(值无所谓)

  2. 原子不可变数据类型(strbytes和数值类型)都是可散列的。

  3. fronzenset也是可散列的,因为它只能容纳可散列类型。

  4. 元组的话,只有当一个元组所包含的元素都是可散列类型的情况下,它才是可散列的。

  5. dict里添加元素,可能会改变已有元素的次序。

tt = (1, 2, (30, 40))
print(hash(tt))       # 8027212646858338501

tf = (1, 2, frozenset([30, 40]))
print(hash(tt))       # 8027212646858338501,和上面一样

tl = (1, 2, [30, 40])
print(hash(tl))       # TypeError: unhashable type: 'list'

Init

a = dict(one=1, two=2, three=3)
b = {'one': 1, 'two': 2, 'three': 3}
c = dict(zip(['one', 'two', 'three'], [1, 2, 3]))
d = dict([('two', 2), ('one', 1), ('three', 3)])
e = dict({'three': 3, 'one': 1, 'two': 2})
print(a == b == c == d == e) # True

Delete

alien_0 = {'color': 'green', 'points': '5'}
print(alien_0)              # {'color': 'green', 'points': '5'}

del alien_0['points']
print(alien_0)              # {'color': 'green'}

print(alien_0.pop('color')) # green
print(alien_0)              # {}

Traverse

user_0 = {'username': 'Tong', 'first': 'April', 'last': 'Wang'}
print(user_0) # {'username': 'Tong', 'last': 'Wang', 'first': 'April'}

for key, value in user_0.items():
    print("\nKey: " + key)
    print("Value: " + value)

# 顺序可能会发生改变
#    Key: username
#    Value: Tong

#    Key: last
#    Value: Wang

#    Key: first
#    Value: April

# for key in user_0.keys(): 等价于 for key in user_0:
# user_0.keys() 返回一个列表
for key in user_0.keys():
    print("\nKey: " + key)

处理找不到的键

  • 使用get(key, default)或者setdefault(key, default)
a = {'one': 1, 'two': 2, 'three': 3}
# print(a['four'])              # KeyError: 'four'

print(a.get('four', -1))        # -1
print(a)                        # {'one': 1, 'two': 2, 'three': 3}

print(a.get('four'))            # None
print(a)                        # {'one': 1, 'two': 2, 'three': 3}

print(a.setdefault('four', 4))  # 若存在键'four', 则返回a['four'],
                                # 要不然,设置a['four'] = 4, 再返回4
                                # 这有利于通过查找插入值的操作
print(a)                        # {'one': 1, 'two': 2, 'three': 3, 'four': 4}
  • defaultdict
import collections

a = collections.defaultdict(list) # 如果找不到某个key, 会创建一个list,然后返回这个list的引用

a['names'].append('Tong')
print(a)                    # defaultdict(<class 'list'>, {'names': ['Tong']})

a['names'].append('Yihui')
print(a)                    # defaultdict(<class 'list'>, {'names': ['Tong', 'Yihui']})

print(a.get('ages'))        # None, 因为get()不会触发创建新的list
print(a)                    # defaultdict(<class 'list'>, {'names': ['Tong', 'Yihui']})

print(a['ages'])            # [], 因为__getitem__会触发创建新的list
print(a)                    # defaultdict(<class 'list'>, {'names': ['Tong', 'Yihui'], 'ages': []})
  • __missing__
class StrKeyDict(dict):

    def __missing__(self, key):
        if isinstance(key, str):
            raise KeyError(key)
        return self[str(key)]

    def get(self, key, default=None):
        try:
            return self[key]
        except KeyError:
            return default

    def __contains__(self, key):
        return key in self.keys() or str(key) in self.keys()

d = StrKeyDict([('1', 'one'), ('2', 'two')])
print(d['1'])           # one
print(d[1])             # one
print(d.get(1))         # one
print(1 in d)           # True
print(d.keys())         # dict_keys(['1', '2'])
print(1 in d.keys())    # False, keys()返回的是列表

字典的变种

  • collections.OrderedDict (记录键-值对的加入顺序)
from collections import OrderedDict

favorite_languages = OrderedDict()

favorite_languages['april'] = 'python'
favorite_languages['tong'] = 'c++'
favorite_languages['edison'] = 'ruby'

#April's favorite language is Python.
#Tong's favorite language is C++.
#Edison's favorite language is Ruby.
for name, language in favorite_languages.items():
    print(name.title() + "'s favorite language is " + language.title() + ".")

# ('edison', 'ruby'), 删除了最后一个元素
print(favorite_languages.popitem())

# ('april', 'python'), 删除了第一个元素
print(favorite_languages.popitem(last=False))

  • collections.ChainMap

ChainMap可以把一组dict串起来并组成一个逻辑上的dictChainMap本身也是一个dict,但是查找的时候,会按照顺序在内部的dict依次查找。

什么时候使用ChainMap最合适?举个例子:应用程序往往都需要传入参数,参数可以通过命令行传入,可以通过环境变量传入,还可以有默认参数。我们可以用ChainMap实现参数的优先级查找,即先查命令行参数,如果没有传入,再查环境变量,如果没有,就使用默认参数。

from collections import ChainMap
import os, argparse

# 构造缺省参数:
defaults = {
    'color': 'red',
    'user': 'guest'
}

# 构造命令行参数:
parser = argparse.ArgumentParser()
parser.add_argument('-u', '--user')
parser.add_argument('-c', '--color')
namespace = parser.parse_args()
command_line_args = { k: v for k, v in vars(namespace).items() if v }

# 组合成ChainMap:
combined = ChainMap(command_line_args, os.environ, defaults)

# 打印参数:
print('color=%s' % combined['color'])
print('user=%s' % combined['user'])

没有任何参数时,打印出默认参数

$ python3 use_chainmap.py
color=red
user=guest

传入命令行参数时,优先使用命令行参数

$ python3 use_chainmap.py -u bob
color=red
user=bob

同时传入命令行参数和环境变量,命令行参数优先级较高

$ user=admin color=green python3 use_chainmap.py -u bob
color=green
user=bobbash
  • collections.Counter
import collections

ct = collections.Counter('abracadabra')
print(ct)                # Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})

ct.update('aaaaazzz')    # Counter({'a': 10, 'z': 3, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
print(ct)

print(ct.most_common(2)) # [('a', 10), ('z', 3)]


# 奇特的'+'号
ct['a'] = 0
ct['b'] = -3
print(ct)                # Counter({'z': 3, 'r': 2, 'c': 1, 'd': 1, 'a': 0, 'b': -3})
print(+ct)               # Counter({'z': 3, 'r': 2, 'c': 1, 'd': 1}), 0或负数会被忽略
  • collections.UserDict
import collections

class StrKeyDict(collections.UserDict):

    def __missing__(self, key):
        if isinstance(key, str):
            raise KeyError(key)
        return self[str(key)]

    def __contains__(self, key):
        return str(key) in self.data

    def __setitem(self, key, item):
        self.data[str(key)] = item

d = StrKeyDict([('1', 'one'), ('2', 'two')])
print(d['1'])           # one
print(d[1])             # one
print(d.get(1))         # one
print(1 in d)           # True
print(d.keys())         # KeysView({'1': 'one', '2': 'two'})
print(1 in d.keys())    # True, keys()返回的是KeysView, 这和之前不同

不可变的映射

标准库里所有的映射类型都是可变的,但是我们可以用types模块里的一个封装类MappingProxyType。如果给这个类一个dict,它会返回一个只读的映射视图。但是它是动态的,这意味着如果对原映射做出了改动,我们可以通过这个视图观察到,但是无法通过这个视图对原映射做出修改。

from types import MappingProxyType

d = {1: 'A'}
d_proxy = MappingProxyType(d)
print(d_proxy)    # {1: 'A'}
print(d_proxy[1]) # A

d[2] = 'B'
print(d_proxy)    # {1: 'A', 2: 'B'}

集合(set)

  1. 不重复的元素

  2. 无序

  3. 不能用index

  4. 不可散列(不可以用来当dictkey

  5. set里添加元素,可能会改变已有元素的次序。

Init

basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}
print(basket) # {'apple', 'pear', 'banana', 'orange'}

a = set('abracadabra')
print(a) # {'c', 'a', 'd', 'r', 'b'}

nums = [1, 2, 3, 3, 4]
print(set(nums)) # {1, 2, 3, 4}


# 注意!!!
# 要想创建空集合,使用set()
# 如果使用了{},那么我们其实得到了个空dict
a = set()
print(type(a)) # <class 'set'>

b = {}
print(type(b)) # <class 'dict'>


a = {1}
a.pop()
print(a)       # set()
a.add(2)
print(a)       # {2}

交集,并集,差集, 对称差集

a = {1, 2, 3}
b = {2, 3, 4}

# 交集 {2, 3}
print(a & b)
print(a.intersection(b))

# 并集 {1, 2, 3, 4}
print(a | b)
print(a.union(b))

# 差集 {1}
print(a - b)
print(a.difference(b))

# XOR 对称差集 {1, 4}
print(a ^ b)
print(a.symmetric_difference(b))

#print(a - [2])          # TypeError: unsupported operand type(s) for -: 'set' and 'list'
print(a.difference([2])) # {1, 3}

变种-frozenset

  1. frozenset() 返回一个冻结的集合,冻结后集合不能再添加或删除任何元素。

  2. set相比的好处是,它可以作为dictkey

a = frozenset(range(10))     # 生成一个新的不可变集合
print(a)                     # frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9})

b = frozenset('helloworld')
print(b)                     # frozenset({'o', 'l', 'r', 'd', 'e', 'h', 'w'})

函数

返回值

  1. return没有返回值时,函数自动返回None,Python没有NULL

  2. return可以返回多个值

  3. 执行到return时,程序将停止函数内return后面的语句

  4. 函数中return不是必须的

实参和形参

如果要想给形参指定 默认值,那么在形参列表中必须先列出没有默认值的形参,再列出有默认值的形参。

def describe_pet(pet_name, animal_type='dog'):
    print("\nI have a " + animal_type + ".")
    print("My " + animal_type + "'s name is " + pet_name.title() + ".")

#I have a dog.
#My dog's name is Willie.
describe_pet(pet_name='willie')

#I have a dog.
#My dog's name is Willie.
describe_pet('willie')

#I have a hamseter.
#My hamseter's name is Willie.
describe_pet('willie', 'hamseter')

禁止函数修改列表

# 传递副本而非列表本身
function_name(list_name[:])

传递任意数量的实参

用带一个星号的形参,是指用一个元组来存储传递过来的实参。

def make_pizza(*toppings):
    print(toppings)

make_pizza('pepperoni') # ('pepperoni',)

make_pizza('mushrooms', 'green peppers', 'extra cheese') # ('mushrooms', 'green peppers', 'extra cheese')

用带两个星号的实参,是指来接受任意数量的关键子实参(键-值对)。

def build_profile(first, last, **user_info):
    profile = {}
    profile['first_name'] = first
    profile['last_name'] = last
    for key, value in user_info.items():
        profile[key] = value
    return profile

user_profile = build_profile('albert', 'einstein', location='princeton', field='physics')

# {'last_name': 'einstein', 'first_name': 'albert', 'location': 'princeton', 'field': 'physics'}
print(user_profile)

作为元素添加到集合

def foo():
    print('from foo')
dic={'func':foo}

foo()              # 'from foo'
print(dic['func']) # <function foo at 0x000001905BD5C1E0>
dic['func']()      # 'from foo'

作为参数值传递给其它函数

def foo():
    print('from foo')

def bar(func):
    print(func)
    func()

bar(foo)

# <function foo at 0x000001D9556EC1E0>
# from foo

函数嵌套

def bar(multiple):
    def foo(n):
        return multiple ** n
    return foo  # 我们可以把foo想象成一个一个元素,其中multiple为输入的multiple

print(bar(2)(3)) # 2**3 = 8

print()

print('\n')  # new line
print('\\n') # \n
print(r'\n') # \n
             # 'r'代表原始字符串标识符,该字符串中的特殊符号不会被转义

xrange() (只存在Python2里面,Python3里面没了)

  1. xrange() 函数用法与 range() 完全相同。只是xrange()生成的是生成器而不是数组。

  2. 要生成很大的数字序列的时候,用xrange会比range性能优很多,因为不需要一上来就开辟一块很大的内存空间。

map()

# map(function_to_apply, list_of_inputs)
# lambda argument: manipulate(argument)

a = map(lambda x : x**3, [1, 2, 3]) # map object
print(list(a))                      # [1, 8, 27]

filter()

def is_odd(n):
    return n % 2 == 1

a = list(filter(is_odd, [1, 2, 3, 4]))
print(a) # [1, 3]

reduce()

from functools import reduce
from operator import mul

def fact(n):
    return reduce(mul, range(1, n+1))

print(fact(5))

zip()

# [(0, 'A'), (1, 'B'), (2, 'C')]
print(list(zip(range(3), 'ABC')))

# [(0, 'A', 0.0), (1, 'B', 1.1), (2, 'C', 2.2)]
print(list(zip(range(3), 'ABC', [0.0, 1.1, 2.2, 3.3])))

# [(0, 'A', 0.0), (1, 'B', 1.1), (2, 'C', 2.2), (-1, -1, 3.3)]
from itertools import zip_longest
print(list(zip_longest(range(3), 'ABC', [0.0, 1.1, 2.2, 3.3], fillvalue=-1)))

x = '1234'
y = '34215'
print([i == j for i, j in zip(x, y)]) # [False, False, False, False]

装饰器 (decorator)

  1. 能把被装饰的函数替换成其他函数

  2. 装饰器在加载模块时立即执行,而被装饰的函数只在明确调用时运行。

def a_new_decorator(a_func):
    def wrapTheFunction():
        print("I am doing some boring work before a_func()")
        a_func()
        print("I am doing some boring work after a_func()")
    return wrapTheFunction

def a_function_requiring_decoration():
    print("I am the function which needs decoration")

a_function_requiring_decoration()
# I am the function which needs decoration

a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)

a_function_requiring_decoration()
# I am doing some boring work before a_func()
# I am the function which needs decoration
# I am doing some boring work after a_func()

上面的代码等价于下面

def a_new_decorator(a_func):
    def wrapTheFunction():
        print("I am doing some boring work before a_func()")
        a_func()
        print("I am doing some boring work after a_func()")
    return wrapTheFunction

@a_new_decorator
def a_function_requiring_decoration():
    print("I am the function which needs decoration")

a_function_requiring_decoration()
# I am doing some boring work before a_func()
# I am the function which needs decoration
# I am doing some boring work after a_func()

print(a_function_requiring_decoration.__name__) # wrapTheFunction

为了保持函数名称不变,我们可以使用@wraps()

from functools import wraps

def a_new_decorator(a_func):
    @wraps(a_func)
    def wrapTheFunction():
        print("I am doing some boring work before a_func()")
        a_func()
        print("I am doing some boring work after a_func()")
    return wrapTheFunction

@a_new_decorator
def a_function_requiring_decoration():
    print("I am the function which needs decoration")

print(a_function_requiring_decoration.__name__) # a_function_requiring_decoration

例子

def dec(f):
    n = 3
    def wrapper(*args,**kw):
        return f(*args,**kw) * n
    return wrapper

@dec
def foo(n):
    return n * 2

print(foo(2)) # 12 = (2 * 2) * 3
print(foo(3)) # 18 = (3 * 2) * 3

闭包 (closure)

闭包是指延伸了作用域的函数,其中包含函数定义体中引用,但是不在定义体中定义的非全局变量。

函数是不是匿名的没有关系,关键是它能访问定义体之外的非全局变量。

假设我们想实现一个函数来计算不断增加的系列值的均值。最开始,我们很可能考虑实现下面这个类

class Averager():

    def __init__(self):
        self.series = []

    def __call__(self, new_value):
        self.series.append(new_value)
        total = sum(self.series)
        return total/len(self.series)

avg = Averager()
print(avg(10)) # 10.0
print(avg(11)) # 10.5
print(avg(12)) # 11.0

除此之外,我们也可以考虑函数式实现,即 闭包,如下所示。

def make_averager():
    series = []

    def averager(new_value):
        series.append(new_value)
        total = sum(series)
        return total/len(series)

    return averager

avg = make_averager()
print(avg(10)) # 10.0
print(avg(11)) # 10.5
print(avg(12)) # 11.0

print(avg.__code__.co_varnames)         # ('new_value', 'total')
print(avg.__code__.co_freevars)         # ('series',)
print(avg.__closure__)                  # (<cell at 0x000001CA393DE588: list object at 0x000001CA38F85C88>,)
print(avg.__closure__[0].cell_contents) # [10, 11, 12]

其中,series自由变量(free variable),指未在本地作用域中绑定的变量。闭包是指

series = []

def averager(new_value):
    series.append(new_value)
    total = sum(series)
    return total/len(series)

综上,闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定。

nonlocal声明

作用是把变量标记为自由变量,即使在函数中为变量赋予新值了,也会变成自由变量。如果为nonlocal声明的变量赋予新值,闭包中保存的绑定会更新。

错误的示例

因为对于数字,字符串,元组等不可变类型来说,只能读取,不能更新。如果尝试重新绑定,例如count = count + 1,其实会隐式创建局部变量count。这样,count就不是自由变量了,因此不会保存在闭包中。

def make_averager():
    count = 0
    total = 0

    def averager(new_value):
        count += 1
        total += new_value
        return total/count

    return averager

    avg = make_averager()
    print(avg(10)) # UnboundLocalError: local variable 'count' referenced before assignment
    print(avg(11))
    print(avg(12))

正确的示例

def make_averager():
    count = 0
    total = 0

    def averager(new_value):
        nonlocal count, total
        count += 1
        total += new_value
        return total/count

    return averager

avg = make_averager()
print(avg(10)) # 10.0
print(avg(11)) # 10.5
print(avg(12)) # 11.0

函数的参数作为引用时

  • 传递的参数可能会被影响
def f(a, b):
    a += b
    return a

x = 1
y = 2
print(f(x, y))  # 3
print(x)        # 1, x未被影响,因为x不可变, x += y 其实等价于 x = x + y, x变成了一个local的变量
print(y)        # 2

a = [1, 2]
b = [3, 4]
print(f(a, b))  # [1, 2, 3, 4]
print(a)        # [1, 2, 3, 4], x被影响,因为x是可变的,在函数里,它是一个引用
print(b)        # [3, 4]
  • 不要使用可变类型作为参数的默认值
class HauntedBus:

    def __init__(self, passengers=[]): # 注意: passengers默认是个空列表
        self.passengers = passengers

    def pick(self, name):
        self.passengers.append(name)

    def drop(self, name):
        self.passengers.remove(name)

bus1 = HauntedBus(['Alice', 'Bill'])
print(bus1.passengers)      # ['Alice', 'Bill']

bus1.pick('Charlie')
print(bus1.passengers)      # ['Alice', 'Bill', 'Charlie']

bus1.drop('Alice')
print(bus1.passengers)      # ['Bill', 'Charlie']


bus2 = HauntedBus()
bus2.pick('Tong')
print(bus2.passengers)      # ['Tong']

bus3 = HauntedBus()
print(bus3.passengers)      # ['Tong'], 列表不会空!!!

bus3.pick('Yihui')
print(bus3.passengers)      # ['Tong', 'Yihui']
print(bus2.passengers)      # ['Tong', 'Yihui'], bus2的乘客也被改动了!!!

# bus2和bus3共用一个列表!!!
print(bus2.passengers is bus3.passengers) # True

print(bus1.passengers)      # ['Bill', 'Charlie']


# 上面的问题是因为self.passengers变成了passengers参数默认值的别名
# 根源是默认值在定义函数时计算(加载模块时),因此默认值变成了函数对象的属性
# 如果默认值时可变对象,而且修改它的值,那么后续的函数调用都会受到影响
print(HauntedBus.__init__.__defaults__) # (['Tong', 'Yihui'],)
  • 防御可变参数
class HauntedBus:

    def __init__(self, passengers=None): # 注意: passengers默认是个空列表
        if passengers is None:
            self.passengers = []
        else:
            self.passengers = list(passengers) # 通过list()创建一个副本!!!

    def pick(self, name):
        self.passengers.append(name)

    def drop(self, name):
        self.passengers.remove(name)

bus1 = HauntedBus(['Alice', 'Bill'])
print(bus1.passengers)      # ['Alice', 'Bill']

bus1.pick('Charlie')
print(bus1.passengers)      # ['Alice', 'Bill', 'Charlie']

bus1.drop('Alice')
print(bus1.passengers)      # ['Bill', 'Charlie']


bus2 = HauntedBus()
bus2.pick('Tong')
print(bus2.passengers)      # ['Tong']

bus3 = HauntedBus()
print(bus3.passengers)      # [], 列表现在为空了

bus3.pick('Yihui')
print(bus3.passengers)      # ['Yihui']
print(bus2.passengers)      # ['Tong'], bus2的乘客不会被改动

print(HauntedBus.__init__.__defaults__) # (None,)

Class

调用类时会运行类的__new__方法创建一个实例,然后运行__init__方法,初始化实例,最后把实例返回给调用方。

Init

class Dog():

    # 创建新实例时,自动运行
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def sit(self):
        print(self.name.title() + " is now sitting.")

    def roll_over(self):
        print(self.name.title() + " rolled over!")

my_dog = Dog('willie', 6)
print(my_dog.age)

继承

class Car():
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0

    def get_descriptive_name(self):
        long_name = str(self.year) + ' ' + self.make + ' ' + self.model
        return long_name.title()

class Battery():
    def __init__(self, battery_size=70):
        self.battery_size = battery_size()

class ElectricCar(Car):
    def __init__(self, make, model, year):
        # super()让ElectricCar实例包含父类的所有属性
        super().__init__(make, model, year)
        self.battery = Battery()

__new__ vs. __init__

  1. __init__方法为初始化方法(什么都不返回), __new__方法才是真正的构造函数。

  2. __new__方法默认返回实例对象供__init__方法、实例方法使用。

  3. __init__方法为初始化方法,为类的实例提供一些属性或完成一些动作 (只有在__new__返回一个cls的实例时,后面的__init__才能被调用)。

  4. __new__方法创建实例对象供__init__方法使用,__init__方法定制实例对象。

  5. __new__是一个静态方法,而__init__是一个实例方法。

  6. 当创建一个新实例时调用__new__,初始化一个实例时用__init__

__call__

只需实现实例方法__call__,任何Python对象都能表现得像函数。

import random

class BingoCage:

    def __init__(self, items):
        self._items = list(items)
        random.shuffle(self._items)

    def pick(self):
        try:
            return self._items.pop()
        except IndexError:
            raise LoopupError('pick from empty BingoCage')

    def __call__(self):
        return self.pick()

bingo = BingoCage(range(3))
print(bingo.pick())     # 2
print(bingo())          # 1
print(callable(bingo))  # True

__slots__

默认情况下,Python在各个实例中名为__dict__的字典里存储实例属性。为了使用底层的散列表提升访问速度,字典会消耗大量内存。如果要处理数百万个属性不多的实例,通过__slots__类属性,能节省大量内存,方法是让解释器在元组中存储实例属性,而不用字典。

  • 每个子类都要定义__slots__属性,因为解释器会忽略继承的__slots__属性

  • 实例只能拥有__slots__中列出的属性,除非把__dict__加入__slots__中(这样就失去了节省内存的功效)

  • 如果不把__weakref__加入__slots__,实例就不能作为弱引用的目标

成员命名方式

  1. object: 公用方法

  2. _object: 半保护, 被看作是protected(但python中其实只分publicprivate),意思是只有类对象和子类对象自己能访问到这些变量,在模块或类外不可以使用,不能用from module import *导入。

  3. _ _object: 全私有,全保护。私有成员private,意思是只有类对象自己能访问,连子类对象也不能访问到这个数据,不能用from module import *导入。_ _object 会导致Python解释器重写属性名称, 对于该标识符描述的方法,父类的方法不能轻易地被子类的方法覆盖,他们的名字实际上是_classname__methodname

  4. _ _object_ _: 内建方法,用户不要这样定义。

class A:
    def __init__(self, a):
        self.__a = a
        self._b = 1

    @property
    def a(self):
        return self.__a

class B(A):
    def __init__(self, a):
        super().__init__(a)
        self.__a = a

a1 = A(1)
b1 = B(2)
print(a1.__dict__) # {'_A__a': 1, '_b': 1}
print(b1.__dict__) # {'_A__a': 2, '_b': 1, '_B__a': 2}

b1._A__a = 3
print(b1.__dict__) # {'_A__a': 3, '_b': 1, '_B__a': 2}

print(b1.a)        # 3
print(b1._B__a)    # 2
print(b1.__a)      # AttributeError: 'B' object has no attribute '__a'
b1.a = 5           # AttributeError: can't set attribute

classmethodstaticmethod

classmethod定义操作类的方法,而不是操作实例的方法。类方法的第一个参数是类本身cls

staticmethod就是普通的函数,只是碰巧在类的定义体中。

# https://blog.csdn.net/youngbit007/article/details/68957848
class A:
    @classmethod
    def cm(cls):
        print('类方法cm(cls)调用者:', cls.__name__)

    @staticmethod
    def sm():
        print('静态方法sm()被调用')

class B(A):
    pass

A.cm()  # 类方法cm(cls)调用者: A
B.cm()  # 类方法cm(cls)调用者: B
A.sm()  # 静态方法sm()被调用
B.sm()  # 静态方法sm()被调用
class Foo:
    def call():
        print("normal")

    @staticmethod
    def call():
        print("static")


f = Foo()
f.call()    # static
Foo.call()  # static

实现Vector2d

from array import array
import math


class Vector2d:
    typecode = 'd'

    def __init__(self, x, y):
        self.__x = float(x) # 私有变量,从而属性不可变,为了让这个类可散列
        self.__y = float(y)

    @property               # 相当实现了一个getter,把读值方法标记为特性
    def x(self):
        return self.__x

    @property
    def y(self):
        return self.__y

    def __iter__(self):
        return (i for i in (self.x, self.y))

    def __repr__(self):
        class_name = type(self).__name__
        return '{}({!r}, {!r})'.format(class_name, *self) # {!r}获取各个分量的表示形式

    def __str__(self):
        return str(tuple(self))

    def __bytes__(self):
        return (bytes([ord(self.typecode)]) +       # ord()以一个字符(长度为1的字符串)作为参数,返回对应的 ASCII 数值
                bytes(array(self.typecode, self)))

    def __eq__(self, other):
        return tuple(self) == tuple(other)

    def __hash__(self):
        return hash(self.x) ^ hash(self.y)

    def __abs__(self):
        return math.hypot(self.x, self.y)

    def __bool__(self):
        return bool(abs(self))

    def angle(self):
        return math.atan2(self.y, self.x)

    def __format__(self, fmt_spec=''):
        if fmt_spec.endswith('p'):
            fmt_spec = fmt_spec[:-1]
            coords = (abs(self), self.angle())
            outer_fmt = '<{}, {}>'
        else:
            coords = self
            outer_fmt = '({}, {})'
        components = (format(c, fmt_spec) for c in coords)
        return outer_fmt.format(*components)

    @classmethod
    def frombytes(cls, octets):
        typecode = chr(octets[0])
        memv = memoryView(octets[1:]).cast(typecode)
        return cls(*memv)

实现Vector

from array import array
import reprlib
import math
import numbers
import functools
import operator
import itertools


class Vector:
    typecode = 'd'

    def __init__(self, components):
        self._components  = array(self.typecode, components)

    def __iter__(self):
        return iter(self._components)

    def __repr__(self):
        components = reprlib.repr(self._components)
        components = components[components.find('['):-1]
        return 'Vector({})'.format(components)

    def __str__(self):
        return str(tuple(self))

    def __bytes__(self):
        return (bytes([ord(self.typecode)]) +       # ord()以一个字符(长度为1的字符串)作为参数,返回对应的 ASCII 数值
                bytes(self._components))

    def __eq__(self, other):
        return (len(self) == len(other) and
                all(a == b for a, b in zip(self, other)))

    def __hash__(self):
        aashes = (hash(x) for x in self)
        return functools.reduce(operator.xor, hashes, 0)

    def __abs__(self):
        return math.sqrt(sum(x * x for x in self))

    def __bool__(self):
        return bool(abs(self))

    def __getitem__(self, index):
        cls = type(self)
        if isinstance(index, slice):
            return cls(self._components[index])
        elif isinstance(index, numbers.Integral):
            return self._components[index]
        else:
            msg = '{.__name__} indices must be integers'
            raise TypeError(msg.format(cls))

    shortcur_names = 'xyzt'

    def __getattr__(self, name):
        cls = type(self)
        if len(name) == 1:
            pos = cls.shortcut_names.find(name)
            if 0 <= pos < len(self._components):
                return self._components[pos]
        msg = '{.__name__!r} object has no attribute {!r}'
        raise AttributeError(msg.format(cls, name))

    def angle(self, n):
        r = math.sqrt(sum(x * x for x in self[n:]))
        a = math.atan2(r, self[n-1])
        if (n == len(self) - 1) and (self[-1] < 0):
            return math.pi * 2 - a
        else:
            return a

    def angles(self):
        return (self.angle(n) for n in range(1, len(self)))

    def __format__(self, fmt_spec=''):
        if fmt_spec.endswith('h'):
            fmt_spec = fmt_spec[:-1]
            coords = itertools.chain([abs(self)],
                                     self.angles())
            outer_fmt = '<{}>'
        else:
            coords = self
            outer_fmt = '({})'
        components = (format(c, fmt_spec) for c in coords)
        return outer_fmt.format(', '.join(components))

    @classmethod
    def frombytes(cls, octets):
        typecode = chr(octets[0])
        memv = memoryView(octets[1:]).cast(typecode)
        return cls(memv)

多重继承

  • 方法解析顺序 (Method Resolution Order, MRO): Pythhon会按照特定的顺序遍历继承图。

下面例子中,B, C都含有pong(),而D都继承了。如果我们运行d.pong(),Python会根据D(B, C)中的顺序,先从B中开始找。

class A:
    def ping(self):
        print('A ping: ', self)

class B(A):
    def pong(self):
        print('B pong: ', self)

class C(A):
    def pong(self):
        print('C pong: ', self)

class D(B, C):
    def ping(self):
        super().ping()
        print('D ping: ', self)

    def pingpong(self):
        self.ping()
        super().ping()
        self.pong()
        super().pong()
        C.pong(self)

d = D()
d.ping()        # A ping:  <__main__.D object at 0x000001E95A33C470>
                # D ping:  <__main__.D object at 0x000001E95A33C470>

d.pingpong()    # A ping:  <__main__.D object at 0x0000028DB3C2C470>
                # D ping:  <__main__.D object at 0x0000028DB3C2C470>
                # A ping:  <__main__.D object at 0x0000028DB3C2C470>
                # B pong:  <__main__.D object at 0x0000028DB3C2C470>
                # B pong:  <__main__.D object at 0x0000028DB3C2C470>
                # C pong:  <__main__.D object at 0x0000028DB3C2C470>

print(D.__mro__)# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

重载运算符

  • 不能重载内置类型的运算符

  • 不能新建运算符,只能重载现有的

  • 某些运算符不能重载 - is, and, or, not (但是,&, |以及~可以)

考题

class A:
    i = '0'

class B(A):
    pass

class C(A):
    pass

print(A.__dict__) # {'__module__': '__main__', 'i': '0', '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
print(B.__dict__) # {'__module__': '__main__', '__doc__': None}
print(C.__dict__) # {'__module__': '__main__', '__doc__': None}

B.i = '1'
A.i = '2'

print(A.__dict__) # {'__module__': '__main__', 'i': '2', '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
print(B.__dict__) # {'__module__': '__main__', '__doc__': None, 'i': '1'}
print(C.__dict__) # {'__module__': '__main__', '__doc__': None}

# C不含i,所以会从父类中寻找
print(A.i, B.i, C.i) # 2 1 2
class Person:
    def __init__(self, new_name):
        self.name = new_name
        print("%s is coming" % self.name)

    def __del__(self):
        print("%s is gone" % self.name)

tom = Person("Tom") # Tom is coming
del tom             # Tom is gone

Exception

ZeroDivisionError

first_number = input("First number\n")
second_number = input("\nSecond number\n")
try:
    answer = int(first_number) / int(second_number)
except ZeroDivisionError:
    print("You can't divide by 0!")
else:
    print(answer)

FileNotFoundError

filename = "alice.txt"

try:
    with open(filename) as f_obj:
        contents = f_obj.read()
except FileNotFoundError:
    msg = "Sorry, the file " + filename + " does not exist."
    print(msg)

运行过程

  • 程序异常执行except
  • 程序正常执行try和else
  • 无论程序正常执行还是出现异常都执行final

Test

Unit test

def get_formatted_name(first, last):
    full_name = first + ' ' + last
    return full_name.title()

import unittest

class NamesTestCase(unittest.TestCase):
    def setUp(self):
        # 创建一些对象用在各个test_里面
        # 这个方法在test_各种方法之前运行

    # all fucntions beginning with "test_" will be tested
    def test_first_last_name(self):
        formatted_name = get_formatted_name('janis', 'joplin')
        self.assertEqual(formatted_name, 'Janis Joplin')

unittest.main()

生成器 (Generator)

简单

def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

__iter__实现类生成器

# 等差数列生成器
class ArithmeticProgression:

    def __init__(self, begin, step, end=None):
        self.begin = begin
        self.step = step
        self.end = end # None -> 无穷数列

    def __iter__(self):
        result = type(self.begin + self.step)(self.begin)
        forever = self.end is None
        index = 0
        while forever or result < self. end:
            yield result
            index += 1
            result = self.begin + self.step * index

ap = ArithmeticProgression(0, 1, 3)
print(list(ap)) # [0, 1, 2]

执行顺序

def gen_AB():
    print('start')
    yield 'A'
    print('continue')
    yield 'B'
    print('end.')

for c in gen_AB():
    print('-->', c)
# start
# --> A
# continue
# --> B
# end.


g = gen_AB()    # 什么都没打印
next(g)         # start
next(g)         # continue
next(g)         # end. Traceback: StopIteration. 因为我们只有两个yield
def gen_AB():
    print('start')
    yield 'A'
    print('continue')
    yield 'B'
    print('end.')

res1 = tuple(x*3 for x in gen_AB())
# start
# continue
# end.

for i in res1:
    print('-->', i)
# --> AAA
# --> BBB

res2 = (x*3 for x in gen_AB()) # 这是生成器对象
for i in res2:
    print('-->', i)
# start
# --> AAA
# continue
# --> BBB
# end.

itertools

import itertools
gen = itertools.count(1, .5) # count(begin, step[, end])
print(next(gen))             # 1
print(next(gen))             # 1.5
print(next(gen))             # 2.0

# itertools.takewhile会生成一个使用另一个生成器的生成器, 在指定的条件计算结果为False停止
gen = itertools.takewhile(lambda n: n < 3, itertools.count(1, .5))
print(list(gen))             # [1, 1.5, 2.0, 2.5]

yield from

如果生成器函数需要产出另一个生成器生成的值,传统解决办法

def chain(*iterables):
    for it in iterables:
        for i in it:
            yield i

s = 'ABC'
t = tuple(range(3))

print(list(chain(s, t))) # ['A', 'B', 'C', 0, 1, 2]

现在可以使用yield from

def chain(*iterables):
    for it in iterables:
        yield from it

s = 'ABC'
t = tuple(range(3))

print(list(chain(s, t))) # ['A', 'B', 'C', 0, 1, 2]

iter()

Python在迭代对象x时会调用iter(x)

除此之外,iter(iterable, flag)可以传入两个参数,第一个必须是可调用的对象,用于不断调用,产出各个值;第二个时哨符,用作标记值,当可调用的对象返回这个值时,触发迭代器抛出StopIteration异常,而不产出哨符。

from random import randint
def d6():
    return randint(1, 6)
d6_iter = iter(d6, 1) # 掷骰子,一直到掷出1为止

for roll in d6_iter:
    print(roll)

生成器 vs. 迭代器

  • 接口:所有生成器都是迭代器

  • 实现方式:迭代器不都是生成器

  • 概念:迭代器用于遍历集合,从中产出元素;生成器可能无需遍历集合就能生成值。

#[(2, 'a'), (3, 'l'), (4, 'b'), (5, 'a'), (6, 't'), (7, 'r'), (8, 'o'), (9, 'z')]
print(list(enumerate('albatroz', 2)))

from collections import abc
e = enumerate('ABC')
print(isinstance(e, abc.Iterator))          # True

import types
print(isinstance(e, types.GeneratorType))   # False

with

with语句开始运行时,会在上下文管理器对象上调用__enter__方法。with语句运行结束后,会在上下文管理器对象上调用__exit__方法,以此扮演finally子句的角色。

class LookingGlass:

    def __enter__(self):
        import sys
        self.original_write = sys.stdout.write
        sys.stdout.write = self.reverse_write
        return 'TONG'

    def reverse_write(self, text):
        self.original_write(text[::-1])


    def __exit__(self, exc_type, exc_value, traceback): # 如果正常,三个参数是None, None, None
         import sys
         sys.stdout.write = self.original_write
         if exc_type is ZeroDivisionError:
             print('Please DO NOT divide by zero!')
             return True

mirror = LookingGlass()
name = mirror.__enter__()
print(name)             # GNOT
print(name == 'TONG')   # eurT, sys.stdout.write已经变了
mirror.__exit__(None, None, None)
print(name)             # TONG
print(name == 'TONG')   # True

with mirror as what:
    print('YIHUI loves Tong')  # gnoT sevol IUHIY
    print(what)                # GNOT

print(what) # TONG

@contextmanager

在使用@contextmanager装饰的生成器中,yield语句的作用是把函数的定义体分成两部分:yield语句前面的所有代码在with块开始时(即解释器调用__enter__方法时)执行,yield语句后面的代码在with块结束时(即调用__exit__方法时)执行。

import contextlib

@contextlib.contextmanager
def looking_glass():
    import sys
    original_write = sys.stdout.write

    def reverse_write(text):
        original_write(text[::-1])

    sys.stdout.write = reverse_write
    msg = ''
    try:
        yield 'TONG'
    except ZeroDivisionError:
        msg = 'Please DO NOT divide by Zero!'
    finally:
        sys.stdout.write = original_write
        if msg:
            print(msg)

with looking_glass() as what:
    print('YIHUI loves Tong')  # gnoT sevol IUHIY
    print(what)                # GNOT

print(what) # TONG

协程 (coroutine)

  1. 一个线程可以运行多个协程

  2. 协程的调度由所在程序自身控制

  3. Linux中线程的调度由操作系统控制

  4. Python对协程的支持是通过generator实现的。

def grep(pattern):
    print("Searching for", pattern)
    while True:
        line = (yield)
        if pattern in line:
            print(line)

search = grep('coroutine')
next(search)                                # Output: Searching for coroutine

search.send("I love you")
search.send("Don't you love me?")
search.send("I love coroutines instead!")   # Output: I love coroutines instead!

search.close()

简单的例子

def simple_coroutine():
    print('-> coroutine started')
    x = yield # yield在表达式中使用;如果协程只需从客户那里接收数据,那么产出的值时None - 这个值是隐式指定的,因为yield关键字右边没有表达式
    print('-> coroutine received:', x)

my_coro = simple_coroutine()
print(my_coro)  # <generator object simple_coroutine at 0x000001FC40E86390>

next(my_coro)   # 首先要调用next()函数,因为生成器还没启动,没在yield语句处暂停,所以一开始无法发送数据。这一步通常称作“预激”(prime)协程

my_coro.send(42)
# -> coroutine received: 42
# StopIteration
def simple_coro2(a):
    print('-> Started: a =', a)
    b = yield a # 这里会先产出a, 然后再接收一个值赋给b
    print('-> Received: b =', b)
    c = yield a + b
    print('-> Received: c', c)

my_coro2 = simple_coro2(14)
from inspect import getgeneratorstate
print(getgeneratorstate(my_coro2))  # GEN_CREATED

print(next(my_coro2))               # -> Started: a = 14
                                    # 14
print(getgeneratorstate(my_coro2))  # GEN_SUSPENDED

print(my_coro2.send(28))            # -> Received: b = 28
                                    # 42
print(my_coro2.send(99))            # -> Received: c = 99
                                    # StopIteration
def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield average   # 注意!这里不需要用nonlocal声明
        total += term
        count += 1
        average = total/count

coro_avg = averager()
next(coro_avg)

print(coro_avg.send(10)) # 10.0
print(coro_avg.send(30)) # 20.0
print(coro_avg.send(5))  # 15.0

捕获StopIteration

from collections import namedtuple

Result = namedtuple('Result', 'count average')

def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield average   # 注意!这里不需要用nonlocal声明
        if term is None:
            break
        total += term
        count += 1
        average = total/count
    return Result(count, average)

coro_avg = averager()
next(coro_avg)

print(coro_avg.send(10)) # 10.0
print(coro_avg.send(30)) # 20.0
print(coro_avg.send(5))  # 15.0

try:
    coro_avg.send(None)
except StopIteration as exc:
    result = exc.value

print(result) # Result(count=3, average=15.0)

优点

协程的特点在于是一个线程执行,那和多线程比,协程有何优势?

最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。

第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。

threading

  • 进程是由若干线程组成的,一个进程至少有一个线程。

  • 线程是操作系统直接支持的执行单元

import time, threading

# 新线程执行的代码:
def loop():
    print('thread %s is running...' % threading.current_thread().name)
    n = 0
    while n < 5:
        n = n + 1
        print('thread %s >>> %s' % (threading.current_thread().name, n))
        time.sleep(1)
    print('thread %s ended.' % threading.current_thread().name)

print('thread %s is running...' % threading.current_thread().name)
t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join() # join()方法可以等待线程结束后再继续往下运行
print('thread %s ended.' % threading.current_thread().name)

上面程序结果如下所示

thread MainThread is running...
thread LoopThread is running...
thread LoopThread >>> 1
thread LoopThread >>> 2
thread LoopThread >>> 3
thread LoopThread >>> 4
thread LoopThread >>> 5
thread LoopThread ended.
thread MainThread ended.

Lock()

  • 多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。(下面的程序结果不一定为0)
'''
下面程序结果不一定为0!!!
'''

import time, threading

# 假定这是你的银行存款:
balance = 0

def change_it(n):
    # 先存后取,结果应该为0:
    global balance
    balance = balance + n
    balance = balance - n

def run_thread(n):
    for i in range(100000):
        change_it(n)

t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)

因此,我们要用Lock()

import time, threading

# 假定这是你的银行存款:
balance = 0
lock = threading.Lock()

def change_it(n):
    # 先存后取,结果应该为0:
    global balance
    balance = balance + n
    balance = balance - n

def run_thread(n):
    for i in range(100000):
        # 先要获取锁:
        lock.acquire()
        try:
            # 放心地改吧:
            change_it(n)
        finally:
            # 改完了一定要释放锁:
            lock.release()

t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)

GIL (Gloabl Interpreter Lock, 全局解释器锁)

  • CPython解释器本身就不是线程安全的,因此有GIL,一次只允许使用一个线程执行Python字节码。因此,一个Python进程通常不能同时使用多个CPU核心。(这是CPython的局限,与Python语言无关。Jython和IronPython没有这种限制。)

  • 标准库中所有执行阻塞型I/O操作的函数,在等待操作系统返回结果时都会释放GIL。这意味着在Python语言这个层次上可以使用多线程,而I/O密集型Python程序能从中受益:一个Python线程等待网络响应时,阻塞型I/O函数会释放GIL,再运行一个线程。

用C、C++或Java来改写相同的死循环,直接可以把全部核心跑满,4核就跑到400%,8核就跑到800%,为什么Python不行呢?

因为Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。

GIL是Python解释器设计的历史遗留问题,通常我们用的解释器是官方实现的CPython,要真正利用多核,除非重写一个不带GIL的解释器。

所以,在Python中,可以使用多线程,但不要指望能有效利用多核。如果一定要通过多线程利用多核,那只能通过C扩展来实现,不过这样就失去了Python简单易用的特点。

不过,也不用过于担心,Python虽然不能利用多线程实现多核任务,但可以通过多进程实现多核任务。多个Python进程有各自独立的GIL锁,互不影响。

ThreadLocal

  • 一个ThreadLocal变量虽然是全局变量,但每个线程都只能读写自己线程的独立副本,互不干扰。ThreadLocal解决了参数在一个线程中各个函数之间互相传递的问题。

  • 全局变量local_school就是一个ThreadLocal对象,每个Thread对它都可以读写student属性,但互不影响。你可以把local_school看成全局变量,但每个属性如local_school.student都是线程的局部变量,可以任意读写而互不干扰,也不用管理锁的问题,ThreadLocal内部会处理。

  • 可以理解为全局变量local_school是一个dict,不但可以用local_school.student,还可以绑定其他变量,如local_school.teacher等等。

  • ThreadLocal最常用的地方就是为每个线程绑定一个数据库连接,HTTP请求,用户身份信息等,这样一个线程的所有调用到的处理函数都可以非常方便地访问这些资源。

import threading

# 创建全局ThreadLocal对象:
local_school = threading.local()

def process_student():
    # 获取当前线程关联的student:
    std = local_school.student
    print('Hello, %s (in %s)' % (std, threading.current_thread().name))

def process_thread(name):
    # 绑定ThreadLocal的student:
    local_school.student = name
    process_student()

t1 = threading.Thread(target= process_thread, args=('Alice',), name='Thread-A')
t2 = threading.Thread(target= process_thread, args=('Bob',), name='Thread-B')
t1.start()
t2.start()
t1.join()
t2.join()

考题

import threading

local = threading.local()

def func(name):
    print('A')
    local.name = name
    print('B')

t1 = threading.Thread(target=func, args=('aly',))
t2 = threading.Thread(target=func, args=('pendy',))

t1.start()
t2.start()
t1.join()
t2.join()

上面程序运行结果为

AA

BB

Socket

  1. connect(): 主动初始化TCP服务器连接,。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。

  2. getsockname(): 返回套接字自己的地址。通常是一个元组(ipaddr,port)

  3. listen(): 开始TCP监听。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。

  4. recvfrom(): 接收UDP数据,与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。

Modules

bisect (二分搜索)

  1. 两个方法返回插入位置:1) bisect.bisect_left(haystack, needle), 2) bisect.bisect(haystack, needle)

  2. bisect_left返回的插入位置使原序列中跟被插入元素相等的元素的位置,也就是新元素会被放置于它相等的元素的前面。

  3. bisect(或者说bisect_right) 返回的则是跟它相等的元素之后的位置。

  4. 两个可选参数:1) lo, 2) hi

import bisect

HAYSTACK = [1, 4, 6, 8]

print(bisect.bisect_left(HAYSTACK, 4)) # 1
print(bisect.bisect(HAYSTACK, 4))      # 2


print(bisect.bisect_left(HAYSTACK, 5)) # 2
print(bisect.bisect(HAYSTACK, 5))      # 2
  1. bisect.insort(seq, item)直接插入新元素,

math

import math

print(type(math.floor(5.5))) # 5, int (Python 3)
                             # 5.0, float(Python 2)

operator, functools

from functools import reduce
from operator import mul

def fact(n):
    return reduce(mul, range(1, n+1))

print(fact(5))
from operator import itemgetter

a = [(1, 4), (3, 2), (4, 1), (2, 3)]
print(sorted(a))                    # [(1, 4), (2, 3), (3, 2), (4, 1)]

# 获取index为1处的值
print(sorted(a, key=itemgetter(1))) # [(4, 1), (3, 2), (2, 3), (1, 4)]
from operator import mul
from functools import partial

triple = partial(mul, 3)                # 使用mul创建triple函数,把第一个参数定为3
print(triple(7))                        # 3
print(list(map(triple, range(1, 5))))   # [3, 6, 9, 12]

random

import random

random.seed()
print(random.random()) # generate a float between 0 and 1

a = [1, 2, 3, 4]
random.shuffle(a)      # 打乱a的顺序
print(a)

weakref

弱引用不会增加对象的引用数量,因此,弱引用不会妨碍所指对象被当作垃圾回收。

弱引用在缓存应用中很有用,因为我们不想因为被缓存引用着而始终保存缓存对象。

import weakref
import sys

a_set ={0, 1}

# sys包中的getrefcount()来查看某个对象的引用计数。
# 需要注意的是,当使用某个引用作为参数,传递给getrefcount()时,参数实际上创建了一个临时的引用。
# 因此,getrefcount()所得到的结果,会比期望的多1
print(sys.getrefcount(a_set)) # 2
wref = weakref.ref(a_set)
print(sys.getrefcount(a_set)) # 2


print(wref)                   # <weakref at 0x0000020F308B8098; to 'set' at 0x0000020F308D43C8>
print(wref())                 # {0, 1}

print(sys.getrefcount(a_set)) # 2
b_set = a_set
print(sys.getrefcount(a_set)) # 3

a_set = {2, 3, 4}
print(wref())                 # {0, 1}
print(wref() is None)         # False
print(sys.getrefcount(wref)) # 2


b_set = {2}
print(wref())                 # None
print(wref() is None)         # True
  • 弱引用的局限
    • listdict不能作为所指对象,但是它们的子类可以。
    • set和用户定义的类型可以作为所指对象。
    • inttuple以及它们的子类都不能作为所指对象。