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

递归目录生成器方式, 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

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

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也有三四年时间了,可是从课程中仍是学习到很多东西,掌握了新技巧的用法,明白了老知识背后的缘由。
下载了课件,作了笔记,但我仍是但愿用讲述的方式把它们表现出来,为将来的本身,也给须要的读者。总体以大雄的课程为蓝本,结合我在开发中的一些本身的体会和想法。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等高频执行的逻辑中尤为要去避免。json

但这并非这段代码的问题所在的重点,当你尝试执行这段代码的时候,会输出以下的错误:设计模式

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变量?app

若是你把value = 1这一行代码注释掉,这段代码就能够正常运行,看上去对于value的赋值操做致使了咱们没法正常访问一个外部的变量,不管这个赋值操做在访问操做以前仍是以后。less

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

简单来讲,命名空间内部若是有对变量的写操做,这个变量在这个命名空间中就会被认为是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。在Stackoverflow上有一个专门的问题叫作“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、一、二、三、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
來源:简书
简书著做权归做者全部,任何形式的转载都请联系做者得到受权并注明出处。
相关文章
相关标签/搜索