0%

Python学习笔记

Learn from this

2020.7.31

容器初始化

1
2
3
4
list:[]
tuple:()
dict:{:}
set:{}

函数参数

位置参数

默认参数:默认参数必须指向不变对象!默认参数的值是在函数构建时被定义的。可以单独定义某个默认参数的值:

1
2
3
4
def enroll(a, b, c=1, d=2):
print(a, b, c, d)

enroll(1, 2, d=5) # output: 1 2 1 5

可变参数

可以传入一个tuple来解决

1
2
3
4
5
6
7
8
def calc(numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum

>>> calc((1,2,3)) # 此处要用双层括号
14
1
2
3
4
5
6
7
8
9
10
11
12
13
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum

>>> calc(1, 2)
5
>>> calc()
0
>>> nums = [1, 2, 3]
>>> calc(*nums)
14

*的作用是将tuple的内容拆成一个个参数然后传入。

关键字参数

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

1
2
3
4
5
6
7
8
9
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)

>>> person('Adam', 45, gender='M', job='Engineer')
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}

>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}

命名关键字参数

限制关键字参数的名字。

1
2
3
4
5
def person(name, age, *, city, job):
print(name, age, city, job)

>>> person('Jack', 24, city='Beijing', job='Engineer')
Jack 24 Beijing Engineer

命名关键字参数必须传入参数名;命名关键字参数可以有缺省值。

如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了。

参数组合

参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。

使用*args**kw是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。

切片

s[L:R:D]表示将s的[L,R)间距为D的片段切下来

特别的,s[:2] 表示切s的前两个元素;s[-2:]表示切s的后两个元素。

迭代

1
2
3
4
5
6
7
d = {'a': 1, 'b': 2, 'c': 3}
for key in d:
print(key)
for val in d.values():
print(val)
for it in d.items():
print(it) # tuple

如果要对list实现类似Java那样的下标循环怎么办?Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身:

1
2
3
4
5
6
7
8
9
for i, val in enumerate(['a', 'b', 'c']):
print(i, val)

0 A
1 B
2 C

for x, y in [(1, 1), (2, 4), (3, 9)]:
print(x, y)

列表生成式

感觉很好用!

1
2
3
L = list(range(1, 11))
L = [x * x for x in range(1, 11) if x % 2 == 0]
L = [m + n for m in ['a', 'b', 'c'] for n in ['x', 'y', 'z']]

if,else,for

1
2
>>> [x if x % 2 == 0 else -x for x in range(1, 11)]
[-1, 2, -3, 4, -5, 6, -7, 8, -9, 10]

在一个列表生成式中,for前面的if ... else是表达式,而for后面的if是过滤条件,不能带else

生成器

简单的生成器相当于把list换成tuple。生成器只有next方式调出下一个,不能随机访问。可以用for遍历。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
g = (x * x for x in range(10))
print(next(g))
for x in g:
print(x, end=' ')

0
1 4 9 16 25 36 49 64 81

# 斐波那契数列生成器
def fib():
a, b = 0, 1
while True:
yield b # !!!
a, b = b, a + b


f = fib()
for x in f:
print(x)
input()

变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

generator函数也可以有返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIterationvalue中。

1
2
3
4
5
6
7
def triangles():
A = []
while True:
for i in range(len(A) - 1, 0, -1):
A[i] += A[i - 1]
A.append(1)
yield list(A)

以上是生成杨辉三角的生成器,值得注意的一点是,函数如果直接返回A,那么在函数里A的值改变的话,导出的A的值也会变。

改进版:

1
2
3
4
5
6
def triangles():
A = [1]
yield A
while True:
A = [1] + [A[i] + A[i + 1] for i in range(len(A) - 1)] + [1]
yield A

为什么这样就可以返回A了呢,因为每经过一次赋值操作,A的地址都会重新分配。

8.1

map&reduce

