作者:manjusaka

原文链接:http://manjusaka.itscoder.com/2016/11/18/Someone-tell-me-that-you-think-Python-is-simple/

前言

最近觉得 Python 太"简单了",于是在师父川爷面前放肆了一把:"我觉得 Python 是世界上最简单的语言!"。于是川爷嘴角闪过了一丝轻蔑的微笑(内心 OS:Naive,作为一个 Python 开发者,我必须要给你一点人生经验,不然你不知道天高地厚!)于是川爷给我了一份满分 100 分的题,然后这篇文章就是记录下做这套题所踩过的坑。

1.列表生成器

描述

下面的代码会报错,为什么?

class A(object): x = 1 gen = (x for _ in xrange(10)) # gen=(x for _ in range(10))if __name__ == "__main__":print(list(A.gen))

答案

这个问题是变量作用域问题,在gen =( x for _ in xrange (10))中gen是一个generator,在generator中变量有自己的一套作用域,与其余作用域空间相互隔离。因此,将会出现这样的NameError:name ' x ' is not defined的问题,那么解决方案是什么呢?答案是:用 lambda。

class A(object): x = 1 gen = (lambda x: (x for _ in xrange(10)))( x) # gen=(x for _ in range(10))if __name__ == "__main__":print(list(A.gen))

或者这样

class A(object): x = 1 gen = (A.x for _ in xrange(10)) # gen=(x for _ in range(10))if __name__ == "__main__":print(list(A.gen))

补充

感谢评论区几位提出的意见,这里我给一份官方文档的说明吧:

The scope of names defined in a class block is limited to the class block ; it does not extend to the code blocks of methods - this includes comprehensions and generator expressions since they are implemented using a function scope. This means that the following will fail :

class A: a = 42 b = list(a + i for i in range(10))

参考链接 Python 2 Execution-Model:Naming-and-Binding , Python 3 Execution-Model:Resolution-of-Names。据说这是 PEP 227 中新增的提案,我回去会进一步详细考证。

2.装饰器

描述

我想写一个类装饰器用来度量函数/方法运行时间

import timeclassTimeit(object):def __init__(self, func): self._wrapped = funcdef __call__(self, *args, **kws): start_time = time.time() result = self._wrapped(*args, **kws)print("elapsed time is %s " % (time.time() - start_time))return result

这个装饰器能够运行在普通函数上:

@Timeitdef func(): time.sleep(1)return"invoking function func"if __name__ == '__main__': func() # output: elapsed time is 1.00044410133

但是运行在方法上会报错,为什么?

class A(object):@Timeitdef func(self): time.sleep(1)return'invoking method func'if __name__ == '__main__': a = A() a.func() # Boom!

如果我坚持使用类装饰器,应该如何修改?

答案

使用类装饰器后,在调用func函数的过程中其对应的 instance 并不会传递给__call__方法,造成其mehtod unbound ,那么解决方法是什么呢?描述符赛高

classTimeit(object):def __init__(self, func): self.func = funcdef __call__(self, *args, **kwargs):print('invoking Timer')def __get__(self, instance, owner):returnlambda *args, **kwargs: self.func(instance, *args, **kwargs)

3. Python 调用机制

描述

我们知道__call__方法可以用来重载圆括号调用,好的,以为问题就这么简单? Naive !

class A(object):def __call__(self):print("invoking __call__ from A!")if __name__ == "__main__": a = A() a() # output: invoking __call__ from A

现在我们可以看到a ()似乎等价于a.__call__ (),看起来很 Easy 对吧,好的,我现在想作死,又写出了如下的代码,

a.__call__ = lambda: "invoking __call__ from lambda"a.__call__()# output:invoking __call__ from lambdaa()# output:invoking __call__ from A!

请大佬们解释下,为什么a ()没有调用出a.__call__ ()(此题由 USTC 王子博前辈提出 )

答案

原因在于,在 Python 中,新式类( new class )的内建特殊方法,和实例的属性字典是相互隔离的,具体可以看看 Python 官方文档对于这一情况的说明

For new - style classes , implicit invocations of special methods are only guaranteed to work correctly if defined on an object ' s type , not in the object ' s instance dictionary. That behaviour is the reason why the following code raises an exception ( unlike the equivalent example with old - style classes ):

同时官方也给出了一个例子:

class C(object):passc = C()c.__len__ = lambda: 5len(c)# Traceback (most recent call last):# File "<stdin>", line 1, in <module># TypeError: object of type 'C' has no len()

回到我们的例子上来,当我们在执行a.__call__ = lambda :" invoking __call__ from lambda "时,的确在我们在a.__dict__中新增加了一个 key 为__call__的 item ,但是当我们执行a ()时,因为涉及特殊方法的调用,因此我们的调用过程不会从a.__dict__中寻找属性,而是从tyee ( a ). __dict__中寻找属性。因此,就会出现如上所述的情况。

