Pyhon笔记

  • pycharm输入一个方法时,不知道要给定哪些参数的话,可以按commond + P得到提示

1. Python基础

(1)直接运行.py文件:
  • 在MacOS或Linux系统中,可以直接运行.py文件,方法是在.py文件的第一行加上以下注释:
#!/opt/miniconda3/bin/python

print("Hello, World!")
  • 然后修改.py文件的权限:
chmod a+x hello.py
  • 这时就可以直接执行hello.py文件了:
./hello.py
(2)数据类型
  • 整数
    • 计算机由于使用二进制,所以,有时候用十六进制表示整数比较方便,十六进制用0x前缀和0-9,a-f表示,例如:0xff000xa5b4c3d2,等等。
    • 对于很大的数,例如10000000000,很难数清楚0的个数。Python允许在数字中间以_分隔,因此,写成10_000_000_00010000000000是完全一样的。十六进制数也可以写成0xa1b2_c3d4
  • 字符串
    • r’ '表示‘’内部的字符串默认不转意
      print(r'\\\t\\') # 输出: \\\t\\
    • 如果字符串内部有很多换行,用\n写在一行里不好阅读,为了简化,Python允许用'''...'''的格式表示多行内容
 print('''line1
 line2
 line3''')

# 输出:
line1
line2
line3
  • 取字符的整数表示 ord() ord('A') # 65

  • 把编码转换为对应的字符 chr() chr(66) # 'B'

  • 把str转换为以字节为单位bytes b x = b'ABC'

  • .encode()方法可以把字符串编码为指定的bytes :'ABC'.encode('ascii') '中文'.encode('utf-8')

  • .decode()方法把字节流bytes转换为字符串: b'ABC'.decode('ascii')

    • 如果bytes中存在无法解码的字节,decode()方法会报错;
    • 可以指定errors='ignore’忽略不能解码的字节: b'\xe4\xb8\xad\xff'.decode('utf-8', errors='ignore')
  • 当Python解释器读取源代码时,为了让它按UTF-8编码读取,我们通常在文件开头写上这两行:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
(3)输出格式
  • % : 'Hello, %s' % 'world' Hi, %s, you have $%d.' % ('Michael', 1000000)

    • %d 整数
    • %f 浮点数
    • %s 字符串
    • %x 十六进制整数
    • %% 转移字符,表示百分号
  • .format() 'Hello, {0}, 成绩提升了{1:.1f}%'.format('小明', 17.125) # Hello,小明,成绩提升了17.1%

  • f-string 使用以f开头的字符串,称之为f-string,它和普通字符串不同之处在于,字符串如果包含{xxx},就会以对应的变量替换

 	r = 2.5
	s = 3.14 * r ** 2
	print(f'The area of a circle with radius {r} is {s:.2f}')
	The area of a circle with radius 2.5 is 19.62
(4)字典
  • dct = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
  • 获取键为’Michael‘的值:
    • dct['Michael']
    • dct.get('Michael') # 如果键不存在,则返回None
    • dct.get('Michael', -1) # 如果键不存在,则返回-1
  • dict 的key必须是不可变对象:
    • 这是因为dict根据key来计算value的存储位置,如果每次计算相同的key得出的结果不同,那dict内部就完全混乱了。
    • 这个通过key计算位置的算法称为哈希算法(Hash)。
    • 要保证hash的正确性,作为key的对象就不能变。
    • 在Python中,字符串、整数等都是不可变的,因此,可以放心地作为key。而list是可变的,就不能作为key
(5)set
  • 创建: s = set([1, 1, 2, 2, 3, 3]) # {1,2,3}
  • 添加: s.add(4)
  • 删除: s.remove(4)
  • 求交: s1 & s2
  • 求并: s1 | s2
  • set的原理和dict一样,所以,同样不可以放入可变对象,因为无法判断两个可变对象是否相等,也就无法保证set内部“不会有重复元素”。list不可以放入set。

2. 函数

(1)默认参数
  • def add_end(L='default')
  • 定义默认参数要牢记一点:默认参数必须指向不变对象!
    • 如果指向可变对象,第二次调用时,默认指向的对象在第一次调用后发生了变化,第二次会直接使用第一次调用后变化的对象,对结果产生不可预知的影响
(2)可变参数
  • 定义函数时传可变参数:使用*numbers表示传入的参数数量可变,numbers为tuple,保存所有传入的参数
# 定义时:
def calc(*numbers):
	sum = 0
	for n in numbers:
		sum += n*n
	return sum

# 调用时:
calc(1,2,3)
calc(1,2,3,4)
  • 调用时传入可变参数: *nums表示将nums中的所有元素传入函数调用中
nums = [1,2,3]
calc(*nums)    # 等价于 calc(nums[0], nums[1], nums[2])
(3)关键字参数
  • 可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict:
def person(name, age, **kw):
	print('name:', name, 'age:', age, 'other:', kw)
	
person('Michael', 30)    # name: Michael age: 30 other: {}
person('Bob', 35, city='Beijing')      # name: Bob age: 45 other: {'city': 'Beijing'}
  • 调用时,用**修饰字典变量,注意此处传入的**extraextra的一份拷贝,函数调用中对传入的参数的改变,不会影响到原始变量extra
extra = {'city': 'Beijing', 'job': Engineer}
person('Jack', 24, **extra)    # 等价于  person('Jack', 24, city=extra['city'], job=extra['job'])
(4)命名关键字参数
  • 如果要限制关键字参数的名字,就可以用命名关键字参数
  • 和关键字参数**kw不同,命名关键字参数需要一个特殊分隔符**后面的参数被视为命名关键字参数
def person(name, age, *, city, job):
    print(name, age, city, job)
  • 如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了:
def person(name, age, *args, city, job):
    print(name, age, args, city, job)
  • 命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:
