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。
*nums
把nums
这个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)
**extra
把 extra
这个dict的所有 key-value
用关键字参数传入到函数的**kw
, 参数kw
将获得的dict
是extra
的一份拷贝,对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()
接收func
和Iterable
, 将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'