我们先看map。map()函数接收两个参数,一个是函数,一个是Iterablemap将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。

1
2
3
4
5
6
def f(x):
return x * x

>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]

reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:

1
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

比方说对一个序列求和,就可以用reduce实现:

1
2
3
4
5
6
from functools import reduce
def add(x, y):
return x + y

>>> reduce(add, [1, 3, 5, 7, 9])
25

利用此方法可以方便的实现str转int:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def str2int(s):
def fn(x, y):
return x * 10 + y
def char2num(s):
return DIGITS[s]
return reduce(fn, map(char2num, s))

# lambda

def char2num(s):
return DIGITS[s]

def str2int(s):
return reduce(lambda x, y: x * 10 + y, map(char2num, s))

# str2float

def str2float(s):
def merge(x, y):
return x * 10 + y

def str2num(s):
return reduce(merge, map(int, s))

p = 0
while p < len(s) and s[p] != '.':
p += 1
if p == len(s):
return str2num(s)
return str2num(s[:p]) + str2num(s[p + 1:]) * 0.1**(len(s) - p - 1)

# 写的还是太长,等学会lambda和str的函数再简化吧

filter

接收一个函数和一个序列,返回一个Iterator,只保留True的元素。

1
2
3
def is_palindrome(n):
s = str(n)
return s == s[::-1]

筛回文好简单…

sorted

我们给sorted传入key函数,即可实现忽略大小写的排序,相当于把所有元素都应用key之后再排序:

1
2
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']

要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True

1
2
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']

闭包

大杀器。类似于C++中的lamba.

返回一个函数,这个函数会同时包含它所需要的变量。

需要注意的问题:

返回的函数没有立即执行,而是等到调用才执行。 返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:

1
2
3
4
5
6
7
8
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

创建计数器

这道题很妙啊。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def createCounter():
n = 0

def counter():
nonlocal n # 声明这是外层的变量
n += 1
return n

return counter

def createCounter():
n = [0]

def counter():
n[0] += 1 # 为什么这里可以修改?因为n这个变量实际上是指向一片内存地址!
return n[0]

return counter

global&nonlocal

global用于修改全局变量;

nonlocal用于在函数或其他作用域中使用外层(非全局)变量。

匿名函数

关键字lambda表示匿名函数,冒号前面的x表示函数参数,冒号后面的表达式是返回值。

装饰器

函数对象有一个__name__属性,可以拿到函数的名字:

1
2
3
4
5
>>> f=now
>>> now.__name__
'now'
>>> f.__name__
'now'

假设我们要在now函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”。

本质上,decorator就是一个返回函数的高阶函数,可以定义如下:

1
2
3
4
5
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper

我们可以借助Python的@语法,把decorator置于函数的定义处:

1
2
3
@log
def now():
pass

调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志。

@log放到now()函数的定义处,相当于执行了语句:

1
now = log(now)

wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。

decorater本身也可以传入参数:

比如,要自定义log的文本:

1
2
3
4
5
6
7
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator

这个3层嵌套的decorator用法如下:

1
2
3
@log('execute')
def now():
pass

但是,这样装饰之后的 函数,它的__name__已经不是原来的函数了。Python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下:

1
2
3
4
5
6
7
8
import functools

def log(func):
@functools.wraps(func) # !!!
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper

请设计一个decorator,它可作用于任何函数上,并打印该函数的执行时间:

1
2
3
4
5
6
7
8
9
10
def metric(fn):
@functools.wraps(fn)
def warpper(*args, **kw):
s = time.time()
r = fn(*args, **kw)
e = time.time()
print('%s run in %.2f ms' % (fn.__name__, e - s))
return r

return warpper

使用decorator,还可以实现诸如:当遇到错误重试最多5次该函数,再结束。

偏函数

1
int2 = functools.partial(int, base=2)

固定函数的参数。

8.2

模块

