python 高级部分精华--那些书本不会告诉你的坑

时间:2022-11-19 07:46:35

递归目录生成器方式, else 里的 tmp 显示获取 yield 不可缺少 , 递归算法中若要使用生成器,需要在生成器的原函数(首次调用)显式得到所有yield值

def get_file_recur(path):
children = os.listdir(path)
for child in children:
qualified_child = os.path.join(path,child)
if os.path.isfile(qualified_child):
yield qualified_child
else:
tmp = get_file_recur(qualified_child)
for item in tmp:
yield item for file in get_file_recur('/home/xxx/xxx/Frank Li'):
print(file) >>> import os
>>> def recur_dir(path):
... children = os.listdir(path)
... for child in children:
... qualified_child = os.path.join(path,child)
... if os.path.isdir(qualified_child):
... tmp = recur_dir(qualified_child)
... for t in tmp:
... yield t
... else:
... yield qualified_child
...
>>> for file in recur_dir('./'):
... print(file)

参考资料来源,如下 flattern list

def flattern(lst):
for item in lst:
if isinstance(item,list):
inner_list = flattern(item)
for i in inner_list:
yield i
else:
yield item l=[1,2,3,4,5,[6],[7,8,[9,[10]]]]
lst=flattern(l)
print(list(lst))
 x = get('key','default value')[0]  or 0

体验一波不一样的 协程, 对比原本的 fib

import random
import time def lazy_fib(n):
a = 0
b =1
i = 0
while i<n:
sleep_cnt = yield b
print('uh...let me think {0} seconds...'.format(sleep_cnt))
time.sleep(sleep_cnt)
a, b = b, a+b
i+=1 print('-'*10 + 'test yield send' + '-'*10) N = 20
lazyFib = lazy_fib(N)
fib_res = next(lazyFib)
while True:
print(fib_res)
try:
fib_res = lazyFib.send(random.uniform(0,0.5))
except StopIteration:
break
import random
import time def stupid_fib(n):
a, b = 0, 1
i = 0
while i<n:
sleep_cnt = yield a
print('sleep {:.3f} secs'.format(sleep_cnt))
time.sleep(sleep_cnt)
a, b = b, a+b
i+=1 def copy_fib(n):
print('I am copy from stupid fib')
yield from stupid_fib(n)
print('Copy end') print('-'*10+'test yield from and send'+'-'*10)
N = 20
cp_fib = copy_fib(N)
fib_res = next(cp_fib)
while True:
print(fib_res)
try:
fib_res = cp_fib.send(random.uniform(0,0.5))
except StopIteration:
break

仔细品味

>>> def set_priority(data,group):
... found = False
... def helper(x):
... nonlocal found
... if x in group:
... found = True
... return (0,x)
... return (1,x)
... data.sort(key=helper)
... return found
...
>>> data = [8,3,1,2,5,4,7,6]
>>> group = {2,3,5,7}
>>> set_priority(data,group)
True
>>> print(data)
[2, 3, 5, 7, 1, 4, 6, 8] # 改进版
>>> class Sorter(object):
... def __init__(self,group):
... self.found = False
... self.group = group
... def __call__(self,x):
... if x in self.group:
... self.found = True
... return (0,x)
... return (1,x)
...
>>> data = [8,3,1,2,5,4,7,6]
>>> group = {2,3,5,7}
>>> sorter = Sorter(group)
>>> data.sort(key=sorter)
>>> data
[2, 3, 5, 7, 1, 4, 6, 8]
>>> assert sorter.found is True

python 高级部分精华--那些书本不会告诉你的坑

python 高级部分精华--那些书本不会告诉你的坑

 x = get('key','default value')[0]  or 0

python 高级部分精华--那些书本不会告诉你的坑

sys.argv与optparse与argparse与getopt的区别

optparse与argparse的区别:
Deprecated since version 3.2: The optparse module is deprecated and will not be developed further; development will continue with the argparse module. Deprecated since version 2.7: The optparse module is deprecated and will not be developed further; development will continue with the argparse module. argparse与sys.argv的区别:
The argparse module makes it easy to write user-friendly command-line interfaces. The program defines what arguments it requires, and argparse will figure out how to parse those out of sys.argv. The argparse module also automatically generates help and usage messages and issues errors when users give the program invalid arguments. argparse 与getopt的区别:
The getopt module is a parser for command line options whose API is designed to be familiar to users of the C getopt() function. Users who are unfamiliar with the C getopt() function or who would like to write less code and get better help and error messages should consider using the argparse module instead.

多多学习

from collections import Iterator, Iterable
from collections import defaultdict
from collections import Counter, ChainMap, OrderedDict, namedtuple, deque
from itertools import islice # 替代 切片,但是只能 是正数
from itertools import zip_longest # 替代 zip 可以 对不一样个数的 进行迭代 from concurrent.futures import ThreadPoolExecutor as Pool