4.描述符

描述

我想写一个 Exam 类,其属性 math 为 [ 0,100 ] 的整数,若赋值时不在此范围内则抛出异常,我决定用描述符来实现这个需求。

classGrade(object):def __init__(self): self._score = 0def __get__(self, instance, owner):return self._scoredef __set__(self, instance, value):if0 <= value <= 100: self._score = valueelse:raiseValueError('grade must be between 0 and 100')classExam(object): math = Grade()def __init__(self, math): self.math = mathif __name__ == '__main__': niche = Exam(math=90)print(niche.math)# output : 90 snake = Exam(math=75)print(snake.math)# output : 75 snake.math = 120# output: ValueError:grade must be between 0 and 100!

看起来一切正常。不过这里面有个巨大的问题,尝试说明是什么问题 为了解决这个问题,我改写了 Grade 描述符如下:

classGrad(object):def __init__(self): self._grade_pool = {}def __get__(self, instance, owner):return self._grade_pool.get(instance, None)def __set__(self, instance, value):if0 <= value <= 100: _grade_pool = self.__dict__.setdefault('_grade_pool', {}) _grade_pool[instance] = valueelse:raiseValueError("fuck")

不过这样会导致更大的问题,请问该怎么解决这个问题?

答案

1. 第一个问题的其实很简单,如果你再运行一次print ( niche.math )你就会发现,输出值是75,那么这是为什么呢?这就要先从 Python 的调用机制说起了。我们如果调用一个属性,那么其顺序是优先从实例的__dict__里查找,然后如果没有查找到的话,那么一次查询类字典,父类字典,直到彻底查不到为止。

好的,现在回到我们的问题,我们发现,在我们的类Exam中,其self.math的调用过程是,首先在实例化后的实例的__dict__中进行查找,没有找到,接着往上一级,在我们的类Exam中进行查找,好的找到了,返回。那么这意味着,我们对于self.math的所有操作都是对于类变量math的操作。因此造成变量污染的问题。那么该则怎么解决呢?很多同志可能会说,恩,在__set__函数中将值设置到具体的实例字典不就行了。

那么这样可不可以呢?答案是,很明显不得行啊,至于为什么,就涉及到我们 Python 描述符的机制了,描述符指的是实现了描述符协议的特殊的类,三个描述符协议指的是__get__, '* set ' ,__delete__以及 Python 3.6 中新增的__set_name__方法,其中实现了__get__以及__set__/__delete__/ __set_name__的是 * Data deors * ,而只实现了__get__的是Non - Data deor

那么有什么区别呢,前面说了, *我们如果调用一个属性,那么其顺序是优先从实例的__dict__里查找,然后如果没有查找到的话,那么一次查询类字典,父类字典,直到彻底查不到为止。

但是,这里没有考虑描述符的因素进去,如果将描述符因素考虑进去,那么正确的表述应该是我们如果调用一个属性,那么其顺序是优先从实例的__dict__里查找,然后如果没有查找到的话,那么一次查询类字典,父类字典,直到彻底查不到为止。其中如果在类实例字典中的该属性是一个 Data deors,那么无论实例字典中存在该属性与否,无条件走描述符协议进行调用,在类实例字典中的该属性是一个Non - Data deors,那么优先调用实例字典中的属性值而不触发描述符协议,如果实例字典中不存在该属性值,那么触发Non - Data deor的描述符协议。回到之前的问题,我们即使在__set__将具体的属性写入实例字典中,但是由于类字典中存在着Data deors,因此,我们在调用math属性时,依旧会触发描述符协议。

2.经过改良的做法,利用dict的 key 唯一性,将具体的值与实例进行绑定,但是同时带来了内存泄露的问题。那么为什么会造成内存泄露呢,首先复习下我们的dict的特性,dict最重要的一个特性,就是凡可 hash 的对象皆可为 key ,dict通过利用的 hash 值的唯一性(严格意义上来讲并不是唯一,而是其 hash 值碰撞几率极小,近似认定其唯一)来保证 key 的不重复性,同时(敲黑板,重点来了),dict中的key引用是强引用类型,会造成对应对象的引用计数的增加,可能造成对象无法被 gc ,从而产生内存泄露。那么这里该怎么解决呢?两种方法 第一种:

classGrad(object):def __init__(self):import weakref self._grade_pool = weakref.WeakKeyDictionary()def __get__(self, instance, owner):return self._grade_pool.get(instance, None)def __set__(self, instance, value):if0 <= value <= 100: _grade_pool = self.__dict__.setdefault('_grade_pool', {}) _grade_pool[instance] = valueelse:raiseValueError("fuck")