1
2
3
4
5
6
7
8
9
10
11
12
13
'A module'  # 任何模块代码的第一个字符串都被视为模块的文档注释,可以用__doc__调用

__author__ = 'i207M' # 标准模板

import sys


def run():
print(sys.argv) # sys模块定义了sys.argv,本质是list,储存了调用这个py文件的参数


if __name__ == '__main__': # 当且仅当它是独立运行而不是被import进别的py时,此判断为True
run()

运行python3 hello.py获得的sys.argv就是['hello.py']

运行python3 hello.py Michael获得的sys.argv就是['hello.py', 'Michael']

作用域

正常函数的命名是公开的。

类似__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途,如__author____name____doc__

类似_xxx__xxx这样的函数或变量就是非公开的,不应该被直接引用。不应该,不是不能。

面向对象OOP

__init__方法相当于构造函数。注意到__init__方法的第一个参数永远是self,表示创建的实例本身。

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,这样在外部就真的无法访问。但是在外部定义class的__开头的变量是可以被访问的。

在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name____score__这样的变量名。

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

这就是著名的**开闭原则 **:

对扩展开放:允许新增Animal子类;

对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。

“鸭子类型”:它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。

print一个class,实质会调用__str__()函数,打印此函数的返回值。直接在控制台输入变量,打印的实例是调用__repr__()

定制类

定义了__iter__(),__next__(),就可以被用于for ... in 循环:

1
2
3
4
5
6
7
8
9
10
11
12
class Fib(object):
def __init__(self):
self._a, self._b = 0, 1

def __iter__(self):
return self

def __next__(self):
self._a, self._b = self._b, self._a + self._b
if self._a > 1000:
raise StopIteration()
return self._a

实现__getitem__()方法,可以按下标访问任意一项。

当调用不存在的属性时,Python解释器会试图调用__getattr__(self, 'name')来尝试获得属性。

利用完全动态的__getattr__,我们可以写出一个链式调用:

1
2
3
4
5
6
7
8
9
10
11
12
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__

试试:

1
2
>>> Chain().status.user.timeline.list
'/status/user/timeline/list'

Niubi

任何类,只需要定义一个__call__()方法,就可以直接对实例进行调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Chain(object):
def __init__(self, path=''):
self.path = path

def __getattr__(self, attr):
return Chain("%s/%s" % (self.path, attr))

def __call__(self, *args):
_self = self
for i in args:
_self = Chain("%s/%s" % (_self.path, i))
return _self

def __str__(self):
return self.path

__repr__ = __str__


>>> print(Chain().a("hello", "good").b)
/a/hello/good/b

type

type返回类型,是严格的类型,同一个父类下的子类互不相同。

使用instance()函数可以判断继承类型。

类和实例

可以定义类属性和实例属性,实例属性优先于类属性。

配合getattr()setattr()以及hasattr(),我们可以直接操作一个对象的状态。

为了统计学生人数,可以给Student类增加一个类属性,每创建一个实例,该属性自动增加:

1
2
3
4
5
6
class Student(object):
count = 0

def __init__(self, name):
self.name = name
Student.count += 1 # 方便!

我们可以给类一个方法,只需要在定义时加上@classmethod

1
2
3
4
5
6
class Dog(object):
@classmethod
def bark(cls):
print('WO-')
# 调用
Dog.bark()

其他方法

__doc__()

__str__()

__repr__()

__lt__()(小于运算符)

__getitem__()(使用obj[key]

__setitem__()

__len__()

__del__()

给实例绑定一个方法

1
2
3
4
5
6
7
8
9
10
>>> def set_age(self, age): # 定义一个函数作为实例方法
... self.age = age
...
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s) # 给实例绑定一个方法,只有s能调用

>>> def set_score(self, score):
... self.score = score
...
>>> Student.set_score = set_score # 所有实例都可以调用

Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:

1
2
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

对子类不起作用,除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__

但是当父类无__slots__,而子类有,则无效。

@property