python 利用生成器读取大文件

def read_block(file_path,block_size=1024*1024):
fr = open(file_path)
while True:
read_blk = fr.read(block_size)
if not read_blk:
break
yield read_blk for blk in read_block('./test.py'):
print(blk)

python 利用 with 读取大文件,使用 rb 时候 效率最高

with open('./test.py','r') as f:
for line in f:
print(line)

让 json 格式看起来更好

    with open('./param.json','w',encoding='utf-8') as f:
f.write(json.dumps(json.loads(json_str),ensure_ascii=False,indent=4))

以下转自简书---

这周听了三节Python进阶课程,有十几年的老程序给你讲课传授一门语言的进阶知识,也许这是在大公司才能享受到的福利。虽然接触使用Python也有三四年时间了,但是从课程中还是学习到不少东西,掌握了新技巧的用法,明白了老知识背后的原因。

下载了课件,做了笔记,但我还是希望用讲述的方式把它们表现出来,为未来的自己,也给需要的读者。整体以大雄的课程为蓝本,结合我在开发中的一些自己的体会和想法。

1. 写操作对于命名空间的影响

首先来看这样一段代码:

import math

def foo(processed):
value = math.pi # The other programmer add logic here.
if processed:
import math
value = math.sin(value) print value foo(True)

思考:你觉得这段代码有没有什么问题,它的运行结果是什么?

首先,我个人不喜欢在代码中进行import math的操作的方式,通常会建议把这一操作放置到文件头部,这主要处于性能的考虑——虽然已经import过的模块不会重复执行加载过程,但毕竟有一次从sys.modules中查询的过程。这种操作在tick等高频执行的逻辑中尤其要去避免。

但这并不是这段代码的问题所在的重点,当你尝试执行这段代码的时候,会输出如下的错误:

Traceback (most recent call last):

File "C:\Users\David-PC\Desktop\Advanced Course on Python 2016\t019.py", line 13, in

foo(True)

File "C:\Users\David-PC\Desktop\Advanced Course on Python 2016\t019.py", line 4, in foo

value = math.pi

UnboundLocalError: local variable 'math' referenced before assignment

在赋值之前被引用了,这似乎是在文件头部进行import的锅。这个例子稍微有点复杂,我们尝试写一段有点近似但是更简单的例子,在之前编码过程中我就遇到过类似的情况:

value = 0
def foo():
if value > 0:
value = 1
print value
foo()

同样会提示value在被赋值之前被使用了,让这段代码正常运作很简单,只需要把global value放在foo函数定义的第一行就可以了。

思考: 为什么在foo函数内部,无法访问其外部的value变量?

如果你把value = 1这一行代码注释掉,这段代码就可以正常运行,看上去对于value的赋值操作导致了我们无法正常访问一个外部的变量,无论这个赋值操作在访问操作之前还是之后。

Write operation will shield the locating outside the current name space, which is determined at compile time.

简单来说,命名空间内部如果有对变量的写操作,这个变量在这个命名空间中就会被认为是local的,你的代码就不能在赋值之前使用它,而且检查过程是在编译的时候。使用global关键字可以改变这一行为。

那我们回到第一段代码,为什么imort的一个模块也无法正常被使用呢?

如果理解import的过程,答案就很简单了——import其实就是一个赋值的过程。

总结:之前我自认为Python的命名空间很容易理解,对于全局变量或者说upvalue的访问却通常不去注意,有时候觉得不需要写global来标识也可以访问得到,有时候又会遇到语法错误的提示,其实一直没有理解清楚是什么规则导致这样的结果。

写操作对于命名空间的影响解答了这一问题,让我看到自己之前“面对出错提示编程”的愚蠢和懒惰。。。

2. 循环引用

Python的垃圾回收(GC)结合了引用计数(Reference Count)、对象池(Object Pool)、标记清除(Mark and Sweep)、分代回收(Generational Collecting)这几种技术,具体的GC实现放在后面来说,我们先看代码中存在循环引用的情况。

游戏开发中设计出循环引用非常地简单,比如游戏中常用的实体(Entity)结构:

class EntityManager(object):
def __init__():
self.__entities = {} def add_entity(eid):
#Some process code.
self.__entities[eid] = Entity(id, self) def get_entity(eid):
return self.__entities.get(eid, None) class Entity(object):
def __init__(eid, mgr):
self.eid = _id
self.mgr = mgr def attact(skill_id, target_id):
target = self.mgr.get_entity(target_id)
#attack the target
#...

很明显,这里EntityManager中的__entities属性引用了它所控制的所有对象,而对于一个游戏实体,有时候需要能够获取别的实体对象,那么最简单的方法就是把EntityManager的自己传递给创建出来的实体,让其保留一个引用,这样在执行攻击这样的函数的时候,就可以很方便地获取到想要拿到的数据。