>>> person('Jack', 24, 'Beijing', 'Engineer')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: person() missing 2 required keyword-only arguments: 'city' and 'job'
  • 由于调用时缺少参数名cityjob,Python解释器把前两个参数视为位置参数,后两个参数传给*args,但缺少命名关键字参数导致报错。
(5)组合参数
  • 在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
  • 对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的
  • 虽然可以组合多达5种参数,但不要同时使用太多的组合,否则函数接口的可理解性很差。
(6)递归函数
  • 尾递归优化:
    • 解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。
    • 尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。
    • 这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
# 优化前的递归函数
def fact(n):
    if n==1:
        return 1
    return n * fact(n - 1)
	
# 尾递归优化
def fact(n):
    return fact_iter(n, 1)

def fact_iter(num, product):
    if num == 1:
        return product
    return fact_iter(num - 1, num * product)

3. 高级特性

(1)迭代
  • 判断一个对象是不是可迭代对象:collections.abcIterable类型
	from collections.abc import Iterable
	
	print(isinstance('abc', Iterable))   # str是可迭代对象 True
	print(isinstance(123, Iterable))   # 整数不是可迭代对象 False
(2)列表生成式
  • nums = list(range(1, 11)) # [1,2,3,4,5,6,7,8,9,10]
  • [x*x for x in range(1, 11)] # [1,4,9,16,25,36,49,64,81,100]
  • [x*x for x in range(1, 11) if x%2==0] # [4,16,36,64,100]
  • [m+n for m in 'ABC' for n in 'XYZ'] # ['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
  • d = {'x': 'A', 'y': 'B', 'z': 'C'}
  • [k + '=' + v for k,v in d.items()] # ['y=B', 'x=A', 'z=C']
(3)生成器

通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator

  • 列表: l = [x*x for x in range(10)] # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
  • 生成器: g = (x*x for x in range(10)) # <generator object <genexpr> at 0x1022ef630>
  • 访问生成器的元素: next(g)
  • 遍历生成器的元素: for n in g:
  • 定义生成器的另一种方法:
def fib(max):
	n, a, b = 0, 0, 1
	while n < max:
		yield b				# yield
		a, b = b, a+b
		n = n+1
	return 'done'
  • generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行
  • 生成杨辉三角
def triangles():  
    L = [1]  
    while True:  
        yield L  
        L1 = L + [0]  
        L2 = [0] + L  
        L = [L1[i] + L2[i] for i in range(len(L1))]
(4)迭代器
  • 将可迭代对象(Iterable)变为迭代器 Iterator对象: iter(.....)

4. 函数式编程

(1)高阶函数
  • 函数名也是变量:
    f = abs  # f指向函数abs(), f是指向函数的变量,其实abs也是指向函数的变量  打印f,是: <built-in function abs>
    print(f(-10))   # 10
    
  • 传入函数:
def add(x, y, f):
	return f(x) + f(y)
	
# 调用:
add(-5, 6, abs)
(2)map/reduce
  • map()
    • map()函数接收两个参数,一个是函数,一个是Iterablemap将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。
    • map(fun, list)
    • 例如: map(str, [1,2,3,4,5]) 将list中的所有int转换为str,转换后的结果是一个Iterator对象,Iterator是惰性序列,因此可以用list()转换
    • list(map(str, [1,2,3,4,5]))
  • reduce() from functools import reduce
    • reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算
    • reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
    • reduce接收的函数可以用lambda函数直接定义
    • reduce(lambda x, y: x*10+y, map(int, s))
      • 首先用map()将字符串s中的每一个字符通过int()函数转换为整型变量;
      • 再通过reduce()将所有int值拼接成一个整型数值
(3)filter
  • map()类似,filter()也接收一个函数和一个序列。
  • map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。
  • 例如,在一个list中,删掉偶数,只保留奇数:
    def is_odd(n):
    	return n % 2 == 1
    list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
    # 结果: [1, 5, 9, 15]
    
  • 可见用filter()这个高阶函数,关键在于正确实现一个“筛选”函数。
  • 注意到filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list。
(4)sorted
  • Python内置的sorted()函数就可以对list进行排序: sorted([36, 5, -12, 9, -21])
  • sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序:
    sorted([36, 5, -12, 9, -21], key=abs) key需传入一个函数,该函数会作用在list的每个元素上,然后将函数的每一个对应输出作为排序的依据
  • 要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True
    sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
(5)返回函数
  • 函数作为返回值:高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回
    def calc_sum(*args):
    	ax = 0
    	for n in args:
    		ax += n
    	return ax
    
    # 可以不返回求和的结果,而是返回求和的函数
    def lazy_sum(*args):
    	def sum():
    		ax = 0
    		for n in args:
    			ax += n
    		return ax
    	return sum
    
  • 当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数: <function lazy_sum.<locals>.sum at 0x101c6ed90>
  • 调用函数f()时,才真正计算求和的结果: 25
  • 在这个例子中,我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。
  • 调用calc_sum()时,返回的函数并没有立刻执行,而是直到调用了f()才执行。
	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
	# 返回的函数引用了变量`i`,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量`i`已经变成了`3`,因此最终结果为`9`。
  • 返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
  • 如果在内层函数fn()中,对外层变量x赋值,由于Python解释器会把x当作函数fn()的局部变量,它会报错,原因是x作为局部变量并没有初始化,直接计算x+1是不行的。但我们其实是想引用inc()函数内部的x,所以需要在fn()函数内部加一个nonlocal x的声明。加上这个声明后,解释器把fn()x看作外层函数的局部变量,它已经被初始化了,可以正确计算x+1
	def inc():
		x = 0
		def fn():
			nonlocal x     # 将x声明为nonlocal,代指外层的x
			x = x + 1
			return x
		return fn

	f = inc()
	print(f()) # 1
	print(f()) # 2
  • 使用闭包时,对外层变量赋值前,需要先使用nonlocal声明该变量不是当前函数的局部变量。