实现类的时候,我们常常需要提供各种借口,但是为每个接口都写一个函数有些麻烦,使用也不方便,我们可以用@property,使得可以直接调用变量,用等号修改,且进行范围检查。

1
2
3
4
5
6
7
8
9
10
11
class Student(object):
@property
def score(self):
return self.__score

@score.setter
def score(self, val):
if 0 <= val and val <= 100:
self.__score = val
else:
raise ValueError('Score Error')

@property后定义的函数作为此名变量的getter方法;此时,@property本身又创造了另一个修饰器@score.setter,实现setter方法,如果没有setter方法,则变量为只读。

多重继承

RT:

1
2
class Dog(Mammal, Runnable):
pass

如果方法重复怎么办?优先保留顺序靠前的。

需要注意,如果子类定义了__init__()函数,那么初始化时不会调用父类的__init__(),因此需要显式地调用它。

枚举类

继承自Enum。@unique装饰器可以帮助我们检查保证没有重复值。

元类

太 妙 了!豁然开朗!(用到时再复习吧)

super()

8.3

错误处理

感觉这个挺重要的。

1
2
3
4
try:
pass
expect ... as ...:
pass

出错时,直接跳转到except语句块。错误也是class,继承自BaseException

如果错误没有被捕获,会一直往上抛,最后被Python解释器捕获,打印错误信息并退出。这就是调用栈

Python内置的logging模块可以记录错误信息。

1
2
3
4
try:
main()
except Exception as e:
logging.exception(e)

这样程序可以继续运行,并且错误被记录。

except语句内可以继续raise,抛给上一层。

finally: 无论有没有异常,都会执行。优先级极高,甚至在tryreturn,则也会执行finally.

调试

assert+VS Code

文件读写

使用f=open(file,'r'/'rb'/'w'/'a'[,encoding=]),退出程序前记得f.close()

方便的方法是使用with语句:

1
2
with open('/Users/michael/test.txt', 'w') as f:
f.write('Hello, world!')

建议使用with语句,以防忘记close,如果不能直接使用with语句,可以

StringIO&BytesIO

在内存中读写str。像文件一样写入即可。from io import StringIO

os模块

1
2
3
4
5
6
7
8
9
10
os.path.abspath('')
os.path.join('','') # 拼接路径
os.mkdir('')
os.rmdir('')
os.path.split('') # 返回list,将路径拆为两部分,后一部分是文件(夹)名
os.path.splittext('') # 返回文件扩展名
os.rename('','')
os.remove('')
os.listdir('') # 文件夹+文件
os.path.isdir('')

copyfile()shutil模块。

os.walk('') 返回的是一个三元组(root,dirs,files)。

  • root 所指的是当前正在遍历的这个文件夹的本身的地址
  • dirs 是一个 list ,内容是该文件夹中所有的目录的名字(不包括子目录)
  • files 同样是 list , 内容是该文件夹中所有的文件(不包括子目录)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
for root, dirs, files in os.walk('.'):
for name in files:
print(os.path.join(root, name))

def mywalk(path):
for name in os.listdir(path):
ful = os.path.join(path, name)
if os.path.isdir(name):
try:
mywalk(ful)
except Exception as e:
print(e)
else:
print(ful)

面向对象式的os替代品:Pathlib,教程见这里

序列化

先鸽了。大致用途是将Python对象变成JSON。

常用内建模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from datetime import datetime

now = datetime.now() # 是一个class
t = now.timestamp() # 返回从1970年开始的秒数,浮点数
datetime.fromtimestamp(t) # 本地时区

day = datetime.strptime('2015-6-1 18:19:59', '%Y-%m-%d %H:%M:%S') # 注意,转换后无时区信息
print(now.strftime('%a, %b %d %H:%M'))