weakref 库中的WeakKeyDictionary所产生的字典的 key 对于对象的引用是弱引用类型,其不会造成内存引用计数的增加,因此不会造成内存泄露。同理,如果我们为了避免 value 对于对象的强引用,我们可以使用WeakValueDictionary。

第二种:在 Python 3.6 中,实现的 PEP 487 提案,为描述符新增加了一个协议,我们可以用其来绑定对应的对象:

classGrad(object):def __get__(self, instance, owner):return instance.__dict__[self.key]def __set__(self, instance, value):if0 <= value <= 100: instance.__dict__[self.key] = valueelse:raiseValueError("fuck")def __set_name__(self, owner, name): self.key = name

这道题涉及的东西比较多,这里给出一点参考链接, invoking-deors , Deor HowTo Guide , PEP 487 , what`s new in Python 3.6 。

5. Python 继承机制

描述

试求出以下代码的输出结果。

classInit(object):def __init__(self, value): self.val = valueclassAdd2(Init):def __init__(self, val): super(Add2, self).__init__(val) self.val += 2classMul5(Init):def __init__(self, val): super(Mul5, self).__init__(val) self.val *= 5classPro(Mul5, Add2):passclassIncr(Pro): csup = super(Pro)def __init__(self, val): self.csup.__init__(val) self.val += 1p = Incr(5)print(p.val)

答案

输出是 36 ,具体可以参考 New-style Classes , multiple-inheritance

6. Python 特殊方法

描述

我写了一个通过重载 * new * 方法来实现单例模式的类。

classSingleton(object): _instance = Nonedef __new__(cls, *args, **kwargs):if cls._instance:return cls._instance cls._isntance = cv = object.__new__(cls, *args, **kwargs)return cvsin1 = Singleton()sin2 = Singleton()print(sin1 is sin2)# output: True

现在我有一堆类要实现为单例模式,所以我打算照葫芦画瓢写一个元类,这样可以让代码复用:

classSingleMeta(type):def __init__(cls, name, bases, dict): cls._instance = None __new__o = cls.__new__def __new__(cls, *args, **kwargs):if cls._instance:return cls._instance cls._instance = cv = __new__o(cls, *args, **kwargs)return cv cls.__new__ = __new__class A(object): __metaclass__ = SingleMetaa1 = A() # what`s the fuck

哎呀,好气啊,为啥这会报错啊,我明明之前用这种方法给__getattribute__打补丁的,下面这段代码能够捕获一切属性调用并打印参数

classTraceAttribute(type):def __init__(cls, name, bases, dict): __getattribute__o = cls.__getattribute__def __getattribute__(self, *args, **kwargs):print('__getattribute__:', args, kwargs)return __getattribute__o(self, *args, **kwargs) cls.__getattribute__ = __getattribute__# Python 3 是 class A(object,metaclass=TraceAttribute):class A(object): __metaclass__ = TraceAttribute a = 1 b = 2a = A()a.a# output: __getattribute__:('a',){}a.b

试解释为什么给 * getattribute * 打补丁成功,而 * new * 打补丁失败。 如果我坚持使用元类给 * new * 打补丁来实现单例模式,应该怎么修改?

答案

其实这是最气人的一点,类里的__new__是一个staticmethod因此替换的时候必须以staticmethod进行替换。答案如下:

classSingleMeta(type):def __init__(cls, name, bases, dict): cls._instance = None __new__o = cls.__new__@staticmethoddef __new__(cls, *args, **kwargs):if cls._instance:return cls._instance cls._instance = cv = __new__o(cls, *args, **kwargs)return cv cls.__new__ = __new__class A(object): __metaclass__ = SingleMetaprint(A() is A()) # output: True

结语

感谢师父大人的一套题让我开启新世界的大门,恩,博客上没法艾特,只能传递心意了。说实话 Python 的动态特性可以让其用众多black magic去实现一些很舒服的功能,当然这也对我们对语言特性及坑的掌握也变得更严格了,愿各位 Pythoner 没事阅读官方文档,早日达到装逼如风,常伴吾身的境界。

End