(6)匿名函数
  • 关键字lambda表示匿名函数,冒号前面的x表示函数参数。
  • 匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。
  • 用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数
  • 同样,也可以把匿名函数作为返回值返回,比如:
	def build(x, y):
		return lambda: x * x + y * y
(7)装饰器
  • 定义一个now()函数
	 def now():
		 print('2015-3-25')
  • 现在,假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)
  • 本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator
  • 不带参数的装饰器log
import functools

def log(func):
    @functools.wraps(func)      # 返回的那个`wrapper()`函数名字就是`'wrapper'`,所以,需要把原始函数的`__name__`等属性复制到`wrapper()`函数中,否则,有些依赖函数签名的代码执行就会出错。不需要编写`wrapper.__name__ = func.__name__`这样的代码,Python内置的`functools.wraps`就是干这个事的
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper
  • 使用:
	@log
	def now():
		print('2015-3-25')
  • 带参数的装饰器:
   import functools

   def log(text):
   	def decorator(func):
   		@functools.wraps(func)
   		def wrapper(*args, **kw):
   			print('%s %s():' % (text, func.__name__))
   			return func(*args, **kw)
   		return wrapper
   	return decorator
  • 使用:
	@log('execute')
	def now():
		print('2015-3-25')
(8)偏函数
  • functools.partial的作用是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。
	import functools
	int2 = functools.partial(int, base=2)
	int2('1000000')  # 64


	# 上述代码定义的int2等价于:
	def int2(x, base=2):
		return int(x, base)   # 以2进制的形式传入x,转为10进制
  • 创建偏函数时,实际上可以接收函数对象、*args**kw这3个参数,当传入:
	int2 = functools.partial(int, base=2)
  • 实际上固定了int()函数的关键字参数base,也就是:
	int2('10010')
  • 相当于:
	kw = { 'base': 2 }
	int('10010', **kw)
  • 当传入:
	max2 = functools.partial(max, 10)
  • 实际上会把10作为*args的一部分自动加到左边,也就是:
	max2(5, 6, 7)
  • 相当于:
args = (10, 5, 6, 7)
max(*args)
  • 结果为10

5. 模块

(1)包:
  • 引入了包以后,只要顶层的包名不与别人冲突,那所有模块都不会与别人冲突。
	mycompany
	├─ __init__.py
	├─ abc.py
	└─ xyz.py
  • 请注意,每一个包目录下面都会有一个__init__.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。__init__.py可以是空文件,也可以有Python代码,因为__init__.py本身就是一个模块,而它的模块名就是mycompany
  • 自己创建模块时要注意命名,不能和Python自带的模块名称冲突。例如,系统自带了sys模块,自己的模块就不可命名为sys.py,否则将无法导入系统自带的sys模块。
(2)使用模块:
	#!/opt/miniconda3/envs/dl/bin/python  
	# -*- coding: utf-8 -*-

	' a test module '

	__author__ = 'Michael Liao'

	import sys

	def test():
		args = sys.argv
		if len(args)==1:
			print('Hello, world!')
		elif len(args)==2:
			print('Hello, %s!' % args[1])
		else:
			print('Too many arguments!')

	if __name__=='__main__':
		test()
  • 第1行和第2行是标准注释,第1行注释可以让这个hello.py文件直接在Unix/Linux/Mac上运行,第2行注释表示.py文件本身使用标准UTF-8编码;
  • 第4行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;
  • 第6行使用__author__变量把作者写进去,这样当你公开源代码后别人就可以瞻仰你的大名;
  • 以上就是Python模块的标准文件模板,当然也可以全部删掉不写,但是,按标准办事肯定没错。
  • 后面开始就是真正的代码部分。
  • 作用域:
    • 在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在Python中,是通过_前缀来实现的。
    • 正常的函数和变量名是公开的(public),可以被直接引用,比如:abcx123PI等;
    • 类似__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的__author____name__就是特殊变量,hello模块定义的文档注释也可以用特殊变量__doc__访问,我们自己的变量一般不要用这种变量名;
    • 类似_xxx__xxx这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc__abc等;
    • 之所以我们说,private函数和变量“不应该”被直接引用,而不是“不能”被直接引用,是因为Python并没有一种方法可以完全限制访问private函数或变量,但是,从编程习惯上不应该引用private函数或变量。

6. 面向对象编程

(1)类和实例
  • class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。继承object类时,object可以省略
  • 和静态语言不同,Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同
	class Student(object):
		pass

	bart = Student()
	bart.name = 'Bart Simpson'
  • 由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__方法,在创建实例的时候,就把namescore等属性绑上去。注意:特殊方法“init”前后分别有两个下划线!!!
	class Student(object):

		def __init__(self, name, score):
			self.name = name
			self.score = score
  • 注意到__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身
  • 有了__init__方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去
(2)访问限制
  • 如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问
	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怎么办?可以给Student类增加get_nameget_score这样的方法:

	class Student(object):
		...

		def get_name(self):
			return self.__name

		def get_score(self):
			return self.__score
  • 如果又要允许外部代码修改score怎么办?可以再给Student类增加set_score方法:
	class Student(object):
		...

		def set_score(self, score):
			self.__score = score
  • 需要注意的是,在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name____score__这样的变量名。

  • 有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。

  • 双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量

(3)继承和多态
  • 在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。 class Dog(Animal) ,表示Dog类继承自Animal父类,Dog类就自动拥有了Animal类的所有功能
  • 多态的好处就是,当我们需要传入DogCatTortoise……时,我们只需要接收Animal类型就可以了,因为DogCatTortoise……都是Animal类型,然后,按照Animal类型进行操作即可。由于Animal类型有run()方法,因此,传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的run()方法,这就是多态的意思
  • 判断一个变量是否是某个类型可以用isinstance()判断: isinstance(b, Animal)
  • 静态语言 vs 动态语言
    • 对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。
    • 对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了:
	class Animal(object):
		def run(self):
			print('Animal is running')

			
	class Dog(Animal):
		def run(self):
			print('Dog is running')
			

	class Timer(object):
		def run(self):
			print('Start...')
			
	def run_twice(animal):
		animal.run()
		animal.run()
