Python进阶(一)

博客链接:http://inarrater.com/2016/06/30/pythonadvance1/

这周听了三节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 <module>
    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的例子,尽量避免使用对象的方法来作为一个回调。

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

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

PPS:刚刚开始使用MarkDown,一些语法还不熟悉,但是用它写起这种包含代码的文章来说还是非常舒服的,这篇开头让我体会到了这一点。所以说,只有程序员这种geek生物才会喜欢Hexo等基于MarkDown需要generate成静态网页的博客。。。

2016年6月30日于杭州家中

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,639评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,277评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,221评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,474评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,570评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,816评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,957评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,718评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,176评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,511评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,646评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,322评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,934评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,755评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,987评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,358评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,514评论 2 348

推荐阅读更多精彩内容

  • 1.元类 1.1.1类也是对象 在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。在Python中这...
    TENG书阅读 1,255评论 0 3
  • http://python.jobbole.com/85231/ 关于专业技能写完项目接着写写一名3年工作经验的J...
    燕京博士阅读 7,557评论 1 118
  • 虽然是自己转载的但是是真的好的一篇图文并茂的对垃圾回收机制的讲解!!! 先来个概述,第二部分的画述才是厉害的。 G...
    东皇Amrzs阅读 118,610评论 13 176
  • 如果有一天我离开了,谁还会记得我? 同事,很快会连我的名字都忘记。 泛泛之交,甚至都不知道我已离去。 朋友,很难说...
    芳草有情阅读 237评论 1 2
  • 一直不知道什么是家,最近一个人待的久了才明白原来家很简单。家不是高楼大厦豪华装修,也不是灯红酒绿美女如群。家是...
    陈泽萧阅读 628评论 1 4