EntityManager中的__entities属性引用了Entity对象,Entity对象身上的mgr属性又引用了EntityManager对象,这就存在循环引用。

有的人也许会说,有循环引用了,so what? 首先我可以从逻辑上保证释放的时候都会把环解开,这样就可以正常释放内存了。再者,本身Python自己就提供了垃圾回收的方式,它可以帮我清理。

对于这种想法,作为一个游戏开发者,我表示——呵呵

我们看一个在游戏开发中常见的循环引用的例子,有些情况下写了循环引用而不自知(实例代码直接使用大雄课程中的)。

class Animation(object):
def __init__(self, callback):
self._callback = callback class Entity(object):
def __init__(self):
self._animation = Animation(self._complete) def _complete(self):
pass e = Entity()
print e._animation._callback.im_self is e

最终print输出的结果是True,也解释了这段逻辑中的循环引用所在。

对于多人协作来实现的大型项目来说,逻辑上保证代码中没有环存在是几乎不可能的事情,况且即使你代码逻辑上可以正确释放,偶发的traceback就可能让你接环的逻辑没有被执行到,从而导致了循环引用对象的无法立即释放。

Python的循环引用处理,如果一个对象的引用计数为0的时候,该对象会立即被释放掉。

然后Python的GC是很耗的一个过程,会造成CPU瞬间的峰值等问题,网易有项目就完全自己实现了一套分片多线程的GC机制来替换掉Python原生的GC。

大量循环引用的存在会导致更慢更加频繁的GC,也会导致内存的波动。

解决方法:对于EntityManager的例子,使用weakref来解决;对于callback的例子,尽量避免使用对象的方法来作为一个回调。

self._animation = Animation(lambda obj = weakref.proxy(self): obj._complete())

总结:对于简单的系统来说,不需要关心循环引用的问题,交给Python的GC就够了,但是需要长时间运行,对于CPU波动敏感的系统,需要关注循环引用的影响,尽量去规避。

题外话:在我们现在的项目中,EntityManager的例子使用了单例模式来解除循环引用,这是一种常用的方法,但是单例模式也不是“银弹”。这种设计模式在限制对象实例化的同时,也提供了全局访问的接口,意味着这个单例对象变成了一个全局对象,于是代码中充满了不考虑耦合性的滥用。在客户端代码中,这些使用全局单例的逻辑没有问题,因为客户端只需要一个EntityManager就可以管理所有的游戏实体,也不会存在其他的并行环境,而当我们需要进行服务端开发的时候,同一份代码拿到服务端就变成了灾难——对于服务端来说,可能会存在很多EntityManager管理不同情境下的游戏实体,单例的模式不再可用,之前任意访问EntityManager的地方都需要经过迭代和整理才可以正常执行。

闭包与引用传递的坑

这一部分是关于Python的Callable。在*上有一个专门的问题叫做“What is a "callable" in Python”,高票回答中说:

A callable is anything that can be called.

这个回答很抽象,大雄从更具体的角度来阐述Callable这个概念——在Python中哪些是callable的?

