Learn from this
2020.7.31
容器初始化
list:[]
tuple:()
dict:{:}
set:{}
函数参数
位置参数
默认参数:默认参数必须指向不变对象!默认参数的值是在函数构建时被定义的。可以单独定义某个默认参数的值:
def enroll(a, b, c=1, d=2):
print(a, b, c, d)
enroll(1, 2, d=5) # output: 1 2 1 5
可变参数
可以传入一个tuple
来解决
def calc(numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
>>> calc((1,2,3)) # 此处要用双层括号
14
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。
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'}
命名关键字参数
限制关键字参数的名字。
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
的后两个元素。
迭代
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
循环中同时迭代索引和元素本身:
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)
列表生成式
感觉很好用!
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
>>> [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遍历。
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
错误,返回值包含在StopIteration
的value
中。
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的值也会变。
改进版:
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()
函数接收两个参数,一个是函数,一个是Iterable
,map
将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator
返回。
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
把结果继续和序列的下一个元素做累积计算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
比方说对一个序列求和,就可以用reduce
实现:
from functools import reduce
def add(x, y):
return x + y
>>> reduce(add, [1, 3, 5, 7, 9])
25
利用此方法可以方便的实现str转int:
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的元素。
def is_palindrome(n):
s = str(n)
return s == s[::-1]
筛回文好简单...
sorted
我们给sorted
传入key函数,即可实现忽略大小写的排序,相当于把所有元素都应用key之后再排序:
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']
要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True
:
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']
闭包
大杀器。类似于C++中的lamba.
返回一个函数,这个函数会同时包含它所需要的变量。
需要注意的问题:
返回的函数没有立即执行,而是等到调用才执行。 返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
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
创建计数器
这道题很妙啊。
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__
属性,可以拿到函数的名字:
>>> f=now
>>> now.__name__
'now'
>>> f.__name__
'now'
假设我们要在now
函数调用前后自动打印日志,但又不希望修改now()
函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”。
本质上,decorator就是一个返回函数的高阶函数,可以定义如下:
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
我们可以借助Python的@语法,把decorator置于函数的定义处:
@log
def now():
pass
调用now()
函数,不仅会运行now()
函数本身,还会在运行now()
函数前打印一行日志。
把@log
放到now()
函数的定义处,相当于执行了语句:
now = log(now)
wrapper()
函数的参数定义是(*args, **kw)
,因此,wrapper()
函数可以接受任意参数的调用。
decorater本身也可以传入参数:
比如,要自定义log的文本:
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用法如下:
@log('execute')
def now():
pass
但是,这样装饰之后的 函数,它的__name__
已经不是原来的函数了。Python内置的functools.wraps
就是干这个事的,所以,一个完整的decorator的写法如下:
import functools
def log(func):
@functools.wraps(func) # !!!
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
请设计一个decorator,它可作用于任何函数上,并打印该函数的执行时间:
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次该函数,再结束。
偏函数
int2 = functools.partial(int, base=2)
固定函数的参数。
8.2
模块
'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
循环:
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__
,我们可以写出一个链式调用:
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'
Niubi
任何类,只需要定义一个__call__()
方法,就可以直接对实例进行调用。
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类增加一个类属性,每创建一个实例,该属性自动增加:
class Student(object):
count = 0
def __init__(self, name):
self.name = name
Student.count += 1 # 方便!
我们可以给类一个方法,只需要在定义时加上@classmethod
:
class Dog(object):
@classmethod
def bark(cls):
print('WO-')
# 调用
Dog.bark()
其他方法
__doc__()
__str__()
__repr__()
__lt__()
(小于运算符)
__getitem__()
(使用obj[key]
)
__setitem__()
__len__()
__del__()
给实例绑定一个方法
>>> 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实例能添加的属性:
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
对子类不起作用,除非在子类中也定义__slots__
,这样,子类实例允许定义的属性就是自身的__slots__
加上父类的__slots__
。
但是当父类无__slots__
,而子类有,则无效。
@property
实现类的时候,我们常常需要提供各种借口,但是为每个接口都写一个函数有些麻烦,使用也不方便,我们可以用@property
,使得可以直接调用变量,用等号修改,且进行范围检查。
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:
class Dog(Mammal, Runnable):
pass
如果方法重复怎么办?优先保留顺序靠前的。
需要注意,如果子类定义了__init__()
函数,那么初始化时不会调用父类的__init__()
,因此需要显式地调用它。
枚举类
继承自Enum。@unique
装饰器可以帮助我们检查保证没有重复值。
元类
太 妙 了!豁然开朗!(用到时再复习吧)
8.3
错误处理
感觉这个挺重要的。
try:
pass
expect ... as ...:
pass
出错时,直接跳转到except语句块。错误也是class,继承自BaseException
。
如果错误没有被捕获,会一直往上抛,最后被Python解释器捕获,打印错误信息并退出。这就是调用栈。
Python内置的logging
模块可以记录错误信息。
try:
main()
except Exception as e:
logging.exception(e)
这样程序可以继续运行,并且错误被记录。
except
语句内可以继续raise
,抛给上一层。
调试
assert
+VS Code
文件读写
使用f=open(file,'r'/'rb'/'w'/'a'[,encoding=])
,退出程序前记得f.close()
。
方便的方法是使用with
语句:
with open('/Users/michael/test.txt', 'w') as f:
f.write('Hello, world!')
建议使用with
语句,以防忘记close
,如果不能直接使用with
语句,可以
StringIO&BytesIO
在内存中读写str。像文件一样写入即可。from io import StringIO
os模块
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 , 内容是该文件夹中所有的文件(不包括子目录)
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)
序列化
先鸽了。大致用途是将Python对象变成JSON。
常用内建模块
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
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
>>> for c in itertools.chain('ABC', 'XYZ'):
... print(c)
# 迭代效果:'A' 'B' 'C' 'X' 'Y' 'Z'
8.4
昨天属实有点摸鱼。今天把urllib
这些事学了。
强基快要出结果了.jpg
学这个之前要先把JSON学了。
JSON

