Python3 语法总结

Python 基础

数据类型和变量

  • 整数: 1, -1, 0; 十六进制: 0xab
  • 浮点数: 1.23, -1.23, 1.23e9, 12.3e8, 1.2e-5
  • 字符串: '', "", r''(不转义), '''...'''(表示多行内容), r'''...'''
  • 布尔值: True, False(运算符 and, or, not)
  • 空值:None
  • 变量:变量名必须是大小写英文、数字和_的组合,且不能用数字开头
  • 常量: 通常用全部大写的变量名表示常量(Python没有任何机制保证常量不会被改变)
  • /精确触发, //地板除, %取余数

字符串和编码

字符编码

ASCII(单字节), Unicode(通常两个字节,复杂的4字节), UTF-8(1~6个字节, 中文3个字节)。
在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。

python字符串

  • 在现在的python3 版本中, 字符串以unicode编码,所以python字符串支持多语言。
  • ord(‘A’), chr(65) 实现单字符编码的整数和字符的转换。
  • '\u4e2d\u6587' 表示 '中文', 4e2d 是'中'字符整数编码的16进制表示。
  • ‘ABC’: str类型,以unicode编码,通常一个字符对应两个字节;
    b’ABC’: bytes类型, 每个字符只占用一个字节(网络传输或保存磁盘需要先变成bytes)。
  • encode()将unicode表示的str转换成指定编码的bytes。例如:
    ‘ABC’.encode(‘ascii’) 结果为 b’ABC’
    ‘中文’.encode(‘utf-8’) 结果为 b’\xe4\xb8\xad\xe6\x96\x87’
  • decode()将bytes 转变成 str,需指定bytes的编码。例如:
    b’ABC’.decode(‘ascii’) ===> ‘ABC’ b’\xe4\xb8\xad\xe6\x96\x87’.decode(‘utf-8’) ===> ‘中文’ b’\xe4\xb8\xad\xff’.decode(‘utf-8’, errors=’ignore’) ; ignore 忽略错误字节, 否则报错。
  • len(str) 计算字符数, len(bytes)计算字节数
  • 使用UTF-8 without BOM对python源码编码,并且在开头加上# -*- coding: utf-8 -*-

格式化

  • 与C语言一致,使用%
  • 使用字符串的format()方法。它会用传入的参数依次替换字符串内的占位符{0}、{1}…
    'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125)
    ‘Hello, 小明, 成绩提升了 17.1%’

list

python内置数据类型: 可变的有序列表

例如:
example = [‘a’, ‘b’, ‘c’]

  • len(list) 获取元素个数
  • list[0], list[-1] 索引访问元素
  • list.appen(‘var’) 追加元素到末尾
  • list.insert(index, ‘var’) 插入元素到指定位置
  • list.pop() 删除末尾的元素
  • list.pop(i) 删除指定位置元素
  • list[index] = ‘var’ 替换元素值
  • list 元素类型可以不同
  • list 可以包含list

tuple

python内置数据类型: 不可变有序列表—元组。

tuple 一旦初始化就不可改变(指向不变),其他获取元素方法与list相同。
只有一个元素的tuple定义时必须加一个逗号:
t = (1,)

条件判断

if  condition1:
	pass
elif condition2:
	pass
else:
	pass

循环

  • break 跳出循环
  • continue 跳过当前迭代
#for ... in 循环
for x in xrange(1,10):
	pass

#while 循环
while condition:
	pass

dict

python内置数据类型: dict, 存储键值(key-value)对。

dict根据key来计算value的存储位置, 因此key必须是不可变对象。(list不能作为key)。

#初始化一个dict  
`d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}`
#设置或者更改键值
`d['Adam'] = 67`
#访问不存在的key,会报错
`d['abc']`
#使用`in`判断key是否存在  
'abc' in d # False
#使用get,如果key不存在,可以返回None,或者自己指定的value
d.get('abc')
d.get('abc', -1)
#删除一个key
d.pop('key')

set

python内置数据类型: 无序无重复元素的集合。

同样的,set中key必须是不可变对象。

#创建一个set,需要提供一个list作为输入集合  
s = set([a,b,c])
#添加元素到set
s.add(c) 
#删除元素
s.remove(c) 
#集合交集、并集操作
s1 & s2
s1 | s2