- 这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
- Python的“file-like object“就是一种鸭子类型。对真正的文件对象,它有一个`read()`方法,返回其内容。但是,许多对象,只要有`read()`方法,都被视为“file-like object“。许多函数接收的参数就是“file-like object“,你不一定要传入真正的文件对象,完全可以传入任何实现了`read()`方法的对象。
	
	class Timer(object):
		def run(self):
			print('Start...')
	# Timer()类有run()方法,就是file-like object方法,它虽然不是继承自Animal类,但是也可以被run_twice()方法调用:
	run_twice(Animal())
	run_twice(Dog())
	run_twice(Timer())   # 
(4)获取对象属性
  • 使用type()判断对象类型
	>>> type(123)
	<class 'int'>
	>>> type('str')
	<class 'str'>
	>>> type(None)
	<type(None) 'NoneType'>
	>>> type(abs)
	<class 'builtin_function_or_method'>
	>>> type(a)
	<class '__main__.Animal'>
  • 判断基本数据类型可以直接写intstr等,但如果要判断一个对象是否是函数怎么办?可以使用types模块中定义的常量:
	>>> type(123)==type(456)
	True
	>>> type(123)==int
	True
	>>> type('abc')==type('123')
	True
	>>> type('abc')==str
	True
	>>> type('abc')==type(123)
	False

	>>> import types
	>>> def fn():
	...     pass
	...
	>>> type(fn)==types.FunctionType
	True
	>>> type(abs)==types.BuiltinFunctionType
	True
	>>> type(lambda x: x)==types.LambdaType
	True
	>>> type((x for x in range(10)))==types.GeneratorType
	True
  • 要判断class的类型,可以使用isinstance()函数。 isinstance(dog, Animal)
    • 能用type()判断的基本类型也可以用isinstance()判断 isinstance(123, int)
    • 并且还可以判断一个变量是否是某些类型中的一种,比如下面的代码就可以判断是否是list或者tuple:
      	>>> isinstance([1, 2, 3], (list, tuple))
      	True
      	>>> isinstance((1, 2, 3), (list, tuple))
      	True
      
  • 如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list
    • 我们自己写的类,如果也想用len(myObj)的话,就自己写一个__len__()方法
    	>>> class MyDog(object):
    	...     def __len__(self):
    	...         return 100
    	...
    	>>> dog = MyDog()
    	>>> len(dog)
    	100
    
    • hasattr(obj, 'x') # 返回bool,查看对象obj是否有'x'属性
    • getattr(obj, 'lower') # 返回bool,查看对象obj是否有'lower()方法'
    • getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
    • setattr(obj, 'y', 0) # 将对象obj中的y属性设置为9
(5)实例属性和类属性
  • 在编写程序的时候,千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。

7. 面向对象高级编程

(1)使用__slots__
  • 给单个实例动态绑定方法:
	class Student(object):
		pass

	def set_age(self, age):
		self.age = age

	from types import MethodType
	s = Student()
	s.set_age = MethodType(set_age, s)		# 给实例绑定一个方法
	s.set_age(25)    # 调用实例方法
  • 给所有实例都绑定方法,可以给class直接绑定该方法:
	Student.set_score = set_score
  • 使用__slots__, 限制实例的属性,只允许对Student实例添加nameage属性
	class Student(object):
		__slots__ = ('name', 'age')  # 用tuple定义允许绑定的属性名称
		
	s = Student()
	s.name = 'Michael'
	s.age = 25
	s.score = 99   # 绑定score属性失败,其不在__slots__中
  • 使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的
  • 除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__
(2)使用@property
  • 在类中,可以通过get_score()set_score()方法来对score属性的读写做限制(例如score必须是int,在0-100之间)
  • 但是,get_score()set_score()的调用方法又略显复杂,没有直接用属性这么直接简单。
  • Python内置的@property装饰器就是负责把一个方法变成属性调用的:
	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
  • 把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作。
  • 注意到这个神奇的@property,我们在对实例属性操作的时候,就知道该属性很可能不是直接暴露的,而是通过getter和setter方法来实现的。
  • 要特别注意:属性的方法名不要和实例变量重名。例如,以下的代码是错误的:
	class Student(object):

		# 方法名称和实例变量均为birth:
		@property
		def birth(self):
			return self.birth
  • 这是因为调用s.birth时,首先转换为方法调用,在执行return self.birth时,又视为访问self的属性,于是又转换为方法调用,造成无限递归,最终导致栈溢出报错RecursionError
(3)多继承
  • 一个类可以继承自多个父类,这个类就可以拥有它的所有父类的功能
	class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
		pass
  • 在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich继承自Bird。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Ostrich除了继承自Bird外,再同时继承Runnable。这种设计通常称之为MixIn。
  • 为了更好地看出继承关系,我们把RunnableFlyable改为RunnableMixInFlyableMixIn。类似的,你还可以定义出肉食动物CarnivorousMixIn和植食动物HerbivoresMixIn,让某个动物同时拥有好几个MixIn