使用内置的json
模块。
json.dumps(arg)
返回str
,内容是标准的JSON。dump(arg,file)
可以直接把结果写入文件流中。
反序列化使用loads()/load()
。
如果是自定义的Class如何序列化?
可选参数default
把任意一个对象变成一个可序列为JSON的对象。
def student2dict(std):
return dict(
name=std.name,
age=std.age,
score=std.score,
)
有一个Trick方法:
json.dumps(t, default=lambda obj: obj.__dict__)
同理可以反序列化。
urllib
基本上实现GET和POST就够了。
GET
request
模块可以发送一个GET到指定的页面。
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等功能。
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
.
模拟微博登录:
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.
# 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
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) # 模糊
生成验证码
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')

加强过后(加入了字符旋转):
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更强悍。
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参数。
重写百度翻译爬虫ing
上传文件可以使用files
参数,读取文件时,必须使用'rb'
模式,这样获取的bytes
长度才是文件的长度。
upfile = {'file': open('name','rb')}
r = requests.post(url, files=upfile)
requests对cookie做了处理,使我们不必解析cookie就可以直接获取指定的cookie。
print(r.cookies['token'])
要传入cookie,只需将dict传入cookies
参数。
要指定超时,传入以秒为单位的timeout
参数。
chardet
对于未知编码的bytes
,转换成str
需要“猜测”编码,chardet
这个第三方库通过收集编码的特征字符,有很大概率猜对。
>>> 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
开头,表达式用{}
包起来(似乎这样无法输出{,}
字符):
>>> name = 'Runoob'
>>> f'Hello {name}' # 替换变量
>>> f'{1+2}' # 使用表达式
'3'
在Python 3.8+可以使用=
拼接表达式与结果:
>>> 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
同理)

其他
Python整数对象的储存为无符号数加符号位。因此不存在补码。
Python的变量只要值相同,标识都相同。也就是说:
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
语句:
# 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
伪三目运算符:
a = 'a'
b = 'b'
print(1 and a or b, 0 and a or b)
# output: a b
要求a
不能为假(不为空)
安全使用的方法:
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
中执行命令。