函数

调用函数

  • 需要知道函数名称和参数,如果参数数量或者参数类型不对,报TypeError
  • 数据类型转换函数: int(), float(), str(), bool()

定义函数

#如果没有return,返回None
def func_name(x):
	pass #占位符,什么也不干,目的是通过语法检查。

#参数检查
isinstance(x, (int, float))

函数返回多个值其实是返回一个tuple,多个变量可以同时接收一个tuple。

函数参数

位置参数

def power(x, n):
	pass

power(5,2)

x, n 就是位置参数,按照位置顺序依次传递的参数。

默认参数

def power(x, n=2):
	pass

power(5)

必选参数在前,默认参数在后。默认参数可以降低函数调用难度。
默认参数在函数定义的时候就被计算出来了,如果在调用中改变了默认参数,默认参数的内容就变了。因此定义默认参数要牢记一点:默认参数必须指向不变对象

可变参数

def calc(*numbers):
	pass

calc(1,2)
calc()

nums = [1,2,3]
calc(*nums)

在函数内部参数numbers接收到的是一个tuple。
*numsnums这个list的所有元素作为可变参数传进去。(tuple也一样)

关键字参数

可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。关键字参数可以扩展函数功能。

def person(name, age, **kw):
	pass

extra = {'city': 'Beijing', 'job':'Engineer'}
person('Jack',24, city=extra['city'], job=extra['job'])
person('Jack', 24, **extra)

**extraextra 这个dict的所有 key-value 用关键字参数传入到函数的**kw, 参数kw将获得的dictextra的一份拷贝,对kw的改动不会影响到函数外的extra

命名关键字参数

命名关键字参数可可以限制关键字参数的名字。

#需要一个特殊分隔符`*`,否则解释器无法识别位置参数和命名关键字参数
def person(name, age, *, city, job):
	pass

#正确调用方式
person('Jack', 24, city='Beijing', job='Engineer')

#如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要特殊分隔符`*`
def person(name, age, *args, city, job)

#命名关键字参数必须传入参数名,否则将被视为位置参数,导致调用报错
person('Jack', 24, 'Beijing', 'Engineer')

#命名关键字参数可以有缺省值,调用时可不传入
def person(name, age, *, city='Beijing', job):
	pass

person('Jack', 24, job='Engineer')

参数组合

在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数

def f1(a, b, c=0, *args, **kw):
	pass
def f2(a, b, c=0, *, d, **kw):
	pass

#通过一个tuple和dict调用函数
args = (1, 2, 3, 4)
kw = {'d': 99, 'x': '#'}
f1(*args, **kw)
a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}

args = (1, 2, 3)
kw = {'d': 88, 'x': '#'}
f2(*args, **kw)
a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}

对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的

递归函数

函数在内部调用自己叫递归函数。

def fact(n):
	if n == 1:
		return 1
	return n * fact(n-1)

使用递归函数需要注意防止栈溢出。解决递归调用栈溢出的方法是通过尾递归优化。 尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式
这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。改成尾递归:

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)

遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化。

高级特性

切片

切片用于取一个list或tuple的部分元素。

L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']
L[0:3] #表示从索引0开始,直到索引3为止,不含索引3。
['Michael', 'Sarah', 'Tracy']
#如果第一个索引是0,可以省略: 
L[:3]
L[-2:]
['Bob', 'Jack']
L[-2:-1]
['Bob']

L=list(range(100))
L[:10:2] # 前10个数,每两个取一个
L[::5] # 所有数,每5个取一个
L[:] # 复制一个list

tuple和字符串也可以使用切片操作,结果仍然是tuple和字符串。

迭代

  • python使用 for ... in 循环对可迭代对象进行迭代。
  • dict默认对key进行迭代,for key in d
    dist 迭代value: for value in d.values()
    同时迭代key,value: fro k, v in d.items()
  • 判断一个对象是不是可迭代对象:
    from collections import Iterable
    isinstance('abc', Iterable)
  • enumerate把list变成索引元素对
    for i, value in enumerate(['A', 'B', 'C'])

列表生成式

python 内置用来创建list的生成式

#要生成的元素放前面,后跟for循环
>>>[x * x for x in range(1,11)]
[1,4,9,16,25,36,49,64,81,100]