function
closure
bound method
unbound method
class method
static method
functor
operator
class
先说答案,很明显,列出的这些都是callable的。这些概念中的大部分我在工作中都有使用,包括比如closure的坑也帮助新同学调试bug的时候看到新入职的同学自己踩到过,但是对于bound method和unbound method这些概念还不是很清晰。我们也一个个来看。 3. Closure
Closure,闭包,在Python中本质上是一个函数,或者更具体来说它和Function的区别是它包含了Code和Environment,而Python中Environment又可以分为globals、locals和cells三部分。
globals和locals比较容易理解,其实就是两个dict,分别保存了全局变量和局部变量,那这个cells是什么?我们先来看一个非常经典的例子: def foo():
logout_lst = [] for i in xrange(5):
def logout():
print i
logout_lst.append(logout) for l in logout_lst:
l() foo()
思考:这段代码的输出是什么? 分析一下这段代码,虽然这里为了方便演示,构造了一个只有print的逻辑,你可能会质疑它的作用,但是在我们开发的过程中,就有同学在循环内部定义了类似的闭包用于引擎回调的调用,引用了外部了一个类似i的变量。例子中,在foo的函数内部,代码def logout()定义了一个闭包(写到这里让我想起了遥远的过去写JAVA代码时使用的Inner Class),然后我们想使用外部变量i的值,这里只是把它输出出来,通常我们想要输出的结果是打印0、1、2、3、4这几个数字,当然中间有换行,但是最终的输出结果是什么呢?
5个4!
为什么呢?我们来添加一些输出日志来查看一下,为了方便看输出,我们只循环两次来看,修改后的代码如下: def foo():
logout_lst = [] for i in xrange(2):
def logout():
print "i:", i, id(i)
print "globals:", globals()
print "locals:", locals()
logout_lst.append(logout) for l in logout_lst:
l()
print "Cells:", l.__closure__, id(l.__closure__[0].cell_contents)
print '' foo()
输出的结果如下: i: 1 35882616
globals: {'__builtins__': <module '__builtin__' (built-in)>, '__file__': 'F:\\David\\narrator.py', '__package__': None, '__name__': '__main__', 'foo': <function foo at 0x022C72B0>, '__doc__': None}
locals: {'i': 1}
Cells: (<cell at 0x02354570: int object at 0x02238678>,) 35882616 i: 1 35882616
globals: {'__builtins__': <module '__builtin__' (built-in)>, '__file__': 'F:\\David\\narrator.py', '__package__': None, '__name__': '__main__', 'foo': <function foo at 0x022C72B0>, '__doc__': None}
locals: {'i': 1}
Cells: (<cell at 0x02354570: int object at 0x02238678>,) 35882616
首先打印一下i的值与i这个变量的id,你可以认为这是i在Python虚拟机中的唯一编号,两次输出它的值都是1,id也都是一个35882616,然后输出一下globals和locals看一下,这两个很简单,不做分析了。最后通过__closure属性来看下闭包的内容: Cells: (<cell at 0x02354570: int object at 0x02238678>,)
这就是前面说的cells,它是一个cell对象,里面的内容有一个int对象,通过cell_contents属性可以查看到它的id是35882616,和i是一样的。
可以看出,cells就是对于up-values的引用(references),注意,引用!
那之前的输出就很容易理解了,引用,当后面调用闭包执行的时候,i变量值已经变成了4,那输出i自然每次都是4。
最后,如何修改可以让你的代码可以按照之前的计划正常执行呢?很简单,不要直接使用cells中的值,而是用一个参数来让它变成参数,就是定义这个闭包的时刻的值了。 def foo():
logout_lst = [] for i in xrange(2):
def logout(x = i):
print "x:", x, id(x)
print "globals:", globals()
print "locals:", locals()
logout_lst.append(logout) for l in logout_lst:
l()
print "Cells:", l.__closure__
print '' foo()
输出结果: x: 0 37062276
globals: {'__builtins__': <module '__builtin__' (built-in)>, '__file__': 'F:\\David\\narrator.py', '__package__': None, '__name__': '__main__', 'foo': <function foo at 0x023E72B0>, '__doc__': None}
locals: {'x': 0}
Cells: None x: 1 37062264
globals: {'__builtins__': <module '__builtin__' (built-in)>, '__file__': 'F:\\David\\narrator.py', '__package__': None, '__name__': '__main__', 'foo': <function foo at 0x023E72B0>, '__doc__': None}
locals: {'x': 1}
Cells: None
此处,cells的内容变为了None,输出的结果也是0和1,它们的id自然也不同。其实参数也可以写成def logout(i = i):,内部可以使用i,但是这会造成一些困扰,个人不推荐这么写。 思考:那么你以为这个坑就踩完了吗?有没有哪里还可能存在问题? def logout(x = i):这种定义虽然用在闭包里,但是其实是函数的默认参数,那么默认参数如果使用list、dict或者python object等这样mutable的值会怎样?这自然是另外一个入门级的坑: 背景: 不建议在函数默认参数中使用mutable value,而保证只使用immutable value。 但有时候为了解决一个坑,可能不小心踩入另外一个坑。如果这里使用了,比如一个list对象作为参数,那么创建出来的这几个闭包中的x都引用的会是同一个对象,而且,在任何一个闭包多次调用的时候,x的值都是同一个对象的引用。如果像例子中是只读的逻辑的话,可能没有问题,如果后面有人添加了修改的逻辑,那就呵呵呵呵了。可能会乱成一锅粥,出现各种神奇的现象,写这样逻辑的人自求多福吧。 总结:理解闭包的概念,理解引用的概念,编写代码保持思路清晰,明确自己使用的变量存在在哪里,是一件非常非常重要的事情,对团队开发中避免匪夷所思令人抓狂的Bug很有帮助! 这一部分只讲闭包这一个点,其实关于闭包还有很多知识点,有兴趣的可以自己查阅相关资料。第三部分讲解bound method和unbound method,这是我这次课程最喜欢的部分。 PS: 很多坑,你看过文章介绍,或者听同事讲过,但是写代码的时候有时还是会由于当时思路的混乱而饶进去,重新踩一遍,这往往难以避免,不亲身经历的坑思维上很难那么敏感。经验学习和知识积累的作用,是让你从坑中往外爬的时候更快一些,回头看那些坑印象更深刻一些。 作者:董夕
链接:https://www.jianshu.com/p/39460eff2d9d
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。