(4)定制类
  • __slot__(),规定只能给类定义特定的属性

  • __len__(),能让class作用于len()函数

  • __str__(),类似于Java的toString()方法,用于打印一个实例:

    	class Student(object):
    		 def __init__(self, name):
    			 self.name = name
    		 def __str__(self):
    			 return 'Student object (name: %s)' % self.name
    
    	print(Student('Michael'))    # Student object (name: Michael)
    
    • 但是细心的朋友会发现直接敲变量不用print,打印出来的实例还是不好看:
    	>>> s = Student('Michael')
    	>>> s
    	<__main__.Student object at 0x109afb310>
    
    • 这是因为直接显示变量调用的不是__str__(),而是__repr__(),两者的区别是__str__()返回用户看到的字符串,而__repr__()返回程序开发者看到的字符串,也就是说,__repr__()是为调试服务的。
    • 解决办法是再定义一个__repr__()。但是通常__str__()__repr__()代码都是一样的,所以,有个偷懒的写法:
    	class Student(object):
    		def __init__(self, name):
    			self.name = name
    		def __str__(self):
    			return 'Student object (name=%s)' % self.name
    		__repr__ = __str__
    
  • __iter__(),如果一个类想被用于for ... in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。

    class Fib(object):
    	def __init__(self):
    		self.a, self.b = 0, 1 # 初始化两个计数器a,b
    
    	def __iter__(self):
    		return self # 实例本身就是迭代对象,故返回自己
    
    	def __next__(self):
    		self.a, self.b = self.b, self.a + self.b # 计算下一个值
    		if self.a > 100000: # 退出循环的条件
    			raise StopIteration()
    		return self.a # 返回下一个值
    		
    
    for n in Fib():
    	 print(n)
    	 
    # Fib实例虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行,比如,取第5个元素Fib()[5]会报错
    
  • __getitem__(),要表现得像list那样按照下标取出元素,需要实现__getitem__()方法:

    class Fib(object):
    	def __getitem__(self, n):
    		a, b = 1, 1
    		for x in range(n):
    			a, b = b, a + b
    		return a
    		
    f = Fib()
    f[0]    # 1
    f[1]    # 1
    
    • 此时,要使用切片Fib()[0:5]依然不行,因为__getitem__(self, n)中的n既可以是int,也可以是切片对象slice,因此要做判断:
    class Fib(object):
    	def __getitem__(self, n):
    		if isinstance(n, int): # n是索引
    			a, b = 1, 1
    			for x in range(n):
    				a, b = b, a + b
    			return a
    		if isinstance(n, slice): # n是切片
    			start = n.start
    			stop = n.stop
    			if start is None:
    				start = 0
    			a, b = 1, 1
    			L = []
    			for x in range(stop):
    				if x >= start:
    					L.append(a)
    				a, b = b, a + b
    			return L
    			
    f = Fib()
    f[0:5]
    
    • __getattr__(),用于处理调用类中不存在的方法或者属性:
    	class Student(object):
    
    		def __init__(self):
    			self.name = 'Michael'
    
    		def __getattr__(self, attr):
    			if attr=='score':		# 调用类中不存在的'score'属性时
    				return 99
    			if attr=='age':   		# 调用类中不存在的'age()'方法时
    			    return lambda: 25 
    			raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
    
    • __call__(),任何类,只需要定义一个__call__()方法,就可以直接对实例进行调用。
    	class Student(object):
    		def __init__(self, name):
    			self.name = name
    
    		def __call__(self):
    			print('My name is %s.' % self.name)
    	# 调用方式如下:
    	>>> s = Student('Michael')
    	>>> s() # self参数不要传入
    	My name is Michael.
    
    	- 通过`callable()`函数,我们就可以判断一个对象是否是“可调用”对象。
    
    	>>> callable(Student())
    	True
    	>>> callable(max)
    	True
    	>>> callable([1, 2, 3])
    	False
    	>>> callable(None)
    	False
    	>>> callable('str')
    	False
    
(5)使用枚举类
  • 为枚举类型定义一个class类型,每个常量都是class的一个唯一实例。Python提供了Enum类来实现这个功能
	from enum import Enum

	Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
  • 使用: Month.Jan # <Month.Jan: 1> 默认从1开始
  • 枚举所有成员:
	for name, member in Month.__members__.items():
		print(name, '=>', member, ',', member.value)
  • 如果需要更精确地控制枚举类型,可以从Enum派生出自定义类
	from enum import Enum, unique

	@unique				# `@unique`装饰器可以帮助我们检查保证没有重复值。
	class Weekday(Enum):
		Sun = 0 # Sun的value被设定为0
		Mon = 1
		Tue = 2
		Wed = 3
		Thu = 4
		Fri = 5
		Sat = 6
		
		
	# 访问:
	Weekday.Mon
	Weekday['Mon']
	Weekday.Mon.value   # 1
	Weekday(1)          # Weekday.Mon
	
(6)使用元类
  • type()函数可以查看一个类型或变量的类型,Hello是一个class,它的类型就是type,而h是一个实例,它的类型就是class Hello
	>>> from hello import Hello
	>>> h = Hello()
	>>> h.hello()
	Hello, world.
	>>> print(type(Hello))
	<class 'type'>
	>>> print(type(h))
	<class 'hello.Hello'>
  • class的定义是运行时动态创建的,而创建class的方法就是使用type()函数。
  • type()函数既可以返回一个对象的类型,又可以创建出新的类型:
	>>> def fn(self, name='world'): # 先定义函数
	...     print('Hello, %s.' % name)
	...
	>>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
	>>> h = Hello()
	>>> h.hello()
	Hello, world.
	>>> print(type(Hello))
	<class 'type'>
	>>> print(type(h))
	<class '__main__.Hello'>
  • 要创建一个class对象,type()函数依次传入3个参数:
    1. class的名称;
    2. 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;如果只继承一个父类,记得要加’,,例如 (object,)
    3. class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。

  • metaclass: 要控制类的创建行为,还可以使用metaclass。

  • 步骤:先定义metaclass,就可以创建类,最后创建实例。

  • metaclass可以向类中动态添加函数

	class ListMetaclass(type):  # metaclass是累的模板,所以必须从type类型派生, 类名通常习惯以Metaclass结尾
		def __new__(cls, name, bases, attrs):		# 当前准备创建的类的对象,类的名字,类继承的父类集合,累的方法集合
			attrs['add'] = lambda self, value: self.append(value)   # 向类中添加self.add()方法
			return type.__new__(cls, name, bases, attrs)
			
	# 定义类时,徐传入关键字参数metaclass
	class MyList(list, metaclass=ListMetaclass):
		pass
		
	L = MyList()
	L.add(1)	# [1]    原list 是没有 add()方法的,自定义的MyList类中添加了add()方法
  • 使用metaclass可以实现对象-关系映射(ORM)就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码更简单,不用直接操作SQL语句