#for循环后可以加if判断
>>>[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']

生成器(generator)

一边循环一边计算的机制,称为生成器。

  • 把列表生成式的[]改成(), next()函数获得generator的下一个返回值。
  • 用包含yield关键字的函数定义generator。在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
  • 捕获StopIteration错误,获取generator返回值(包含在value中,参考代码)。
g = (x * x for x in range(10))

#next(g),没有更多元素时,抛出StopIteration错误。一般使用for循环迭代生成器
for n in g:
	print(n)

def fib(max):
	n,a,b = 0,0,1
	while n<max:
		yield b
		a, b = b, a+ b
		n =n+1
	return 'done'

for n in fib(6):
	print(n)

>>> g = fib(6)
while True:
	try:
		x = next(g)
		print('g:', x)
	except StopIteration as e:
		print('Generator return value: ', e.value)
		break

迭代器

  • 凡是可作用于for循环的对象都是Iterable类型
  • 凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列。
  • list,tuple,dict,set,str是Iterable, 通过iter()函数变成Iterator + isinstance(g, Iterator)判断g是不是一个Iterator

函数式编程

高阶函数

变量可以指向函数, 函数名是指向函数的变量。一个函数接收另一个函数作为参数,就叫高阶函数。

map/reduce

  • map() 接收funcIterable, 将func应用到Iterable中的每个元素,返回Iterator
    list() 函数可以把Iterator整个序列都计算出来,返回一个list
  • reduce() 把一个函数作用在一个序列上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算:
    reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
def f(x):
	return x*x

r = map(f, [1,2,3,4])
list(r)
[1,4,9,16]

def add(x,y):
	return x+y

reduce(add, [1,2,3,4])
10

filter

filter()函数用于过滤序列,接收一个函数和一个序列,把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素, 返回一个Iterator

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

list(filter(is_odd, [1,2,3,4,5,6,7,8]))
[1,3,5,7]

sorted

  • sorted() 函数可以对list直接进行排序
  • sorted() 函数可以接收一个key函数,key函数作用于list的每一个元素,并对key函数返回的结果进行排序,最后按照对应关系返回list相应的元素。
  • 反向排序,传入第三个参数 reverse=True
sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]

sorted([36, 5, -12, 9, -21], key=abs)
[5,9,-12,-21,36]

sorted([36, 5, -12, 9, -21], key=abs, reverse=True)
[36,-21,-12,9,5]

返回函数

闭包:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

def lazy_sum(*args):
	def sum():
		ax = 0
		for n in args:
			ax = ax + n
		return ax
	return sum

匿名函数

关键字lambda表示匿名函数,冒号前面表示函数参数,只能有一个表达式。可以把匿名函数赋值给变量。

f = lambda x: x*x

装饰器

装饰器的本质是一个返回函数的高阶函数,用于在代码运行期间动态增加功能。

import functools

def log(func):
	@functools.wraps(func) # 复制func的属性到wrapper
	def wrapper(*args, **kw):
		print('call %s():' % func.__name__)
		return func(*args, **kw)
	return wrapper

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放到now()函数的定义处,相当于执行了语句: now = log(now)
@log
def now():
	print('2015-01-01')

# 相当于 now = log('execute')(now)
@log('execute')
def now():
	print('2015-01-01')

偏函数

  • functools.partial 把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数。
  • 创建偏函数时,实际上可以接收 函数对象*args**kw 这3个参数 。

例如:
int2 = functools.partial(int, base=2)
max2 = functools.partial(max, 10)

模块

  • 一个.py文件就称之为一个模块(Module)
  • 按目录组织模块的方法称为包(Package)
  • 每个包目录下面都有一个__init__.py,这个文件必须存在,否则,Python把这个目录当成普通目录,而不是一个包。__init__.py可以是空文件,也可以有Python代码,因为__init__.py本身就是一个模块,而它的模块名就是包目录名, 多层级包目录使用.引用。
  • 标识符名称不要与内置函数冲突,模块名不要与内置模块名冲突。

