一篇文章踩遍Python中的坑

文章转载自「Python网络爬虫与数据挖掘」

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

In [1]: class A(object):
   ...:     x = 1
   ...:     gen = (x for _ in xrange(10))
   ...:

In [2]: if __name__ == "__main__":
   ...:     print(list(A.gen))
   ...:
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-2-bb15a85da3e4> in <module>()
      1 if __name__ == "__main__":
----> 2     print(list(A.gen))
      3

<ipython-input-1-45646dbd84a7> in <genexpr>((_,))
      1 class A(object):
      2     x = 1
----> 3     gen = (x for _ in xrange(10))
      4

NameError: global name 'x' is not defined

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

In [7]: class A(object):
   ...:     x = 1
   ...:     gen = (lambda x: (x for _ in xrange(10)))(x)
   ...:

In [9]: if __name__ == "__main__":
    print(list(A.gen))
   ...:
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

装饰器

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

In [10]: import time

In [11]: class Timeit(object):
   ....:     def __init__(self, func):
   ....:         self._wrapped = func
   ....:     def __call__(self, *args, **kws):
   ....:         start_time = time.time()
   ....:         result = self._wrapped(*args, **kws)
   ....:         print("elapsed time is %s ")%(time.time() - start_time)
   ....:         return result
   ....:
# 这个装饰器能够运行在普通函数上:
In [12]: @Timeit
   ....: def func():
   ....:     time.sleep(2)
   ....:     return "invoking function func"
   ....:

In [13]: if __name__ == "__main__":
   ....:     func()
   ....:

elapsed time is 2.00392723083

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

In [14]: class A(object):
   ....:     @Timeit
   ....:     def func(self):
   ....:         time.sleep(1)
   ....:         return "invoking method func"
   ....:

In [15]: if __name__ == "__main__":
   ....:     a = A()
   ....:     a.func()
   ....:
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-15-eb4fb185288b> in <module>()
      1 if __name__ == "__main__":
      2     a = A()
----> 3     a.func()
      4

<ipython-input-11-aedd3f23516b> in __call__(self, *args, **kws)
      4     def __call__(self, *args, **kws):
      5         start_time = time.time()
----> 6         result = self._wrapped(*args, **kws)
      7         print("elapsed time is %s ")%(time.time() - start_time)
      8         return result

TypeError: func() takes exactly 1 argument (0 given)

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

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

In [16]: class Timeit(object):
   ....:     def __init__(self, func):
   ....:         self.func = func
   ....:     def __call__(self, *args, **kwargs):
   ....:         print("invoking Timer")
   ....:     def __get__(self, instance, owner):
   ....:         return lambda *args, **kwargs: self.func(instance, *args, **kwargs)
   ....:

In [17]: class A(object):
   ....:     @Timeit
   ....:     def func(self):
   ....:         time.sleep(1)
   ....:         return "invoking method func"
   ....:

In [18]: if __name__ == "__main__":
   ....:     a = A()
   ....:     a.func()
   ....:

Python调用机制

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

In [19]: class A(object):
   ....:     def __call__(self):
   ....:         print("invoking __call__ from A!")
   ....:

In [20]: if __name__ == "__main__":
   ....:     a = A()
   ....:     a()
   ....:
invoking __call__ from A!

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

In [21]: a.__call__ = lambda: "invoking __call__ from lambda"

In [22]: a.__call__()
Out[22]: 'invoking __call__ from lambda'

In [23]: a()
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):

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

In [24]: class C(object):
   ....:     pass
   ....:

In [25]: c = C()

In [26]: c.__len__ = lambda: 5

In [27]: len(c)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-27-c6494b964a51> in <module>()
----> 1 len(c)

TypeError: object of type 'C' has no len()

In [28]: c.__len__
Out[28]: <function __main__.<lambda>>

In [29]: c.__len__()
Out[29]: 5

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

原文:http://yangcongchufang.com/%E9%AB%98%E7%BA%A7python%E7%BC%96%E7%A8%8B%E5%9F%BA%E7%A1%80/python-someone-tell-me-not-simple.html

原文发布于微信公众号 - 日常学python(daily_learn)

原文发表时间:2018-11-12

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Redis源码学习系列

Redis源码学习之字典

在字典结构体中,包含了一组字典函数(dictType),通过封装的方法处理对应的操作,通常在字典初始化的时候对其进行配置。

3371
来自专栏移动端开发

Swift Runtime ?

你肯定也想过       在OC中相信每一个iOS开发都知道Runtime, 现在Swift也更新到4.0版本了,要是你也学习过Swift的话你可能也会想过这样...

1.4K7
来自专栏大内老A

WCF技术剖析之十二:数据契约(Data Contract)和数据契约序列化器(DataContractSerializer)

大部分的系统都是以数据为中心的(Data Central),功能的实现表现在对相关数据的正确处理。而数据本身,是有效信息的载体,在不同的环境具有不同的表示。一个...

3578
来自专栏欧阳大哥的轮子

深入解构iOS的block闭包实现原理

在iOS4出来后,苹果公司在OC中推出了block机制(也许更早就有了)。并且在后续的版本中大量的推广和使用了这项技术,比如对视图动画API的改版,比如GCD技...

963
来自专栏java初学

海量数据处理

49414
来自专栏开源优测

python unittest之异常测试

前言 在python unittest测试框架中,提供了一系列的方法用于验证程序的异常。 下面和我一起看看在unittest中,如何使用异常验证来进行断言,完成...

3836
来自专栏运维小白

linux基础(day28)

9.6 awk(上) awk工具 head -n2 test.txt|awk -F ':' '{print $1}' head -n2 test.txt|awk...

2146
来自专栏Golang语言社区

动手实现一个JSON验证器(上)

分析 既然要验证JSON的有效性,那么必然需要清楚的知道JSON格式,这个在JSON官网已经给我们画出来了: ? ? ? ? ? 从官方的图上面可以看出,JSO...

5297
来自专栏mathor

2017百度之星资格赛

 题目大意是说,给你n个熊(那么他们编号默认就是1...n),然后给定m行输入,每一行都有u,v,w三个变量,表示u熊和v熊之间有强关系w,然后问你至少需...

772
来自专栏mathor

LeetCode55. 跳跃游戏

 首先创建一个index数组,存储当前位置最大可达的数组下标,就以样例1来举例,输入是[2,3,1,1,4],那么对应的这个index数组就是[2,4,3...

2272

扫码关注云+社区

领取腾讯云代金券