8. 错误、调试、测试

(1)错误处理:
  • try-except-finally:

    	try:
    		do_something
    	except ZeroDivisionError as e:
    		print('except:', e)
    	except ValueError as e:
    		print('except:', e)
    	except Exception as e:
    		print('Error:', e)
    	
    	else:
    		print('no error!')   # 配合except使用,如果没捕获异常,就执行else。可以没有else
    	
    	finally:
    		print('finally...')  # 必定执行,可以没有finally
    
  • 记录错误:logging

    • Python内置的logging模块可以非常容易地记录错误信息
    	# err_logging.py
    
    	import logging
    
    	def foo(s):
    		return 10 / int(s)
    
    	def bar(s):
    		return foo(s) * 2
    
    	def main():
    		try:
    			bar('0')
    		except Exception as e:
    			logging.exception(e)
    
    	main()
    	print('END')
    
    • 同样是出错,但程序打印完错误信息后会继续执行,并正常退出
      $ python3 err_logging.py
      ERROR:root:division by zero
      Traceback (most recent call last):
        File "err_logging.py", line 13, in main
      	bar('0')
        File "err_logging.py", line 9, in bar
      	return foo(s) * 2
        File "err_logging.py", line 6, in foo
      	return 10 / int(s)
      ZeroDivisionError: division by zero
      END
      
  • 抛出错误:

    • 自定义错误:
    	class FooError(ValueError):
    		pass
    
    • 抛出:
    	raise FooError('invalid value')
    
    • 捕获后,不处理,继续向上抛出,直接用 raise,抛出当前错误
       # err_reraise.py
    
       def foo(s):
       	n = int(s)
       	if n==0:
       		raise ValueError('invalid value: %s' % s)
       	return 10 / n
    
       def bar():
       	try:
       		foo('0')
       	except ValueError as e:
       		print('ValueError!')
       		raise
    
       bar()
    
(2)调试
  • 断言:
    • assert 表达式, 字符串信息 # 如果表达式是True,正常执行, 如果为False,抛出AssertionError,并打印字符串信息
    • 启动Python解释器时可以用-O参数来关闭assert (断言的开关“-O”是英文大写字母O,不是数字0。) 关闭后,你可以把所有的assert语句当成pass来看。
  • logging:
    • assert比,logging不会抛出错误,而且可以输出到文件
    	import logging  
    	logging.basicConfig(level=logging.INFO)   # 设置输出级别
    	s = '0'  
    	n = int(s)  
    	logging.info('n = %d' % n)  
    	print(10 / n)
    	
    	# 输出为:
    	$ python err.py
    	INFO:root:n = 0
    	Traceback (most recent call last):
    	  File "err.py", line 8, in <module>
    		print(10 / n)
    	ZeroDivisionError: division by zero
    
    • logging允许你指定记录信息的级别,有debuginfowarningerror等几个级别,当我们指定level=INFO时,logging.debug就不起作用了。同理,指定level=WARNING后,debuginfo就不起作用了。这样一来,你可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。
  • pdb:启动Python的调试器pdb,让程序以单步方式运行,可以随时查看运行状态
    • $ python -m pdb err.py
(3)单元测试
  • 编写单元测试时,我们需要编写一个测试类,从unittest.TestCase继承

    import unittest
    
    from mydict import Dict
    
    class TestDict(unittest.TestCase):
    
    	def test_init(self):
    		d = Dict(a=1, b='test')
    		self.assertEqual(d.a, 1)
    		self.assertEqual(d.b, 'test')
    		self.assertTrue(isinstance(d, dict))
    
    	def test_key(self):
    		d = Dict()
    		d['key'] = 'value'
    		self.assertEqual(d.key, 'value')
    
    	def test_attr(self):
    		d = Dict()
    		d.key = 'value'
    		self.assertTrue('key' in d)
    		self.assertEqual(d['key'], 'value')
    
    	def test_keyerror(self):
    		d = Dict()
    		with self.assertRaises(KeyError):
    			value = d['empty']
    
    	def test_attrerror(self):
    		d = Dict()
    		with self.assertRaises(AttributeError):			# 期待抛出指定类型的Error,比如通过`d['empty']`访问不存在的key时,断言会抛出`KeyError`
    			value = d.empty
    
    • test开头的方法就是测试方法,不以test开头的方法不被认为是测试方法,测试的时候不会被执行
    • self.assertEqual()
    • self.assertTrue()
    • with self.assertRaises(AttributeError): 检查代码执行错误时,是否抛出了指定的异常
    • 运行单元测试:
      • 方法一:
      	if __name__ == '__main__':
      		unittest.main()
      
      • 方法二:
      	$ python -m unittest mydict_test
      
  • setUp()tearDown()

    • 相当于Java的JUnit中的before(),after(),可以在单元测试中编写这两个特殊的setUp()tearDown()方法。这两个方法会分别在每调用一个测试方法的前后分别被执行。
(4)文档测试
  • 用于测试写在注释中的例子
  • 对于如下的文档注释,要求Example:之下的例子严格按照Python交互式命令行的格式书写
    def abs(n):
    	'''
    	Function to get absolute value of number.
    
    	Example:
    
    	>>> abs(1)
    	1
    	>>> abs(-1)
    	1
    	>>> abs(0)
    	0
    	'''
    	return n if n >= 0 else (-n)
    
  • doctest,如果所有例子完全通过测试,则没有任何输出,如果不通过,输出测试的错误信息
    if __name__=='__main__':
       import doctest
       doctest.testmod()
    