使用模块

  • 任何模块代码的第一个字符串都被视为模块的文档注释
  • 使用模块前先导入,使用import导入模块
  • 在命令行运行模块文件,解释器设置 __name____main__,而如果模块被导入,则不设置。
  • 正常的函数和变量名是公开的(public),可以被直接引用
  • 类似__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途,自己的变量一般不要用这种变量名
  • 类似_xxx__xxx这样的函数或变量就是非公开的(private),不应该 被直接引用

Python并没有一种方法可以完全限制访问private函数或变量

安装第三方模块

  • 使用包管理工具 pip 安装第三方模块
  • 安装常用模块 Anaconda
  • 解释器会搜索当前目录、所有已安装的内置模块第三方模块,搜索路径存放在sys模块的path变量中
  • 添加自己的搜索目录:
    • 一是直接修改sys.path,添加要搜索的目录
    • 设置环境变量PYTHONPATH,该环境变量的内容会被自动添加到模块搜索路径中。

面向对象编程

类和实例

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))

bart = Student('Bart Simpson', 59)

Python允许对实例变量绑定任何数据

访问限制

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))

    def get_score(self):
        return self.__score

    #可以对参数做检查,避免传入无效的参数
    def set_score(self, score):
        if 0 <= score <= 100:
            self.__score = score
        else:
            raise ValueError('bad score')

以一个下划线开头的实例变量名_xxx外部是可以访问的,但一般视为私有变量。
双下划线开头的实例变量__xxx不能直接访问__xxx,因为解释器对外把__变量改成了_Class__xxx
不同版本的Python解释器可能会把__xxx改成不同的变量名。

继承和多态

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

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

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

def run_twice(animal):
    animal.run()
    animal.run()

run_twice(Animal())
Animal is running...
Animal is running...

run_twice(Dog())
Dog is running...
Dog is running.

#使用isinstance()判断一个对象的类型
a = list()
b = Animal()
c = Dog()

isinstance(a,list) # True
isinstance(b, Animal) # True
isinstance(c,Dog) # True
isinstance(c,Animal) # True
isinstance(b,Dog) # False 子类可以转变成父类, 父类不能变成子类(数据丢失)。

#动态语言的鸭子类型, 并不要求严格的继承体系,
#一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
class Timer(object):
    def run(self):
        print('Start...')

run_twice(Timer())
Start...
Start...

获取对象信息

使用type()

使用type()返回对应的Class类型。基本类型和指向函数或者类的变量可以使用type判断。

a = Animal()
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'>

#类型判断
type(123)==type(456) #True
#判断基本数据类型可以直接写int,str等
type(123)==int #True
type('abc')==type(123) # False

#使用types模块中定义的常量判断一个对象是否是函数
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的继承关系来说,使用type()就很不方便,应该使用isinstance()函数,见前面小节。

使用dir()

dir()获得一个对象的所有属性和方法,返回字符串list。

dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']

class MyObject(object):
    def __init__(self):
        self.x = 9
    def power(self):
         return self.x * self.x

#hasattr(), getattr(), setattr()
obj = MyObject()
hasattr(obj, 'x') #True
hasattr(obj, 'y') #False
setattr(obj, 'y', 19)
hasattr(obj, 'y') #True

#如果试图获取不存在的属性会抛出AttributeError的错误
getattr(obj, 'z')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'MyObject' object has no attribute 'z'
#传入一个默认参数,如果属性不存在,返回默认值
getattr(obj, 'z', 404) # 404
#也可以获得对象的方法
fn = getattr(obj, 'power')

实例属性和类属性

#实例属性通过self绑定,或者实例变量自身
class Student(object):
    def __init__(self, name):
        self.name = name

s = Student('Bob')
s.score = 90

#类属性直接在Class中定义,可以被所有实例访问
class Student(object):
    name = 'Student'

s = Student()
print(s.name) #Student
print(Student.name)# Student
#因为相同名称的实例属性将屏蔽掉类属性。
s.name='Mac'
print(s.name) # Mac
del s.name # 删除属性
print(s.name) # Student

面向对象高级编程

使用__slots__

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

#给实例绑定一个方法
from types import MethodType

class Student(object):
	pass

s = Student()

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

s.set_age = MethodType(set_age,s)
#给class绑定一个方法
def set_score(self, score):
	self.score = score

Student.set_score = set_score

class Student(object):
	__slots__ = ('name', 'age')