def to_timestamp(dt_str, tz_str):
tim = datetime.strptime(dt_str, '%Y-%m-%d %H:%M:%S')
sq = re.findall(r'UTC(.*):00', tz_str)
sq = int(sq[0])
tim = tim.replace(tzinfo=timezone(timedelta(hours=sq)))
return tim.timestamp()


t1 = to_timestamp('2015-6-1 08:10:30', 'UTC+7:00')
assert t1 == 1433121030.0

如果要存储datetime,最佳方法是将其转换为timestamp再存储,因为timestamp的值与时区完全无关。

collections

namedtuple('NAME',['args'])

deque

defaultdict(function)有默认值的dict

hashlib

1
2
3
4
5
6
7
8
9
10
import hashlib

md5 = hashlib.md5('original')
md5.update('Hello, '.encode('utf-8')) # 必须encode
md5.update('World'.encode('utf-8'))
print(md5.hexdigest())
# 同理可用sha1

import hmac
h=hmac.new(key, message, digestmode='MD5')

itertools

1
2
3
>>> for c in itertools.chain('ABC', 'XYZ'):
... print(c)
# 迭代效果:'A' 'B' 'C' 'X' 'Y' 'Z'

8.4

昨天属实有点摸鱼。今天把urllib这些事学了。

强基快要出结果了.jpg

学这个之前要先把JSON学了。

JSON

image-20200804083520132

使用内置的json模块。

json.dumps(arg)返回str,内容是标准的JSON。dump(arg,file)可以直接把结果写入文件流中。

反序列化使用loads()/load()

如果是自定义的Class如何序列化?

可选参数default把任意一个对象变成一个可序列为JSON的对象。

1
2
3
4
5
6
def student2dict(std):
return dict(
name=std.name,
age=std.age,
score=std.score,
)

有一个Trick方法:

1
json.dumps(t, default=lambda obj: obj.__dict__)

同理可以反序列化。

urllib

基本上实现GET和POST就够了。

GET

request模块可以发送一个GET到指定的页面。

1
2
3
4
5
6
7
8
9
10
11
12
13
from urllib import request

with request.urlopen('https://yesno.wtf/api') as f:
data = f.read()
print('Status:', f.status, f.reason)
for k, v in f.getheaders():
print('%s: %s' % (k, v))
data = data.decode('utf-8')
print('Data:', data)
js = json.loads(data)
print(js)
for it in js.items():
print('%s: %s' % it)

挺简单易懂的。如果使用f.getheader(str),可以返回某个header.

urlopen函数除了直接传入网址,还可以传入Request类,实现自定义header,从而自定义UA等功能。

1
2
req = request.Request('http://www.douban.com/')
req.add_header('User-Agent', 'Mozilla/6.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/8.0 Mobile/10A5376e Safari/8536.25')

POST

只需要把参数以bytes形式传入。可以借助parse.urlencode.

模拟微博登录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from urllib import request, parse

print('Login Weibo...')
email = input('Email:')
pwd = input('Password:')
login_data = parse.urlencode(
[
('username', email), ('password', pwd), ('entry', 'mweibo'), ('client_id', ''), ('savestate', '1'), ('ec', ''),
('pagerefer', 'https://passport.weibo.cn/signin/welcome?entry=mweibo&r=http%3A%2F%2Fm.weibo.cn%2F')
]
)

req = request.Request('https://passport.weibo.cn/sso/login')
req.add_header('Origin', 'https://passport.weibo.cn')
req.add_header('User-Agent', 'Mozilla/6.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/8.0 Mobile/10A5376e Safari/8536.25')
req.add_header('Referer', 'https://passport.weibo.cn/signin/login?entry=mweibo&res=wel&wm=3349&r=http%3A%2F%2Fm.weibo.cn%2F')

with request.urlopen(req, data=login_data.encode('utf-8')) as f:
print('Status:', f.status, f.reason)
for it in f.getheaders():
print('%s: %s' % it)
print('Data:', f.read().decode('utf-8'))

Handler