9. IO编程

(1)文件读写
  • 读文件:

    • f = open('test.txt', 'r') 文件名,标识符
    • f.close()
    • 如果文件名不存在,会抛出IOError的错误,因此要用try-finally块进行包裹:
    try:
    	f = open('test.txt', 'r')
    	f.read()
    finally:
    	if f:
    		f.close()
    
    • f.read(),一次把文件中的所有内容读入到内存;如果内容较多,超出内存大小,则会报错
    • f.read(size),一次读取size大小的内容,如果不能确定文件大小,反复调用read(size)比较保险
    • f.readline(),一次读取文件中的一行
    • f.readlines(),一次读取所有内容并按行返回list
    • with open('test.txt', 'r') as f: print(f.read()) Python引入了with语句来自动帮我们调用close()方法
  • 读取二进制文件:

    • f.open('test.jpg', 'rb') # 十六进制表示的字节 要读取二进制文件,比如图片、视频等等,用'rb'模式打开文件即可
  • 指定字符编码格式:encoding

    • f = open('test.txt', 'r', encoding='gbk')
    • 如遇到非法编码的字符,会抛出异常,open()可接收参数errors指定处理方式: f = open('test.txt', 'r', encoding='gbk', errors='ignore')
  • 写文件:wwb(二进制)

    	f = open('test.txt', 'w')
    	f.write('Hello, world!')
    	f.close()
    
    • 或用with open('test.txt', 'w'): 自动关闭文件,无需自己写f.close()
    • wwb模式写入文件时,如果文件已经存在,会覆盖掉原有内容
    • a模式写入文件,可实现追加效果
(2)StringIO、BytesIO
  • StringIO是在内存中读写str

    • 写入:
    	from io import StringIO
    	f = StringIO()
    	f.write('hello')
    	f.write(' ')
    	f.write('world!')
    	print(f.getvalue())		# 获得写入后的str
    
    • 读入:
    	from io import StringIO
    	f = StringIO('Hello!\nHi!\nGoodbye!')  # 用一个str初始化StringIO
    	while True:
    		s = f.readline()					# 像读文件一样操作
    		if s == '':
    			break
    		print(s.strip())
    
  • BytesIO:StringIO操作的只能是str,如果要操作二进制数据,就需要使用BytesIO

    • 写入:
    	from io import BytesIO
    	f = BytesIO()
    	f.write('中文'.encode('utf-8'))
    	print(f.getvalue())
    
    • 读入:
    	from io import BytesIO
    	f = BytesIO(b'\xe4\xb8\xad\xe6\x96\x87')   
    	f.read()                # 像读文件一样操作
    