s = Student()
s.age = 25
s.score = 99 ## AttributeError错误

使用@property

@property 把一个方法变成属性调用。把一个getter方法(func)变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@func.setter,负责把一个setter方法变成属性赋值。只定义getter方法,不定义setter方法就是一个只读属性。

class Student(object):

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

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

	#只读属性
	@property
	def age(self):
		return self._age
	

s = Student()
s.score = 60 # 调用s.set_score(60)
s.core # s.get_socre

多重继承

使用MixIn给一个类增加多个功能

class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
	pass

#编写一个多进程模式的TCP服务
class MyTCPServer(TCPServer, ForkingMixIn):
	pass

#编写一个多线程模式的UDP服务
class MyUDPServer(UDPServer, ThreadingMixIn):
	pass

#协程模型
class MyTCPServer(TCPServer, CoroutineMixIn):
	pass

定制类

__str__

打印实例,定义__str__方法, __repr__用于调试服务

class Student(object):
	def __init__(self, name):
		self.name = name
	def __str__(self):
		return 'Student object (name=%s)' % self.name
	__repr__ =  __str__

s = Student('Mark')
print(s) # 调用 __str__
s        # 直接显示变量 调用 __repr__

__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)

__getitem__

按索引访问元素,定义__getitem. 此外还有 __setitem__()把对象视作list 或dict来对集合赋值, __delitem__()用于删除某个元素

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] # 1
f[0:5] # [1,1,2,3,5]
f[:10]#[1,1,2,3,5,8,13,21,34,55,89]

__getattr__

__getattr__ 动态的返回一个属性, 可以是方法。只有在没有找到属性的情况下,才调用__getattr__。(不会注册属性,也就是说一个不存在的属性,每次获取都会调用__getattr__)

class Student(object):

	def __init__(self):
		self.name = 'mac'
	def __getattr__(self,attr):
		if attr=='score': # 返回属性
			return 99
		if attr=='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() # My name is Michael

callable(Student()) # True

__call__()还可以定义参数。对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看成函数,把函数看成对象,因为这两者之间本来就没啥根本的区别。使用callable判断一个对象是否能被调用。

使用枚举类

from enum import Enum, unique

Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 
						'Jul', 'Aug', 'Sep','Oct', 'Nov', 'Dec'))

#value属性是自动赋给成员的int常量,默认从1开始计数
for name, member in Month.__members__.items():
    print(name, '=>', member, ',', member.value) # Jan => Month.Jan, 1

#从Enum派生出自定义类
#@unique装饰器可以帮助我们检查保证没有重复值。
@unique
class Weekday(Enum):
    Sun = 0 # Sun的value被设定为0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6

#既可以用成员名称引用枚举常量,又可以直接根据value的值获得枚举常量。
print(Weekday.Tue) #Weekday.Tue
print(Weekday['Tue'])#Weekday.Tue
print(Weekday.Tue.value) # 2
print(Weekday(1)) # Weekday.Mon

使用元类

type()

type() 函数既可以返回一个对象的类型,又可以创建出新的类型。
要创建一个class对象,type()函数依次传入3个参数:

  • class的名称;
  • 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
  • class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。
def fn(self, name='world'): # 先定义函数
    print('Hello, %s.' % name)

Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class

metaclass

metaclass 元类,可以看作是模板。先定义metaclass,就可以创建类,最后创建实例。
具体应用参考orm的例子。

# metaclass是类的模板,所以必须从`type`类型派生:
class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)

#定义类的时候使用ListMetaclass来定制类,传入关键字参数metaclass
class MyList(list, metaclass=ListMetaclass):
    pass

L = MyList()
L.add(1)
L.add(2)
L            # [1, 2]

__new__()方法接收到的参数依次是:

  • 当前准备创建的类的对象;
  • 类的名字;
  • 类继承的父类集合;
  • 类的方法集合。

当用户定义一个类时,Python解释器首先在当前类的定义中查找metaclass,如果没有找到,就继续在父类中查找metaclass,找到了,就使用父类中定义的metaclass来创建类。即metaclass可以隐式地继承到子类。

错误调试和测试

错误处理

try

try:
	print('try...')
	r = 10 / int('2')
	print('result:', r)
