跟学廖雪峰python教程新学新知
廖雪峰python教程新学新知
python基础
-
r''
表示''
内部的字符串默认不转义。 -
ord()
函数获取字符的整数表示,chr()
函数把编码转换为对应的字符。 -
bytes
类型的数据用带b前缀的单引号或双引号表示:
x = b'ABC'
。 -
encode('编码类型')
方法可以编码为指定的bytes
,decode('编码类型')
方法将bytes
转换为str
。 -
decode('编码类型', errors='ignore')
中,传入errors='ignore'
忽略错误的字节。 -
保存文件时指定编码类型以及增加运行头文件。
#!/usr/bin/env python3 # -*- coding: utf-8 -*-
-
输出格式化有三种方法:
- % 运算符 :同c语言
>>> 'Hello, %s' % 'world' 'Hello, world' >>> 'Hi, %s, you have $%d.' % ('Michael', 1000000) 'Hi, Michael, you have $1000000.'
- format函数 :传入的参数依次替换字符串内的占位符{0}、{1}……
>>> 'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125) 'Hello, 小明, 成绩提升了 17.1%'
- f-string
小明的成绩从去年的72分提升到了今年的85分,请计算小明成绩提升的百分点,并用字符串格式化显示出
'xx.x%'
,只保留小数点后1位:s1 = 72 s2 = 85 r = (s2-s1)/s1 print('%004.1f%%' % r)
-
tuple元素不可变,但tuple中的list可变。
-
模式匹配可匹配复杂语句和列表。
age = 15 match age: case x if x < 10: print(f'< 10 years old: {x}') case 10: print('10 years old.') case 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18: print('11~18 years old.') case 19: print('19 years old.') case _: print('not sure.') args = ['gcc', 'hello.c', 'world.c'] # args = ['clean'] # args = ['gcc'] match args: # 如果仅出现gcc,报错: case ['gcc']: print('gcc: missing source file(s).') # 出现gcc,且至少指定了一个文件: case ['gcc', file1, *files]: print('gcc compile: ' + file1 + ', ' + ', '.join(files)) # 仅出现clean: case ['clean']: print('clean') case _: print('invalid command.')
-
dict创建有三种方法:
d = {'a': 6,}
,d = dict(a=6)
,d={} d['a']=6
。 -
.get()
函数,第一个参数是key
,第二个参数是key无效时返回的value
,默认是None
。 -
set创建:
s = set([1,2,3,])
。 -
set.add(), set.remove(), s1&s2, s1|s2
,set是数学中的集合。 -
eval
函数将字符串转为python语句然后执行转化后的语句。 -
-17//4
的值时-5
。
函数
-
hex()函数转换16进制。
-
函数名是一个指向函数对象的引用,可以把函数赋给一个变量。
-
函数返回值是一个单一值,当有多个值返回时,实际上是返回一个tuple。
-
默认参数必须指向不变对象。
def add_end(L=[]): L.append('END') return L >>> add_end([1, 2, 3]) [1, 2, 3, 'END'] >>> add_end() ['END'] >>> add_end() ['END', 'END']
修改后
def add_end(L=None): if L is None: L = [] L.append('END') return L >>> add_end() ['END'] >>> add_end() ['END']
-
可变参数
*argv
,可传入数量是任意的,在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去。 -
关键字参数
**kw
,传入的是dict内容,**dict
可以将dict中内容传入函数,同时**kw
获得的参数则是**dict
的一份拷贝。 -
命名关键词参数特殊分隔符
*
,*
后面的参数被视为命名关键字参数。其作用是限制关键字参数。def person(name, age, *, city, job): print(name, age, city, job)
如果函数定义中已经有了一个可变参数,后面的命名关键字参数就不再需要一个特殊分隔符
*
了。def person(name, age, *args, city, job): print(name, age, args, city, job)
-
定义函数可以组合使用这5种参数:必选参数、默认参数、可变参数、关键字参数和命名关键字参数。
参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。 -
对于任意函数,都可以通过类似
func(*args, **kw)
的形式调用它,无论它的参数是如何定义的。 -
对于递归来说,不需要展开思考的它的中间过程,这违背了递归的初心。
按照递归的思路,只需要考虑n和n-1的关系就好,至于n-2,n-3...完全没必要考虑。
高级特性
-
代码越少,开发效率越高。
-
enumerate
函数可以把一个list变成 索引-元素 对。 -
在一个列表生成式中,
for
前面的if ... else
是表达式(所以必须有else),而for
后面的if
是过滤条件,不能带else
。>>> [x for x in range(1, 11) if x % 2 == 0] [2, 4, 6, 8, 10] >>> [x if x % 2 == 0 else -x for x in range(1, 11)] [-1, 2, -3, 4, -5, 6, -7, 8, -9, 10]
-
创建一个generator有两种办法:
-
只要把一个列表生成式的[]改成(),就创建了一个generator。
>>> L = [x * x for x in range(10)] >>> L [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] >>> g = (x * x for x in range(10)) >>> g <generator object <genexpr> at 0x1022ef630>
-
如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator函数,调用一个generator函数将返回一个generator。
def fib(max): n, a, b = 0, 0, 1 while n < max: yield b a, b = b, a + b n = n + 1 return 'done' >>> f = fib(6) >>> f <generator object fib at 0x104feaaa0>
-
-
generator函数和普通函数的执行流程不一样。
普通函数是顺序执行,遇到return语句或者最后一行函数语句就返回。
而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。def odd(): print('step 1') yield 1 print('step 2') yield(3) print('step 3') yield(5) >>> o = odd() >>> next(o) step 1 1 >>> next(o) step 2 3 >>> next(o) step 3 5 >>> next(o) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
-
调用generator函数会创建一个generator对象,多次调用generator函数会创建多个相互独立的generator。
接上 >>> next(odd()) step 1 1 >>> next(odd()) step 1 1 >>> next(odd()) step 1 1
-
解杨辉三角,有三种解法(至少会一种,且理解第一种解法):
def triangles(): L = [1] while True: yield L L = [sum(i) for i in zip([0]+L, L+[0])] def triangles(): ret = [1] while True: yield ret for i in range(1, len(ret)): ret[i] = pre[i] + pre[i - 1] ret.append(1) pre = ret[:] def YangHui (num = 10): LL = [[1]] for i in range(1,num): LL.append([(0 if j== 0 else LL[i-1][j-1])+ (0 if j ==len(LL[i-1]) else LL[i-1][j]) for j in range(i+1)]) return LL
-
通函数调用直接返回结果:
>>> r = abs(6) >>> r 6
generator函数的调用实际返回一个generator对象:
>>> g = fib(6) >>> g <generator object fib at 0x1022ef948>
一般用for循环来调用generator:
>>> g = (x * x for x in range(10)) >>> for n in g: ... print(n) >>> for n in fib(6): ... print(n)
-
可以直接作用于
for
循环的对象统称为可迭代对象:Iterable
, 包括集合数据类型、generator
可以被next()
函数调用并不断返回下一个值的对象称为迭代器:Iterator
-
可以用
isinstance()
函数判断是否是Iterable
或Iterator
。 -
生成器都是
Iterator
对象,但list、dict、str
虽然是Iterable
,却不是Iterator
。 -
Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator甚至可以表示一个无限大的数据流,如全体自然数。
总结:-
凡是可作用于for循环的对象都是Iterable类型;
-
凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
-
集合数据类型是Iterable但不是Iterator,可以通过iter()函数获得一个Iterator对象。
-
-
zip函数将参数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表,返回值是zip对象。如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同,利用 * 号操作符,可以将元组解压为列表。
函数式编程
-
一个函数可以接收另一个函数作为参数,这种函数称之为高阶函数:
def add(x, y, f): return f(x) + f(y) 推导过程: x = -5 y = 6 f = abs f(x) + f(y) ==> abs(-5) + abs(6) ==> 11 return 11
-
map(f,Iterable)
函数接受两个参数,一个是函数f
,一个是Iterable
。
作用是将函数f
作用到Iterable
中的每个元素上,返回类型是Iterator
。 -
reduce(g,Iterable)
函数接受两个参数,一个是函数g
,一个是Iterable
。
作用是将函数g
作用到Iterable
中的每个元素上并把结果继续和序列的下一个元素做累积计算,返回类型是Iterator
。reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
-
filter(h,Iterable)
函数接受两个参数,一个是函数h
,一个是Iterable
。
作用是将函数f
作用到Iterable
中的每个元素上,然后根据返回值是True还是False决定保留还是丢弃该元素(true保留)。返回类型是Iterator
。h=None
会自动将None解释为函数(非空且非0为True)。 -
filter()
函数应用于求素数的埃氏筛法:def _odd_iter(): n = 1 while True: n = n + 2 yield n def _not_divisible(n): return lambda x: x % n > 0 def primes(): yield 2 it = _odd_iter() # 初始序列 while True: n = next(it) # 返回序列的第一个数 yield n it = filter(_not_divisible(n), it) # 构造新序列
-
sorted()
函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序:>>> sorted([36, 5, -12, 9, -21], key=abs) [5, 9, -12, -21, 36]
-
返回函数,把函数作为一个函数的返回值:
def lazy_sum(*args): def sum(): ax = 0 for n in args: ax = ax + n return ax return sum >>> f = lazy_sum(1, 3, 5, 7, 9) >>> f <function lazy_sum.<locals>.sum at 0x101c6ed90> >>> f() 25
-
调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数。
-
闭包:一个函数的相关参数和变量都保存在返回的函数中,称为闭包结构。
-
返回函数不要引用任何循环变量,或者后续会发生变化的变量:
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。
如果一定要引用循环变量,再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
def count(): def f(j): def g(): return j*j return g fs = [] for i in range(1, 4): fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f() return fs >>> f1, f2, f3 = count() >>> f1() 1 >>> f2() 4 >>> f3() 9
-
函数内部引用并修改全局变量需要
global
引用。
函数内部引用并修改父函数的局部变量需要nonlocal
引用。
使用闭包时,对外层变量赋值前,需要先使用nonlocal声明该变量不是当前函数的局部变量。 -
在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator):
def log(func): def wrapper(*args, **kw): print('call %s():' % func.__name__) return func(*args, **kw) return wrapper @log def now(): print('2015-3-25') >>> now() call now(): 2015-3-25 # 等价于now = log(now)
如果decorator本身需要传入参数,则:
def log(text): def decorator(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') >>> now() execute now(): 2015-3-25 # 等价于now = log('execute')(now)
-
经过
decorator
装饰之后的函数,它们的__name__
已经从原来的'now'
变成了'wrapper'
。
如需还原其__name__,则:'''不带参数''' import functools def log(func): @functools.wraps(func) def wrapper(*args, **kw): print('call %s():' % func.__name__) return func(*args, **kw) return wrapper '''带参数''' 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
-
functools.partial创建一个偏函数,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数:
>>> import functools >>> int2 = functools.partial(int, base=2) >>> int2('1000000') 64 >>> int2('1010101') 85 # 相当于 # kw = { 'base': 2 } # int('10010', **kw)
面向对象编程(OOP, Objcet Oriented Programming)
-
数据封装、继承和多态是面向对象的三大特点。
-
隐藏对象的内部状态,只通过对象的方法进行访问修改,称为数据封装。
-
方法是与实例绑定的函数,可以直接访问实例的数据。
-
实例的变量名如果以
__
开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。 -
变量名类似
__xxx__
的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量。 -
以一个下划线开头的实例变量名,如
_name
,是约定俗成的private变量,无强制限制访问。 -
Python解释器对外把
__xxx
变量改成了_ClassName__xxx
,所以,可以private变量也可访问,但不同版本的Python解释器可能会把__xxx
改成不同的变量名。 -
多态允许你使用相同的接口(方法名)来执行不同类型的操作:
class Animal: def speak(self): pass class Dog(Animal): def speak(self): return "Woof!" class Cat(Animal): def speak(self): return "Meow!" def let_animal_speak(animal): print(animal.speak()) # 创建Dog和Cat对象 dog = Dog() cat = Cat() # 使用相同的接口调用不同对象的方法 let_animal_speak(dog) # 输出: Woof! let_animal_speak(cat) # 输出: Meow!
-
“鸭子类型”:不需要严格的继承体系,只要看起来像鸭子,走起路来像鸭子,就可以被视作鸭子,例如"file-like object",只要有
read()
方法就可以被视为"file-like object":class Animal(object): def run(self): print('Animal is running...') def run_twice(animal): animal.run() animal.run() class Timer(object): def run(self): print('Start...') timer = Timer() # run_time(timer) is true
-
types
模块中有type
类型常量:.FunctionType
: 表示用户定义的函数类型;.MethodType
: 表示类的方法类型;.BuiltinFunctionType
: 表示内建函数类型;.BuiltinMethodType
: 表示内建方法类型;.ModuleType
: 表示模块类型;.LambdaType
: 表示Lambda函数类型;.ClassType
: 在Python 2中表示旧式类类型,但在Python 3中已不存在,因为所有类都是新式类;.GeneratorType
: 表示生成器类型;.GetSetDescriptorType
: 表示获取器/设置器描述符类型;.MemberDescriptorType
: 表示成员描述符类型;.SimpleNamespace
: 表示一个简单的命名空间,类似于一个动态创建的类,可以用来存储任意数量和类型的属性。
-
如果要获得一个对象的所有属性和方法,可以使用
dir()
函数。 -
“开闭”原则:
- 对拓展开放:允许新增子类;
- 对修改封闭:不需要修改依赖父类的内部函数。
-
调用
len()
函数,其内部自动去调用该对象的__len__
方法。 -
getattr()
、setattr()
以及hasattr()
可以直接操作一个对象的状态:>>> class MyObject(object): ... def __init__(self): ... self.x = 9 ... def power(self): ... return self.x * self.x ... >>> obj = MyObject() >>> hasattr(obj, 'x') True >>> setattr(obj, 'y', 19) # 设置一个属性'y' >>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404 >>> getattr(obj, 'power') # 获取属性'power' <bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>> >>> fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn >>> fn # fn指向obj.power <bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>> >>> fn() # 调用fn()与调用obj.power()是一样的 81
-
为了统计学生人数,可以给Student类增加一个类属性,每创建一个实例,该属性自动增加:
class Student(object): count = 0 def __init__(self, name): self.name = name self.count += 1
-
实例会继承类属性,当实例修改继承的属性后,会覆盖继承的属性,但一旦删除修改后的属性,原来继承的属性会自动还原。
面向对象高级编程
-
给实例绑定方法用
instance.method = MethodType(instance, method)
,给类或全体实例绑定方法用class
。 -
Python允许在定义
class
的时候,定义一个特殊的__slots__
变量,来限制该class
实例能添加的属性,一般用tuple
定义允许绑定的属性名称:>>> class Student(object): >>> __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称 >>> s = Student() # 创建新的实例 >>> s.name = 'Michael' # 绑定属性'name' >>> s.age = 25 # 绑定属性'age' >>> s.score = 99 # 绑定属性'score' Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute 'score'
-
__slots__
定义的属性(的限定)仅对当前类实例起作用,对继承的子类无效,在子类也定义__slots__
即可继承限定。 -
在类中定义方法前加
@property
前缀,把一个方法变成属性调用(可读)。
@xxx.getter
和@xxx.setter
前缀,可直接读写xxx
属性,并且property默认自带可读属性:class Student(object): @property def birth(self): return self._birth @birth.setter def birth(self, value): self._birth = value @property def age(self): return 2015 - self._birth
-
@property
的方法名称不要与实例变量/属性重名,负责会导致无限递归栈溢出。 -
一个子类可继承多个父类。
-
MixIn:一种设计模式,主要思想时通过继承多个父类,使得拥有多个功能,即优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。
-
__str__()
可print()打印出自定义内容,而__repr__()
直接返回自定义内容:>>> class Student(object): ... def __init__(self, name): ... self.name = name ... >>> print(Student('Michael')) <__main__.Student object at 0x109afb190> >>> class Student(object): ... def __init__(self, name): ... self.name = name ... def __str__(self): ... return 'Student object (name: %s)' % self.name ... __repr__ = __str__ ... >>> print(Student('Michael')) # __str__的功能 Student object (name: Michael) >>> Student('Michael') # __repr__的功能 Student object (name: Michael)
-
__iter__()
可返回迭代对象、__next__()
可迭代迭代对象,当只定义__iter__
但不定义__next__
将不会使该类被python解释器识别为一个迭代器: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) ... 1 1 2 3 5 ... 46368 75025
-
__getitem__
使得类的实例可以像list一样用下表调用,但如果想用切片、step、处理负数等list本身具有的功能需要在__getitem__
中判断并自己写: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] [1, 1, 2, 3, 5] >>> f[: 10] [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
对应的是
__setitem__()
、__delitem__
方法,将对象视为list或dict对集合复制或删除元素,这是鸭子类型的功劳。 -
__getter__()
动态返回属性:class Student(object): def __init__(self): self.name = 'Michael' >>> s = Student() >>> s.score Traceback (most recent call last): ... AttributeError: 'Student' object has no attribute 'score' class Student(object): def __getattr__(self, attr): if attr == 'score': return 99 >>> s = Student() >>> s.score 99 >>> s.age None # 如果想让class只响应特定的几个属性,只需要抛出AttributeError即可 class Student(object): def __getattr__(self, attr): if attr=='age': return lambda: 25 raise AttributeError(f'\'Student\' object has no attribute \'{attr}\'')
只有在没有找到属性的情况下才会调用
__getattr__
,attr
指的是对象的属性。
这样就可以把一个类的所有属性和方法调用全部动态化处理了,而不需要任何特殊手段。 -
__getattr__()
实现的链式调用:class Chain(object): def __init__(self, path=''): self._path = path def __getattr__(self, path): return Chain('%s/%s' % (self._path, path)) def __str__(self): return self._path __repr__ = __str__ >>> Chain().status.user.timeline.list '/status/user/timeline/list'
-
__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对象,即是否拥有__call__()
方法。 -
enum
库中有Enum
类可以实现枚举:from enum import Enum Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')) for name, member in Month.__members__.items(): print(name, '=>', member, ',', member.value) #输出: Jan => Month.Jan , 1 Feb => Month.Feb , 2 ... Nov => Month.Nov , 11 Dec => Month.Dec , 12
-
可以通过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 # 以下是多种访问方式 >>> day1 = Weekday.Mon >>> print(day1) Weekday.Mon >>> print(Weekday.Tue) Weekday.Tue >>> print(Weekday['Tue']) Weekday.Tue >>> print(Weekday.Tue.value) 2 >>> print(Weekday(1)) Weekday.Mon
-
Enum可以把一组相关常量定义在一个class中,且class不可变,而且成员可以直接比较。
-
动态语言中函数和类是运行时动态创建,而非编译时定义创建。
-
type()
函数可以返回一个对象的类型,又可以创建出新的类型:>>> def fn(self, name='world'): # 先定义函数 ... print('Hello, %s.' % name) ... >>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class,dict中是将函数fn绑定到方法名hello上 >>> h = Hello() >>> h.hello() Hello, world. >>> print(type(Hello)) <class 'type'> >>> print(type(h)) <class '__main__.Hello'>
type传入的三个参数依次是:class名称、继承父类的tuple,方法名与函数绑定的dict。
-
通过type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。
-
metaclass(元类):实例根据类创建,类根据metaclass创建。
-
简易ORM(Object Relational Mapping,对象-关系映射)框架的实现:
'''ORM框架的调用,在这其中Model、StringField等全部由ORM框架提供,save等方法由Model自动完成''' class User(Model): # 定义类的属性到列的映射: id = IntegerField('id') name = StringField('username') email = StringField('email') password = StringField('password') # 创建一个实例 u = User(id=1234, name='Michael', email='test@ww.com', password='pw1') # 保存到数据库 u.save() '''编写ORM框架''' # 定义Field class Field(object): def __init__(self, name, column_type): self.name = naem self.column_type = column_type def __str__(self): return f'<{self.__class__.__name__}:{self.name}>' # 定义各种类型的Field class StringField(Field): def __init__(self, name): super(StringField, self).__init__(name, 'varchar(100)') class IntegerField(Field): def __init__(self, name): super(IntegerField, self).__init__(name, 'bigint') # 编写ModelMetaclass class ModelMetaclass(type): def __new__(cls, name, bases, attrs): if name == 'Model': print(f'Found model: {name}') mappings = dict() for k, v in attrs.items(): if isinstance(v, Field): print(f'Found mapping: {k} ==> {v}') mappings[k] = v for k in mappings.keys(): attrs.pop(k) attrs['__mappings__'] = mappings # 保存属性和列的映射关系 attrs['__table__'] = name # 假设表名和类名一致 return tpe.__new__(cls, name, bases, attrs) class Model(dict, metaclass=ModelMetaclass): def __init__(self, **kw): super().__init__(**kw) def __getattr__(self, key): try: return self[key] except KeyError: raise AttributeError(f"\'Model\' object has no attribute \'{key}\'") def __setter__(self, key, value): self[key] = value def save(self): fields = [] params = [] args = [] for k, v in self.__mappings__.items(): fields.append(v.name) params.append('?') args.append(getattr(self, k, None)) sql = f'insert into {self.__table__} ({','.join(fields)} values ({','.join(params)}))' print(f'SQL: {sql}') print(f'ARGS: {str(args)}')
错误、调试和测试
-
try...except...finally...
:错误在捕获后会立刻进入except,不再继续运行try,可以有多个except...as...:
,并且可以用except:...else:...
。 -
常见的错误类型与继承关系:
-
出错的时候要分析调用栈,从第一句开始分析。
-
可以通过标准库logging模块记录错误信息:
# 设置日志等级和格式(这里是时间戳-级别、实际内容) logging.basicConfig(level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s') try: # 可能会引发异常的代码 num = 10 / 0 except Exception as e: # 使用logging.exception记录异常 logging.exception("An error occurred") # 输出示例: # 2023-04-24 12:34:56,789 - ERROR - An error occurred
-
通过logging.exception记录信息后会继续执行,并正常退出。
-
抛出一个错误就是捕获到该class的一个实例,错误不是凭空产生的,而是有意创建并抛出的,自己编写的函数也可以抛出错误:
class FooError(ValueError): pass def foo(s): n = int(s) if n==0: raise FooError(f'invalid value: {s}') return 10 / n foo('0') ''' 输出: FooError Traceback (most recent call last) Cell In[25], line 10 7 raise FooError(f'invalid value: {s}') 8 return 10 / n ---> 10 foo('0') Cell In[25], line 7, in foo(s) 5 n = int(s) 6 if n==0: ----> 7 raise FooError(f'invalid value: {s}') 8 return 10 / n FooError: invalid value: 0 '''
-
raise语句不带参数会把错误原样抛出,层层上抛,也可以raise成其他符合逻辑的错误,从而将一种类型错误转化成另外一种类型。
-
一般可以用assert(断言)来代替print检查,,并且在运行时可以用-O参数关闭assert:
def foo(s): n = int(s) # 断言n != 0是true,否则抛出'n is zero!' assert n != 0, 'n is zero!' return 10 / n def main(): foo('0') ''' Traceback (most recent call last): ... AssertionError: n is zero! '''
-
可以用logging代替,同时basicConfig可以指定起作用的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 '''
-
可以使python调试器pdb:
-
python -m pdb xxx.py
启动; -
命令
l
可以查看代码; -
命令
n
可以单步执行代码; -
命令
p 变量名
可以查看变量; -
命令
q
可以结束调试,退出程序。 -
代码中
pdb.set_trace()
处是断点(需提前引入pdb库); -
命令
c
继续运行;
-
-
单元测试:对一个模块、一个函数或者一个类进行正确性检验的测试工作。
-
单元测试举例说明:
"""mydict.py""" class Dict(dict): def __init__(self, **kw): super().__init__(**kw) def __getattr__(self, key): try: return self[key] except KeyError: raise AttributeError(r"'Dict' object has no attribute '%s'" % key) def __setattr__(self, key, value): self[key] = value """mydict_test.py""" import unittest from mydict import Dict # 从unittest.TestCase继承测试类 class TestDict(unittest.TestCase): # 以test开头的方法就是测试方法,否则不被认为是测试方法,测试时将不予执行 def test_init(self): d = Dict(a=1, b='test') # 这里是常用的断言assertEqual 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'] ''' 相当于: try: value = d['empty'] except KeyError: pass else: raise AssertionError("Expected a KeyError") 如果d['empty']尝试访问一个不存在的键时引发了一个KeyError异常,with语句将不会执行任何代码。相反,它会继续执行with语句之后的代码。如果d['empty']没有引发KeyError异常,则会抛出一个AssertionError,指出没有预期的异常被引发。 ''' def test_attrerror(self): d = Dict() with self.assertRaises(AttributeError): value = d.empty # 拥有这段代码可以把mydict_test.py当作正常的python脚本运行; if __name__ == '__main__': unittest.main() # 或者可以在命令行通过参数`-m unittest`直接运行单元测试 python -m unittest mydict_test
-
单元测试中还有两个特殊的方法:
setUp()
、tearDown()
,这两个方法分别在每调用一个测试方法的前后分别被执行。 -
python内置文档测试模块,可以直接提取注释中的代码并执行测试:
import doctest doctest.testmod()
IO编程
-
同步IO和异步IO:
- 同步IO:CPU等待,程序暂停执行代码,等待数据全部写入后再往下执行;
- 异步IO:CPU不等待,程序代码可以在数据写入时同步执行。
-
在磁盘上读写文件的功能是由操作系统提供,现代操作系统不允许普通程序直接操作磁盘,读写文件是请求操作系统打开一个文件对象(通常称为文件描述符),然后通过操作系统提供的接口从此文件对象中读取数据(读文件),或者把数据写入这个文件对象(写文件)。
-
读文件使用
open()
函数:''' 参数说明: - file=path-like object,要打开文件的路径或者是封装文件对应的整数类型文件描述符。 - mode=str,文件打开模式,常用r读w写x排他性创建a追加写入b二进制模式t文本模式+打开用于更新。 - buffering=int,设置缓冲策略,0关闭缓冲,1选择缓冲行,>1固定大小的块缓冲区的字节大小。 - encoding=str,选择编码格式。 - errors=str,错误处理模式,'strict'/'ignore'/'replace'... - newline=str,如何解析换行符,None时启用通用换行。 - closefd=Bool,保持文件打开状态,由file的值决定。 - opener,开启器。 ''' # 以下有值为默认参数 open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -> IO stream
-
如果文件不存在
open
函数会抛出IOError
;如果文件存在f.read()
方法可以一次读取文件的全部内容。 -
保证正确地关闭文件的方法:
# method1:手动调用close try: f = open('path', 'r') finally: if f: f.close() # method2:自动调用close with open('path', 'r') as f: print(f.read)
-
文件过大时可以通过
read(size)
指定每次读取大小,或者调用readline()
每次读取一行内容,或者调用readlines()
一次读取所有内容并按行返回list。for line in f.readlines(): print(line.strip()) # 把末尾的'\n'删掉
-
file-like Object是鸭子类型,只要拥有read方法就是,StringIO是在内存中创建的file-like Object用作临时缓冲。
-
写模式时OS会将数据放到内存缓冲起来,所以为了防止丢失数据,需要在写入后
close
文件:with open('path', 'w') as f: f.write('xxx')
-
StringIO是在内存中读写str,BytesIO是在内存中读写bytes:
"""StringIO""" >>> from io import StringIO # 写入StringIO: >>> f = StingIO() >>> f.write('xxx') 3 # 返回输入长度 # 获取写入后的IO >>> print(f.getvalue()) xxx # 读取StringIO >>> f = StringIO('xxx\nxxx') >>> while True: ... s = f.readline() ... if s == '': ... break ... print(s.strip()) xxx xx """BytesIO""" from io import BytesIO # 写入BytesIO,写入的不是str,而是经过utf-8编码的bytes: >>> f = BytesIO() >>> f.write('中文'.encode('utf-8')) 6 # 获取写入后的IO >>> print(f.getvalue()) b'\xe4\xb8\xad\xe6\x96\x87' # 读取StringIO >>> f = BytesIO(b'\xe4\xb8\xad\xe6\x96\x87') >>> f.read() b'\xe4\xb8\xad\xe6\x96\x87'
-
与OS进行交互
import os
:- 获取操作系统类型:
os.name
- 获取详细的系统信息:
os.uname()
- 获取系统环境变量:
os.environ
- 获取环境变量的某个值:
os.environ.get('key')
- 获取操作系统类型:
-
操作文件和目录:
- 查看当前目录绝对路径:
os.path.abspath('.')
- 连接目录和文件名:
os.path.join('dir1', 'dir2/filename', ...) -> dir1/dir2/filename
- 拆分目录和文件名:
os.path.split('dir1/dir2/filename') -> ('dir1/dir2', 'filename')
- 获得文件拓展名:
os.path.splitext(dir.filename) -> 文件拓展名
- 创建目录:
os.mkdir('dirpath')
- 删除目录:
os.rmdir('dirpath')
- 对文件重命名:
os.rename('old_name', 'new_name')
- 删除文件:
os.remove('filename')
- 更多拓展操作例如copy等可以使用
shutil
模块,作为os
模块的补充。
- 查看当前目录绝对路径:
-
利用python特性来过滤文件:
# 列出当前dir下所有目录 >>> [x for x in os.listdir('.') if os.path.isdir(x)] ['.lein', '.local', '.m2', '.npm', '.ssh', '.Trash', '.vim', 'Applications', 'Desktop', ...] # 列出所有的.py文件 >>> [x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py'] ['apis.py', 'config.py', 'models.py', 'pymonitor.py', 'test_db.py', 'urls.py', 'wsgiapp.py']
-
把变量从内存中变成可存储或可运输的过程称之为序列化(pickling),序列化之后就可以把序列化后的内容写入磁盘或网络传输;把变量内容从序列化的对象重新读到内存里称之为反序列化(unpickling)。
-
把对象序列化并写入文件
pickle.dumps()
:>>> import pickle >>> d = dict(age=20,) >>> pickle.dumps(d) # 返回对象序列化的bytes 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.close()
-
将对象反序列化读取
pickle.load()
:>>> import pickle >>> f = open('dump.txt', 'rb') >>> d = pickle.load(f) >>> f.close() >>> d {'age': 20, 'score': 88, 'name': 'Bob'}
-
Python序列化的内容只能用于python。
-
json模块中的
dumps
和loads
可以将对象序列化成标准格式,其编码是utf-8:>>> import josn >>> d =dict(name='Bob', age=20, score=88) >>> json.dumps(d) '{"age": 20, "score": 88, "name": "Bob"}' >>> json_str = '{"age": 20, "score": 88, "name": "Bob"}' >>> json.loads(json_str) {'age': 20, 'score': 88, 'name': 'Bob'}
-
其他类型对象必须先转换为dict才可用json读取或保存:
import json class Student(object): def __init__(self, name, age, score): self.name = name self.age = age self.score = score # 序列化 s = Student('Bob', 20, 88) def student2dict(std): return { 'name': std.name, 'age': std.age, 'score': std.score } >>> json.dumps(s, default=student2dict) {"age": 20, "name": "Bob", "score": 88} >>> json.dumps(s, default=lambda obj: obj.__dict__) # 反序列化 def dict2student(d): return Student(d['name'], d['age'], d['score']) >>> json_str = '{"age": 20, "score": 88, "name": "Bob"}' >>> json.loads(json_str, object_hook=dict2student) <__main__.Student object at 0x10cd3c190>