默认使用的代理是系统设置的,如果要更改,感觉urllib提供的实现很复杂,之后学习requsets或urllib2.

1
2
3
4
5
6
# urllib
proxy_handler = request.ProxyHandler({'http': 'http://127.0.0.1:2334', 'https': 'https://127.0.0.1:2334'})
proxy_auth_handler = request.ProxyBasicAuthHandler()
opener = request.build_opener(proxy_handler, proxy_auth_handler)
with opener.open('https://www.google.com/') as f:
print('Status:', f.status, f.reason) # 200 OK

8.10

HTMLParser:先鸽了。

PIL

Python Imaging Library

1
2
3
4
5
6
7
8
from PIL import Image

im = Image.open('t.png')
w, h = im.size
im.thumbnail((w // 4, h // 4)) # 缩放
im.save('tt.jpg', 'jpeg')

im2 = im.filter(ImageFilter.BLUR) # 模糊

生成验证码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from PIL import Image, ImageDraw, ImageFont, ImageFilter
import random


def rndChar():
return chr(random.randint(65, 90))


def rndColor():
return (random.randint(64, 255), random.randint(64, 255), random.randint(64, 255))


def rndColor2():
return (random.randint(32, 127), random.randint(32, 127), random.randint(32, 127))


h = 60
w = h * 4

im = Image.new('RGB', (w, h), (255, 255, 255)) # 创建。Modes除了RGB还有'1'(1-bit,black and white),'L'(8-bit,black and white)

font = ImageFont.truetype('arial.ttf', 36) # 字体
draw = ImageDraw.Draw(im)
for x in range(w):
for y in range(h):
draw.point((x, y), fill=rndColor()) # 画背景

for t in range(4):
draw.text((60 * t + 10, random.randint(1, 27)), rndChar(), font=font, fill=rndColor2()) # 写字母

im = im.filter(ImageFilter.BLUR)
im.save('code.jpg', 'jpeg')

加强过后(加入了字符旋转):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from PIL import Image, ImageDraw, ImageFont, ImageFilter
import random


def rndChar():
return chr(random.randint(65, 90))


def rndColor():
return (random.randint(64, 255), random.randint(64, 255), random.randint(64, 255))


def rndColor2():
return (random.randint(32, 127), random.randint(32, 127), random.randint(32, 127))


h = 60
w = h * 4

im = Image.new('RGB', (w, h), (255, 255, 255))

font = ImageFont.truetype('arial.ttf', 40)
dr = ImageDraw.Draw(im)
for x in range(w):
for y in range(h):
dr.point((x, y), fill=rndColor())
for t in range(4):
text_layer = Image.new('RGBA', (40, 40)) # 注意是RGBA,A表示透明度
text_draw = ImageDraw.Draw(text_layer)
text_draw.text((0, 0), rndChar(), font=font, fill=(*rndColor2(), 255))
text_layer = text_layer.rotate(random.randint(0, 360)) # 旋转
im.paste(text_layer, (60 * t, random.randint(0, 20)), text_layer) # 粘贴

im = im.filter(ImageFilter.BLUR)
im.save('code.jpg', 'jpeg')

爽!PythonNB!

requests

比urllib更强悍。

1
2
3
4
r = requests.get('URL')
r.status_code
r.text # 内容(自动检测编码)
r.content # bytes对象

对于带参数的URL,传入dict作为params参数。

headers参数可以传入dict。

另外一提,使用http访问百度搜索不需要UA,使用https就必须UA。

要发送POST,只需要把方法变成post(),然后用data参数作为POST请求的数据。默认使用application/x-www-form-urlencoded对POST数据编码。如果要传递JSON,可以传入json参数。

四种常见的 POST 提交数据方式

重写百度翻译爬虫ing

上传文件可以使用files参数,读取文件时,必须使用'rb'模式,这样获取的bytes长度才是文件的长度。

1
2
upfile = {'file': open('name','rb')}
r = requests.post(url, files=upfile)

requests对cookie做了处理,使我们不必解析cookie就可以直接获取指定的cookie。

1
print(r.cookies['token'])

要传入cookie,只需将dict传入cookies参数。

要指定超时,传入以秒为单位的timeout参数。

chardet

对于未知编码的bytes,转换成str需要“猜测”编码,chardet这个第三方库通过收集编码的特征字符,有很大概率猜对。

1
2
3
>>> data = '离离原上草,一岁一枯荣'.encode('gbk')
>>> chardet.detect(data)
{'encoding': 'GB2312', 'confidence': 0.7407407407407407, 'language': 'Chinese'}

confidence是概率。

psutil

psutil = process and system utilities

获取系统信息,先鸽了。

8.13

字符串

新的格式化字符串的语法:f-string。以f开头,表达式用{}包起来(似乎这样无法输出{,}字符):

1
2
3
4
5
>>> name = 'Runoob'
>>> f'Hello {name}' # 替换变量

>>> f'{1+2}' # 使用表达式
'3'

在Python 3.8+可以使用=拼接表达式与结果:

1
2
3
>>> x = 1
>>> print(f'{x+1=}')
'x+1=2'

内建函数

.count(str,beg,end)

返回出现次数,可以指定范围。

.find(str,beg,end)

查找子串,如果存在,返回开始的索引,否则返回-1.类似的有rfind()

.join(seq)

以字符串为分隔符,合并seq中的元素为新串。

.replace()
strip()

删去前后空格。类似的还有lstrip(),rstrip()

.split(str,num)

以str为分隔符截取字符串,如果num有指定值,则仅截取num+1个。

.splitlines()
.startwith()

运算符

:=:海象运算符,可以在表达式内部为变量赋值。

is,is not:身份运算符,x is y等价于id(x)==id(y)id用于取址)。

and:从左到右计算表达式,若所有值为真,返回最后一个值,否则返回第一个假值。(or同理)

为class自定义运算符

image-20200814154005185

其他

Python整数对象的储存为无符号数加符号位。因此不存在补码。

Python的变量只要值相同,标识都相同。也就是说:

1
2
3
a = 5
b = 5
print(id(a) == id(b)) # True

只要列表、字典内容一样,那么标识也一样。

元组的标识是跟着变量名的。

8.14

heapq 提供了基于正规链表的堆实现。最小的值总是保持在 0 点。

heapify,heappop,heappush

calendar标准库提供对日期、星期的计算支持。

Python Performance Tips

对于字符串拼接,优先使用''.join();避免过多的加法连接,使用'<html>%s%s%s</html>' % str代替。

避免.,将频繁使用的推出函数用一个变量代替。

有限使用局部变量,它们的访问速度很快。(因为Python会现在局部的__dict__中查询,再到全局中查询,而且miss的哈希表查询更加慢)

优先使用map,速度快。

对于大部分情况下为假的if语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# slow
wdict = {}
for word in words:
if word not in wdict:
wdict[word] = 0
wdict[word] += 1

# fast
wdict = {}
for word in words:
try:
wdict[word] += 1
except KeyError:
wdict[word] = 1

另一种选择是使用defaultdict类。

Python函数调用的开销高。优先使用内置函数,因为内置函数是用C直接写的。

Python中加法比位运算快。(因为Python的int不定长,所以位运算比较鸡肋)

8.16

伪三目运算符:

1
2
3
4
a = 'a'
b = 'b'
print(1 and a or b, 0 and a or b)
# output: a b

要求a不能为假(不为空)

安全使用的方法:

1
2
3
4
a = ''
b = 'b'
print((1 and [a] or [b])[0], (0 and [a] or [b])[0])
# output: b

为什么要使用这么丑陋的语句?因为Python有时不能使用if语句,例如lambda函数中。

通过compile,exec,eval函数可以在运行时确定执行的代码。

os.system()可以在cmd中执行命令。