4 其他流程控制工具
- else在for循环中的应用
- 函数按值调用
- 位置参数、可变参数、关键字参数
- 将*、**理解为从列表/元组、字典中解包参数
- lamda表达式
- doc
- annotations
数据结构
- append
- extend
- insert
- remove
- pop(i)
- clear
- index(x[, start[, end]])
- count(x)
- reverse
- sort
- copy
- collections.deque
- popleft
- 列表推导式
- 嵌套的列表推导式
- zip函数
- del关键字
- tuple声明方式
- 打包/解包
Set
- {xxx}创建set,但a={}是一个空字典
-
- | & ^
dict
- list(dict) return list of key
- sorted(dict)按插入顺序
loop
- items
- enumerate
- 如果要按某个指定顺序循环一个序列,可以用 sorted() 函数,它可以在不改动原序列的基础上返回一个新的排好序的序列
- is/not is
module
- name 属性
- 模块可以包含可执行的语句以及函数定义。这些语句用于初始化模块。它们仅在模块 第一次 在 import 语句中被导入时才执行。
- 每个模块都有它自己的私有符号表,该表用作模块中定义的所有函数的全局符号表。因此,模块的作者可以在模块内使用全局变量,而不必担心与用户的全局变量发生意外冲突。
- 变量表概念、名称
- module.reload()
if __name__ == "__main__":
stat
stat
- sys变量
- dir函数
- __init.py__文件的作用
- 相对导入、绝对导入
input & output
- 字符串前加f的作用
- .format()
- str
- repr
- f-
f'{:int}'
f'{:!a}' -> ascii()
f'{:!s}' -> str()
f'{:!r}' -> repr()
- str.rjust()、ljust()、center()、zfill()
- %操作符进行格式化
file manipulate
- open
- mode:r w a r+ b
- f.readline(size)
- list(f)
- f.readlines()
- f.write()
- f.seek(offset, from_what) from_what值为0或1
- f.tell()
json
- json.dumps
- json.dump
- json.load
ch-1 Python数据模型
- namedtuple用于构建只有少量属性但是没有方法的对象
import collections
Card = collections.namedtuple('Card', ['rank', 'suit'])
- 特殊方法的存在是为了被Python解释器调用的, 没有object.len(),应该用len(object)
- 如果是python内置类型,比如list,str,bytearray,CPython会直接返回 PyVarObject 里的 ob_size 属性
- PyVarObject 时表示内存中长度可变的内置对象的C语言结构体
- __repr__把一个对象用字符串表达出来以便辨认
- __str__会被str()调用,或者用print函数打印一个对象时被调用,如果对象没有实现__str__方法,解释器则会去调用__repr__方法
- 默认情况,自定义类的实例总被认为真的,除非该类实现了__bool__或__len__,如果没有__bool__,bool()则调用__len__,0为False,否则为True
- 如果x是一个内置类型的实例,那么CPython在len的实现中则会直接从C结构体里读取对象的长度,这样则使得运行高效
ch-2 序列构成的数组
- 容器序列存放任意类型的对象的引用,例如list、tuple、collections.deque
- 扁平序列存放的是值,其实质为一段连续的内存空间,只能存放字符、字节和数值这种基础类型
列表推导
- python2中列表推导的for关键词之后的赋值操作可能会影响上下文中的同名变量,而python3中列表推导有了局部作用域,表达式内部的变量和赋值只在局部起作用
tuple
- tuple不仅是不可变列表,将每个元素的字段位置信息利用起来可以更加高效,比如namedtuple就是
- 元组拆包
- 平行赋值 a, b = my_tuple
- 交换两值 a, b = b, a
- 用*运算符将某可迭代对象拆开作为函数参数
- 用namedtuple构建的类的实例所消耗的内存跟元组是一样的,但是python不会用__dict__属性来存储实例的属性,所以namedtuple构建的类要比普通类节省空间
- 两个参数,第一个是类名,第二个是类中各个字段的名字,后者可以由数个字符串组成的可迭代对象,或者由空格分开的字段名
- 可以用字段名或位置信息获取字段信息
- namedtuple还有诸如_fields类属性、_make(iterable)类方法、_asdict()实例方法等
切片
- 如果赋值的对象是切片,那么语句的有操作数必须是一个可迭代对象,即便是单值也要转换,否则会引起TypeError
序列操作
- +、*都不会改变原有序列,而是新建一个包含同样类型数据的序列作为result
- 如果a实现了__iadd__,那么a += b则会调用它,如果没有,则调用a = a + b即__add__
- +=、*=对于可变序列则会就地修改、不可变序列则会创建一个新的序列并赋值
- 下面的语句会出错、但是t[2]仍然被成功修改
t = (1, 2, [30, 40])
t[2] += [50, 60]
print(t)
- 因为解释器先增量、再赋值
- 所以
- 不要把可变对象放在元组里面
- 增量赋值不是一个原子操作
- list.sort()就地修改
- sorted(seq, key=, reverse=)新建副本
- position = bisect.bisect(seq, item, lo=, hi=)
- bisect.insort(seq, item, lo=, hi=)
- array.array.tofile(fp)
- array.array.fromfile(fp, size)
- collections.deque方法如下
appendleft()
append()
extendleft(iter)
# extendleft将iter中从左到右依次添加到deque最左侧,所以iter中元素会逆序出现在deque中
extend(iter)
rotate(n) # 右旋n位
pop()
popleft()
ch-3 字典和集合
- 可散列:
- 整个生命周期中,散列值不变
- 必须实现__hash__,__eq__方法
- 若两个可散列对象相等,那么他们的散列值一定是一样的
- frozenset只可容纳可散列类型
- 自定义的对象一般都是可散列的,散列值就是他们的id()值
字典
- 字典推导
new_dict = {k: v for k, v in src_dict} - setdefault(k, default).xxxx(),如果k不在映射之中,就把{k: default}放入字典,并返回default的引用
if key not in my_dict:
my_dict[key] = []
my_dict[key].append(new_value)
- collections.defaultdict(my_executor),defaultdict中的参数是一个可调用对象,在__getitem__发生keyError时,defaultdict会调用被赋值给实例default_factory属性的my_executor
- 所有映射类型,在__getitem__找不到键的时候都会自动调用__missing__方法,而不是抛出KeyError异常
- 但是__missing__的存在,对__contains__或get不会有任何影响
- 在python3中运用in运算符会递归调用__contains__函数,而__contains__函数返回的是一个高效的内存视图,不像python2中返回一个列表,然后逐个比对
- collections.Counter()是一个对k进行计数的字典
- a = {}创建的是一个空字典,如果想创建空集合,必须使用a = set()
散列表
- 散列表的单元是表元(bucket),python的装填银子一般为2/3
- 如果一个整形对象能被存进一个机器字之中,那么它的散列值就是它本身的值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UR8ik9tS-1580538431162)(C:\Users\Mr.Robot\Desktop\workspace\python_study\hash.png)]
ch-9 符合python风格的对象
- repr一般得到实例的构造方法表示
- print会调用str函数
- {0.mass:5.3e} :左边的表示字段名,右侧部分为格式说明符,最后一位为格式码,传入format_spec的最后一个字符
- 使用两个前导下划线,将分量标记为私有,并用@property装饰器把读值方法标记为 特性
- 此时读值方法与公开属性同名
- 对于以 __property形式命名的实例属性,Python会修改为_className__property,并且把属性名存入实例的__dict__属性中
- 这个特性称为 名称改写
def property_name(self):return self.__property
-
python默认会在各实例中名为__dict__的字典里存储实例属性,为了使用底层的散列表提升访问速度,字典会消耗大量内存
-
使用__slots__属性的目的是告诉解释器“这个类中的所有实例属性都在这儿了”,这样python会在各个实例中使用类似元组的结构存储实例变量
-
如果为不存在的实例属性赋值,同名类属性不受影响,而是在实例中创建新属性,这样之后,self.property读取的是该实例中的新属性,而非类中的同名属性
-
子类会_继承_父类的所有_类属性_
ch-16 协程
- 只有协程处于暂停状态下,才可以调用send方法
- 预激协程
- next(my_coroutine)
- my_coroutine.send(None)
- 当协程因未处理异常而return时,其return的内容会默认赋值给异常类的.value属性
- 就像for循环迭代一样,会自动捕获StopIteration异常
yield from
- 在gen中使用yield from subgen()时
- subgen会获取控制权,并把产出的值传给gen的调用方
- 即yield from就好像一个管道,让调用方直接
- yield from x
- 获取iter(x)
- 将职责_委托_给子生成器
- 打开双向通道,把最外层的调用方与最内层的子生成器连接起来,即_委托职责_
python 多线程
多线程
- threading.Thread(target=,args=())
- class myThread(threading.Thread)
线程通信
- queue.Queue本身是线程安全的
- Queue方式即OS中所讲的共享数据结构方式
线程同步
- threading.Lock()
- lock.acquire()
- lock.release()
- threading.Condition()
- condition.wait()
- condition.notify()
- threading.Semaphore(size)
- semaphore.acquire()
- semaphore.release()
concurrent.futures.ThreadPoolExecutor
- executor = ThreadPoolExecutor(max_workers=)
- task = executor.submit(func, (args))
- task.done()
- task.cancel()
- task.result()
- wait(all_tasks, )
- 可直接通过executor的map方法获取已经完成的task的值
- for result in executor.map(my_func, args)
- concurrent.futures.as_completed
for future in as_completed(all_tasks):
- 此处为所有已完成task
多进程编程
由于GIL的存在,CPU频繁型操作,使用多进程编程
- multiprocessing
- process = multiprocess.Process(target=my_func, args=())
- process.pid
- process.start
- process.join
- multiprocessing.Pool()线程池
import multiprocessing
import time
def get_html(n):
time.sleep(n)
print("sub progress success")
return n
pool = multiprocessing.Pool(multiprocessing.cpu_count())
# 当pool中进程完成后,pool以进程的结果为参数,调用my_callback_func函数
result = pool.apply_async(get_html, args=(3, ), callback=my_callback_func)
# pool不再接收新进程
pool.close()
# 等待pool中所有进程执行结束
pool.join()
print(result.get())
for result in pool.imap(get_html, [1, 5, 3]):
print("{} sleep success".format(result))
for result in pool.imap_unordered(get_html, [1, 5, 3]):
print("{} sleep success".format(result))
- ProcessPoolExecutor()用法与ThreadPoolExecutor类似
多进程通信
- queue.Queue不再适用,要改用multiprocessing.Queue
- 共享变量不再适用于多进程,因为fork子线程时,OS将父进程中所有变量进行值拷贝
- 而queue.Queue仍不能适用于Pool中的多进程通信,要改用Manager().Queue()
- 采用Pipe进行多进程通信
from multiprocessing import Pipe
receive_pipe, send_pipe = Pipe()
send_pipe.send("information")
receive_pipe.recv()
# pipe性能优于Manager().Queue()
- 其他进程通信方式
- 共享内存方式
- Array
- Value
- dict
- list
数学运算
- 在交互模式中,最近的一次数学运算的结果赋值给解释器系统内部的 _ 变量,但是如果自己定义了一个名为 _ 的变量,那么系统内部的 _ 变量就会被覆盖
函数
默认参数问题
def add_end(L=[]):
L.append('END')
return L
>>> add_end()
['END']
>>> add_end()
['END', 'END']
>>> add_end()
['END', 'END', 'END']
- 可以理解为:
- Python函数在定义的时候,默认参数L的值就被计算出来了,即[],
- 因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。
可变参数
- 参数可变
def my_variables(x, *args):
print(x)
print(args)
my_variables(1, 2, 3, 5, 6)
>>>1
(2, 3, 5, 6)
my_variables(1)
>>>1
()
- 将list转为默认参数传入函数:
a = [1, 2, 3, 5, 6]
my_variables(*a)
>>>1
(2, 3, 5, 6)
关键字参数
- 可变关键字参数
def my_variable(x, y, **kwargs):
kwargs["job"] = "CTO"
print(x, y, kwargs)
my_variable(1, 2, name="刘震", gender="男")
>>>1 2 {'job': 'CTO', 'gender': '男', 'name': '刘震'}
- 将dict转为关键字参数传入
- 主义函数内部只是获得了关键字参数字典的一个拷贝,函数内部对该字典的修改不影响外部的字典
personal_info = {"name": "刘震", "gender": "男"}
my_variable(1, 2, **personal_info)
print(personal_info)
命名关键字参数
def limit_kw(name, gender, *, location, job):
print("limit_kw:=====" + name, gender, location, job)
limit_kw("刘震", "男", location="南京", job="CTO")
高级特性
切片
迭代
- 迭代字典
- 迭代key(注意dict内部无序存储,迭代顺序可能跟打印顺序不同)
for key in d: print(key, d[key])
- 迭代item
for k, v in d.items(): print(k, v)
- 迭代value:
for v in d.values():
- 同时迭代下标索引和值:
L = ['a', 'b', 'c', 'd', 'e'] for i, value in enumerate(L): print(i, value)
列表生成式
L1 = ['Hello', 'World', 18, 'Apple', None]
L2 = [s.lower() for s in L1 if isinstance(s, str)]
高阶函数
map
map(f, [1, 2, 3, 4]) = Iterator(f(L[0]), f(L[1]), f(L[2]), f(L[3]))
reduce
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
应用
### str转int
def fn(x, y):
return x * 10 + y
def char2num(s):
digits = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
return digits[s]
reduce(fn, map(char2num, '13579'))
>>> 13579
### 整理
def str2int(s):
digits = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
def fn(x, y):
return x * 10 + y
def char2num(c):
return digits[c]
return reduce(fn, map(char2num, s))
### 运用lambda表达式化简
def str2int(s):
digits = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
def char2num(c):
return digits[c]
return reduce(lambda x, y: x * 10 + y, map(char2num, s))
闭包
注意到返回的函数在其定义内部引用了局部变量args,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。
另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()才执行。我们来看一个例子:
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()
在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都返回了。
你可能认为调用f1(),f2()和f3()结果应该是1,4,9,但实际结果是:
>>> f1()
9
>>> f2()
9
>>> f3()
9
全部都是9!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9。
偏函数
import functools
int2 = functools.partial(int, base=2)
- 把一个函数的某些参数给固定住(设置默认值),返回一个新函数,调用这个新函数会更简单。
- 注意到上面的新的int2函数,仅仅是把base参数重新设定默认值为2,但int2仍然可以传入base参数
模块
模块
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
' a test module ' # 模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;
__author__ = 'Michael Liao'
import sys # 导入sys模块后,我们就有了变量sys指向该模块
# 利用sys这个变量,就可以访问sys模块的所有功能
# sys模块有一个argv变量,用list存储了命令行的所有参数
# argv至少有一个元素,因为第一个参数永远是该.py文件的名称
def test():
args = sys.argv
if len(args)==1:
print('Hello, world!')
elif len(args)==2:
print('Hello, %s!' % args[1])
else:
print('Too many arguments!')
if __name__=='__main__':
test()