except ValueError as e: # 捕获所有指定类型及其子类型的错误
	print('ValueError:',e)
except ZeroDivisionError as e:#可以有多个except子句
	print('ZeroDivisionError:' ,e)
else: # 没有错误发生时执行
	print('no error!')
finally: # 可以没有finally语句, 如果有,一定会执行
	print('finally...')
print('END')

调用栈

如果错误没有被捕获,它就会一直往上抛,最后被Python解释器捕获,打印一个错误信息,然后程序退出。

# err.py:
def foo(s):
    return 10 / int(s)

def bar(s):
    return foo(s) * 2

def main():
    bar('0')

main()

#执行 err.py
$ python3 err.py
Traceback (most recent call last):
  File "err.py", line 11, in <module>
    main()
  File "err.py", line 9, in main
    bar('0')
  File "err.py", line 6, in bar
    return foo(s) * 2
  File "err.py", line 3, in foo
    return 10 / int(s)
ZeroDivisionError: division by zero

记录错误

把错误堆栈打印出来,然后分析错误原因,同时,让程序继续执行下去

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')

抛出错误

  • 因为错误是class,捕获一个错误就是捕获到该class的一个实例。
  • 尽量使用Python内置的错误类型,只有在必要的时候才定义我们自己的错误类型。
  • raise语句如果不带参数,就会把当前错误原样抛出。
  • raise可以抛出与当前错误类型不同的错误。
# err_raise.py
class FooError(ValueError):
    pass

def foo(s):
    n = int(s)
    if n==0:
        raise FooError('invalid value: %s' % s)
    return 10 / n

foo('0')

调试

断言

如果断言失败,assert语句本身就会抛出AssertionError
启动Python解释器时可以用-O参数来关闭assert

def foo(s):
    n = int(s)
    assert n != 0, 'n is zero!'
    return 10 / n

def main():
    foo('0')

##断言失败
$ python err.py
Traceback (most recent call last):
  ...
AssertionError: n is zero!

logging

logging,允许指定记录信息的级别, 比如debug,info,warning,error
通过简单的配置,可以输出到不同的地方。

pdb

与gdb使用方式相同, 以 -m pdb启动单步调试。如:python -m pdb err.py
在程序中需要设置断点的地方插入一条语句pdb.set_trace(), 需要导入模块import pdb

IO编程

文件读写

#如果文件不存在,抛出IOError 错误
#读取‘UTF-8’编码的文本文件
f = open('path', 'r') 
#读取二进制文件
f = open('path', 'rb')
#读取非UTF-8编码文本文件,传入encoding参数
f = open('gbk.txt','r', encoding='gbk')
#处理编码错误
f = open('gbk.txt', 'r', encoding='gbk', errors='ignore')

#read()一次读取文件全部内容,用str表示
f.read() # 'Hello, world!'
#最多读取指定size个字节内容
f.read(size)
#每次读取一行
f.readline()
#一次读取所有内容,按行返回list
f.readlines()

#写文件,以'w','wb', 'a'模式打开文件
f = open('path', 'w')
#写入指定编码的文本文件,open添加encoding参数。

f.write('Hello, world!')
# 使用完关闭文件
f.close()

#try ... finally 捕获读写错误,关闭文件
try:
	f = open('path', 'r')
	print(f.read())
finally:
	if f:
		f.close()
#with 语句自动调用close, 与try ... finally 等效
with open('path', 'r') as f:
	print(f.read())

StringIO和BytesIO

StringIO

在内存中读写str

from io import StringIO
#先创建一个StringIO,像文件一样读写
f = StringIO()
f.write('hello') # 5
f.wite(' ') # 1
f.write('world!') # 6
print(f.getvalue())
hello world!

f  = StringIO('Hello!\nHi!\nGoodbye!')
while True:
	s.f.readline
	if s == '':
		break
	print(s.strip())

BytesIO

在内存中操作二进制数据

from io import BytesIO
f = BytesIO()
f.write('中文'.encode('utf-8')) #6
print(f.getvalue())
b'\xe4\xb8\xad\xe6\x96\x87'

f = BytesIO(b'\xe4\xb8\xad\xe6\x96\x87')
f.read()
b'\xe4\xb8\xad\xe6\x96\x87'