(3)操作文件和目录
  • Python内置的os模块可以直接调用操作系统提供的接口函数
	import os
	
	os.name  # 操作系统类型
	os.uname()  # 获取详细的系统信息
	os.environ	# 操作系统中定义的环境变量,全部保存在os.environ中
	os.environ.get('PATH')   # 获取环境变量PATH的值
	os.envirion.get('x', 'default')		# 获取环境变量'x'的值,如果不存在,返回默认值'default'
  • 操作文件和目录的函数一部分放在os模块中,一部分放在os.path模块中
	os.path.abspath('.')	# 查看当前目录的绝对路径
	
	os.path.join('/Users/michael', 'testdir')	# '/Users/michael/testdir,把两个路径合成一个时,不要直接拼字符串,而要通过`os.path.join()`函数,这样可以正确处理不同操作系统的路径分隔符
	
	# 拆分路径时,也不要直接拆分字符串
	os.path.split('/Users/michael/testdir/file.txt') 	# ('/Users/michael/testdir', 'file.txt')
	#直接获得文件的扩展名
	os.path.splitext('/path/to/file.txt')     # ('/path/to/file', '.txt')
	
	os.mkdir('/Users/michael/testdir')		# 创建目录
	
	os.rmdir('Users/michael/testdir')  		# 删除目录
	
	os.rename('test.txt', 'test.py')  		# 重名名文件
	
	os.remove('test.py')					# 删除文件
	
	# os不提供文件复制的函数,shutil模块提供了copyfile()函数可用于文件复制
	
	
	# 列出当前目录下的所有目录,即过滤掉文件
	[x for x in os.listdir('.') if os.path.isdir(x)]
	
	#列出所有.py文件
	[x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py']
	
(4)序列化
  • 序列化: 把变量从内存中编程可存储或传输的过程,在Python中叫 pickling,序列化后就可把内容写入磁盘或者通过网络传输到别的机器
  • 反序列化: 把变量内容从序列化的对象重新读入到内存,unpickling
	import pickle
	
	d = dict(name='Bob', age=20, score=88)
	
	pickle.dumps(d)		# 把对象序列化为bytes,注意是dumps,有s		   
	# b'\x80\x03}q\x00(X\x03\x00\x00\x00ageq\x01K\x14X\x05\x00\x00\x00scoreq\x02KXX\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00Bobq\x04u.'
	
	f = open('dump.txt', 'wb')
	pickle.dump(d, f)		# 把对象序列化后直接写入f
	f.close()
	
	
	f = open('dump.txt', 'rb')
	d = pickle.load(f)		# 反序列化出对象
	f.close()
  • JSON:如果在不同的编程语言之间传递对象,就必须把对象序列化为标准格式,序列化为JSON是比较好的选择
	import json
	d = dict(name='Bob', age=20, score=88)
	json.dumps(d)		# 序列化为JSON对象,返回str:'{"age":20, "score":88, "name":"Bob"}'
	
	# 反序列化
	json_str = '{"age":20, "score":88, "name":"Bob"}'
	json.loads(json_str)		# 注意是loads , 有s
  • JSON序列化自定义的class时,需指定default参数,即如何将class转换为dict:
	class Student(object):
		def __init__(self, name, age, score):
			self.name = name
			self.age = age
			self.score = score
	
	# 指定序列化的方式
	def student2dict(student):
    	return dict(name=student.name, age=student.age, score=student.score)
		
	# 指定反序列化的方式
	def dict2student(dct):
    return Student(dct['name'],dct['age'],dct['score'])
	
	# 序列化
	s = Student('Bob',22,88)
	json_str = json.dumps(s, default=student2dict)
	
	# 更加简化的序列化:
	json.dumps(s, default=lambda obj: obj.__dict__)
	
	
	# 反序列化
	json.loads(json_str, object_hook=dict2student)

10. 进程、线程

(1)多进程
  • 获取父进程的ID: os.getppid()
  • 获取当前进程的ID:os.getpid()
  • 普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。
  • 子进程永远返回0,而父进程返回子进程的ID。这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID。
    import os
    
    print('Process (%s) start...' % os.getpid())
    # Only works on Unix/Linux/Mac:
    pid = os.fork()
    if pid == 0:
    	print('I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid()))
    else:
    	print('I (%s) just created a child process (%s).' % (os.getpid(), pid))
    

运行结果如下:
Process (876) start... I (876) just created a child process (877). I am child process (877) and my parent is 876.

  • multiprocessing

    • multiprocessing模块提供了一个Process类来代表一个进程对象
      from multiprocessing import Process
      import os
      
      # 子进程要执行的代码
      def run_proc(name):
      	print('Run child process %s (%s)...' % (name, os.getpid()))
      
      if __name__=='__main__':
      	print('Parent process %s.' % os.getpid())
      	p = Process(target=run_proc, args=('test',))
      	print('Child process will start.')
      	p.start()
      	p.join()
      	print('Child process end.')
      

    执行结果如下:

    	Parent process 928.
    	Child process will start.
    	Run child process test (929)...
    	Process end.
    
    • 创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动,这样创建进程比fork()还要简单。
    • join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。
  • Pool 进程池

    • 如果要启动大量的子进程,可用进程池的方式批量创建子进程
		from multiprocessing import Pool
		import os, time, random

		def long_time_task(name):
			print('Run task %s (%s)...' % (name, os.getpid()))
			start = time.time()
			time.sleep(random.random() * 3)
			end = time.time()
			print('Task %s runs %0.2f seconds.' % (name, (end - start)))

		if __name__=='__main__':
			print('Parent process %s.' % os.getpid())
			p = Pool(4)
			for i in range(5):
				p.apply_async(long_time_task, args=(i,))
			print('Waiting for all subprocesses done...')
			p.close()
			p.join()
			print('All subprocesses done.')

执行结果如下:
Parent process 669. Waiting for all subprocesses done... Run task 0 (671)... Run task 1 (672)... Run task 2 (673)... Run task 3 (674)... Task 2 runs 0.14 seconds. Run task 4 (673)... Task 1 runs 0.27 seconds. Task 3 runs 0.86 seconds. Task 0 runs 1.41 seconds. Task 4 runs 1.91 seconds. All subprocesses done.
- 对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了。
- 请注意输出的结果,task 0123是立刻执行的,而task 4要等待前面某个task完成后才执行,这是因为Pool的默认大小在我的电脑上是4,因此,最多同时执行4个进程。这是Pool有意设计的限制,并不是操作系统的限制。如果改成:
p = Pool(5)
就可以同时跑5个进程。

	(看到多进程的子进程)
(2)多线程
(3)ThreadLocal

11. 正则表达式

import re
re.match(正则表达式, 待匹配的字符串) # 如果匹配成功,则返回一个Match对象,否则返回None

(1)基础:
  • \d 匹配一个数字
  • \w 匹配一个字母或数字
  • \s 匹配一个空格
  • . 匹配任意一个字符
  • * 匹配任意个字符**(包括0个)**
  • + 匹配至少1个字符
  • ? 匹配0个或1个字符
  • {n} 匹配n个字符
  • {n,m} 匹配n-m个字符
(2)进阶:
  • [ ] 表示范围:
    • [0-9a-zA-Z\_] 匹配一个数字、字母或者下划线
    • [0-9a-zA-Z\_]+ 匹配至少一个数字、字母或者下划线
  • (A|B): A或者B:
    • (P|p)ython 匹配Python或者python
  • ^ 表示行的开头
    • ^\d 必须以数字开头
  • $ 表示行的结束
    • \d$ 必须以数字结尾
  • py 可以匹配到python,如果要精确匹配到py,则要用^py$
  • 强烈建议使用Python的r前缀,就不用考虑转义的问题了:
    • s = r'ABC\-001' #对应正则表达式 'ABC\-001'
(3)切分字符串
  • 'a b c'.split(' ') # ['a', 'b', ' ', ' ', 'c'] 没办法去除空格
  • re.split(r'\s+', 'a b c') # ['a', 'b', 'c'] 去除所有空格
  • re.split(r'[\s\,\;]+', 'a,b;;c d') # 以空格 逗号 分号分割
(4)分组
  • () 用括号提取分组:
    • m = re.match(r'^(\d{3})-(\d{3,8})$', '010-12345')
    • m.group(0) # 原始字符串 010-12345
    • m.group(1) # 提取第一个括号中的内容——区号 010
    • m.group(2) # 提取第二个括号中的内容—— 12345
    • m.groups() # 返回所有组组成的tuple—— ('010', '12345'))
(5)贪婪匹配
  • 默认为贪婪匹配
  • 末尾加?,改为非贪婪匹配
(6)预编译
  • 如果一个正则表达式要重复使用几千次,可以预编译该表达式,提高效率:
    	import re
    	re_telephone = re.compile(r'^(\d{3})-(\d{3,8})$')
    	re_telephone.match('010-12345').groups()
    	re_telephone.match('010-8086').groups()
    
    
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值