听说你会Python?做几道题看看呗相关推荐

  1. python怎么做项目_听说你没有python项目可做,我教你个方法

    原标题:听说你没有python项目可做,我教你个方法 学习了一段时间的Python,最近出现了"饥荒",感觉需要多看些代码,多学习学习别人做些什么,但却不知道做点什么来进行练习. ...

  2. python可以做什么有趣的东西-您用python做过什么有趣的事?(什么事python)

     前一段时间,我尝试使用Python生成QR码,包括更有趣的动态QR码. 接下来,我将介绍如何实现它. Python MyQR模块支持自定义QR码,并可以生成普通QR码,艺术QR码,动态QR码. 我 ...

  3. python宣传海报_用Python做一个令人发疯的海报

    Python可以做海报? Python真的可以做海报吗? Python做海报,你确定不是在逗我? 重要的问题问了三遍,答案是真的可以! 今天我们就来用Python的一个比较好玩的模块来进行创作---- ...

  4. 小年到了,回家抢票太难,用Python做个脚本12306自动查票以及自动购票....

    今天就是小年了,听说还有人买不到票?不要慌,今天咱们来用Python做一个自动查票抢票的脚本,24小时抢票,谁抢的过你!源码包已打包文件夹获取方式:点击这里[ Python全套资料] 即可获取. 准备 ...

  5. 桔子菌和楼下超市田大爷的角色互换经历–Python做的商品价格语音播报器

    原文链接:http://www.juzicode.com/archives/4635 周末在楼下超市买菜的时候,结完账超市老板田大爷把桔子菌拉到一边神秘兮兮地说:听说你是写程序的? 桔子菌茫然地点点头 ...

  6. 什么是自动化运维?为什么选择Python做自动化运维?

    "Python自动化运维"这个词,想必大家都听说过,但是很多人对它并不了解,也不知道是做什么的,那么你对Python自动化运维了解多少呢?跟着蛋糕往下看. 什么是Python自动化 ...

  7. “晓白”学python-科普篇(2)-人们都用python做什么?

    上一小节里面,老袁给晓白讲了python是什么,python的由来,发展历程,崛起和python的特点.这一小节里面,老袁会告诉晓白人们都用python来做什么. "我刚刚说了,python ...

  8. 一日一技:用Python做游戏有多简单 (2)

    现在用Python来制作游戏越来越方便,虽然某些方面有所限制.但是利用Pygame工具包基本能制作所有的2D游戏在制作的同时对游戏的理解也会更加深刻,因为本质上都是对图片的定位和图片碰撞的判断,从而以 ...

  9. 你到底能用Python做什么?下面是Python的三个主要应用程序。

    如果你正在考虑学习Python–或者你最近开始学习它–你可能会问自己: "我到底能用Python做什么?" 这是一个很难回答的问题,因为Python的应用程序太多了. 但随着时间的 ...

  10. 用Python做一个令人发疯的海报

    Python可以做海报? Python真的可以做海报吗? Python做海报,你确定不是在逗我? 重要的问题问了三遍,答案是真的可以! 今天我们就来用Python的一个比较好玩的模块来进行创作---- ...

最新文章

  1. iOS 屏幕亮度和闪光灯控制
  2. 使用Flow检查React,Redux和React-Redux的全面指南
  3. SAP零售行业解决方案初阶 1
  4. python序列类型-Python(第八课,序列类型)
  5. webpack 的基本使用—— 创建列表隔行变色项目||在项目中安装和配置 webpack
  6. 安装Tomcat6.0
  7. 实现链栈的各种基本运算的算法_LeetCode基础算法题第78篇:如何不用加减号实现两数的加法运算?...
  8. AC自动机:例题与机制详解
  9. java 邮件 tls_通过TLS发送的Java邮件
  10. 长得像鳗鱼的Envirobot,利用传感器检测并追踪水中有害元素
  11. 使用Mapnik生成地形图——thematicmapping.org译文(四)
  12. 超详细的MySQL完全卸载教程
  13. Pytorch 分布式训练
  14. PS制作一寸带白框的证件照
  15. Linux中如何查看Hadoop版本以及Java版本
  16. linux 触摸屏多点触摸改成单点触摸 驱动调试
  17. 压力换算公斤单位换算_常用压力单位换算表
  18. 国内的邮箱哪最好用,个人的邮箱排名?
  19. 今天那个服务器有无限火力,无限火力即将登陆,测试服已出,这次的无限火力有什么不一样?...
  20. Python的namedtuple使用详解

热门文章

  1. 增量式编码器有哪些分类?增量式编码器是如何工作的?
  2. 博客添加音乐插件、网站运行时间、文章阅读次数和网站访客统计
  3. matlab标题斜体_matlab 斜体 正体
  4. PHP如何实现嵌入网页功能思路
  5. 如何使用计算机建模,计算机模拟在数学建模中的应用
  6. GCC编译器使用指北
  7. tair用ldb做分布式存储
  8. Mac系统升级后,无法安装Cornerstone解决办法
  9. 联合国发布2019年《世界人口展望》:人口老化加剧, 到本世纪末地球人口将达109亿...
  10. simics虚拟机+solaris 9 sparc系统运行memory compiler(非常详细)