1. 首页
  2. Python

觉得 Python 太“简单了”,这些题你能答对几个?

“u003Cdivu003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp3.pstatp.comu002Flargeu002Fdfic-imagehandleru002F570eeb00-b444-4c3c-93ae-15d469c5d98e” img_width=”1200″ img_height=”960″ alt=”觉得 Python 太“简单了”,这些题你能答对几个?” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Cpu003E这篇文章就是记录下做这套题所踩过的坑u003Cu002Fpu003Eu003Cpu003E下面的代码会报错,为什么?u003Cu002Fpu003Eu003Cpreu003Eclass A(object):u003Cbru003E x = 1u003Cbru003E gen = (x for _ in xrange(10)) # gen=(x for _ in range(10))u003Cbru003Eif __name__ == “__main__”:u003Cbru003E print(list(A.gen))u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E答案u003Cu002Fpu003Eu003Cpu003E这个问题是变量作用域问题,在 gen=(x for _ in xrange(10)) 中 gen 是一个 generator,在 generator 中变量有自己的一套作用域,与其余作用域空间相互隔离。因此,将会出现这样的 NameError: name ‘x’ is not defined 的问题,那么解决方案是什么呢?答案是:用 lambda 。u003Cu002Fpu003Eu003Cpreu003Eclass A(object):u003Cbru003E x = 1u003Cbru003E gen = (lambda x: (x for _ in xrange(10)))(x) # gen=(x for _ in range(10))u003Cbru003Eif __name__ == “__main__”:u003Cbru003E print(list(A.gen))u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E2.装饰器u003Cu002Fpu003Eu003Cpu003E描述u003Cu002Fpu003Eu003Cpu003E我想写一个类装饰器用来度量函数u002F方法运行时间u003Cu002Fpu003Eu003Cpreu003Eimport timeu003Cbru003Eclass Timeit(object):u003Cbru003E def __init__(self, func):u003Cbru003E self._wrapped = funcu003Cbru003E def __call__(self, *args, **kws):u003Cbru003E start_time = time.time()u003Cbru003E result = self._wrapped(*args, **kws)u003Cbru003E print(“elapsed time is %s ” % (time.time() – start_time))u003Cbru003E return resultu003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E这个装饰器能够运行在普通函数上:u003Cu002Fpu003Eu003Cpreu003E@Timeitu003Cbru003Edef func():u003Cbru003E time.sleep(1)u003Cbru003E return “invoking function func”u003Cbru003Eif __name__ == ‘__main__’:u003Cbru003E func() # output: elapsed time is 1.00044410133u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E但是运行在方法上会报错,为什么?u003Cu002Fpu003Eu003Cpreu003Eclass A(object):u003Cbru003E @Timeitu003Cbru003E def func(self):u003Cbru003E time.sleep(1)u003Cbru003E return ‘invoking method func’u003Cbru003Eif __name__ == ‘__main__’:u003Cbru003E a = A()u003Cbru003E a.func() # Boom!u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E如果我坚持使用类装饰器,应该如何修改?u003Cu002Fpu003Eu003Cpu003E答案u003Cu002Fpu003Eu003Cpu003E使用类装饰器后,在调用 func 函数的过程中其对应的 instance 并不会传递给 __call__ 方法,造成其 mehtod unbound ,那么解决方法是什么呢?描述符赛高u003Cu002Fpu003Eu003Cpreu003Eclass Timeit(object):u003Cbru003E def __init__(self, func):u003Cbru003E self.func = funcu003Cbru003E def __call__(self, *args, **kwargs):u003Cbru003E print(‘invoking Timer’)u003Cbru003E def __get__(self, instance, owner):u003Cbru003E return lambda *args, **kwargs: self.func(instance, *args, **kwargs)u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E3.Python 调用机制u003Cu002Fpu003Eu003Cpu003E描述u003Cu002Fpu003Eu003Cpu003E我们知道 __call__ 方法可以用来重载圆括号调用,好的,以为问题就这么简单?Naive!u003Cu002Fpu003Eu003Cpreu003Eclass A(object):u003Cbru003E def __call__(self):u003Cbru003E print(“invoking __call__ from A!”)u003Cbru003Eif __name__ == “__main__”:u003Cbru003E a = A()u003Cbru003E a() # output: invoking __call__ from Au003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E现在我们可以看到 a() 似乎等价于 a.__call__() ,看起来很 Easy 对吧,好的,我现在想作死,又写出了如下的代码,u003Cu002Fpu003Eu003Cpreu003Ea.__call__ = lambda: “invoking __call__ from lambda”u003Cbru003Ea.__call__()u003Cbru003E# output:invoking __call__ from lambdau003Cbru003Ea()u003Cbru003E# output:invoking __call__ from A!u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E请大佬们解释下,为什么 a() 没有调用出 a.__call__() (此题由 USTC 王子博前辈提出)u003Cu002Fpu003Eu003Cpu003E答案u003Cu002Fpu003Eu003Cpu003E原因在于,在 Python 中,新式类( new class )的内建特殊方法,和实例的属性字典是相互隔离的,具体可以看看 Python 官方文档对于这一情况的说明u003Cu002Fpu003Eu003Cblockquoteu003Eu003Cpu003EFor 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):u003Cu002Fpu003Eu003Cu002Fblockquoteu003Eu003Cpu003E同时官方也给出了一个例子:u003Cu002Fpu003Eu003Cpreu003Eclass C(object):u003Cbru003E passu003Cbru003Ec = C()u003Cbru003Ec.__len__ = lambda: 5u003Cbru003Elen(c)u003Cbru003E# Traceback (most recent call last):u003Cbru003E# File “”, line 1, in u003Cbru003E# TypeError: object of type ‘C’ has no len()u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E回到我们的例子上来,当我们在执行 a.__call__=lambda:”invoking __call__ from lambda” 时,的确在我们在 a.__dict__ 中新增加了一个 key 为 __call__ 的 item,但是当我们执行 a() 时,因为涉及特殊方法的调用,因此我们的调用过程不会从 a.__dict__ 中寻找属性,而是从 tyee(a).__dict__ 中寻找属性。因此,就会出现如上所述的情况。u003Cu002Fpu003Eu003Cpu003E4.描述符u003Cu002Fpu003Eu003Cpu003E描述u003Cu002Fpu003Eu003Cpu003E我想写一个 Exam 类,其属性 math 为 [0,100] 的整数,若赋值时不在此范围内则抛出异常,我决定用描述符来实现这个需求。u003Cu002Fpu003Eu003Cpreu003Eclass Grade(object):u003Cbru003E def __init__(self):u003Cbru003E self._score = 0u003Cbru003E def __get__(self, instance, owner):u003Cbru003E return self._scoreu003Cbru003E def __set__(self, instance, value):u003Cbru003E if 0 <= 0=”” 75=”” 90=”” value=”” <=”100:” self._score=”value” else:=”” raise=”” valueerror(‘grade=”” must=”” be=”” between=”” and=”” 100’)=”” exam(object):=”” math=”Grade()” def=”” __init__(self,=”” math):=”” self.math=”math” if=”” __name__=”=” ‘__main__’:=”” niche=”Exam(math=90)” print(niche.math)=”” #=”” output=”” :=”” snake=”Exam(math=75)” print(snake.math)=”” snake.math=”120″ output:=”” valueerror:grade=”” 100!<=”” code=””>u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E看起来一切正常。不过这里面有个巨大的问题,尝试说明是什么问题u003Cu002Fpu003Eu003Cpu003E为了解决这个问题,我改写了 Grade 描述符如下:u003Cu002Fpu003Eu003Cpreu003Eclass Grad(object):u003Cbru003E def __init__(self):u003Cbru003E self._grade_pool = {}u003Cbru003E def __get__(self, instance, owner):u003Cbru003E return self._grade_pool.get(instance, None)u003Cbru003E def __set__(self, instance, value):u003Cbru003E if 0 <= value=”” <=”100:” _grade_pool=”self.__dict__.setdefault(‘_grade_pool’,” {})=”” _grade_pool[instance]=”value” else:=”” raise=”” valueerror(“fuck”)<=”” code=””>u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E不过这样会导致更大的问题,请问该怎么解决这个问题?u003Cu002Fpu003Eu003Cpu003E答案u003Cu002Fpu003Eu003Cpu003E1.第一个问题的其实很简单,如果你再运行一次 print(niche.math) 你就会发现,输出值是 120 ,那么这是为什么呢?这就要先从 Python 的调用机制说起了。我们如果调用一个属性,那么其顺序是优先从实例的 __dict__ 里查找,然后如果没有查找到的话,那么一次查询类字典,父类字典,直到彻底查不到为止。好的,现在回到我们的问题,我们发现,在我们的类 Exam中,其 self.math 的调用过程是,首先在实例化后的实例的 __dict__ 中进行查找,没有找到,接着往上一级,在我们的类 Exam 中进行查找,好的找到了,返回。那么这意味着,我们对于 self.math 的所有操作都是对于类变量 math 的操作。因此造成变量污染的问题。那么该则怎么解决呢?很多同志可能会说,恩,在 __set__ 函数中将值设置到具体的实例字典不就行了。u003Cu002Fpu003Eu003Cpu003E那么这样可不可以呢?答案是,很明显不得行啊,至于为什么,就涉及到我们 Python 描述符的机制了,描述符指的是实现了描述符协议的特殊的类,三个描述符协议指的是 __get__ , ‘set‘ , __delete__ 以及 Python 3.6 中新增的 __set_name__ 方法,其中实现了 __get__ 以及 __set__ u002F __delete__ u002F __set_name__ 的是 Data descriptors ,而只实现了 __get__的是 Non-Data descriptor 。那么有什么区别呢,前面说了, 我们如果调用一个属性,那么其顺序是优先从实例的 __dict__ 里查找,然后如果没有查找到的话,那么一次查询类字典,父类字典,直到彻底查不到为止。 但是,这里没有考虑描述符的因素进去,如果将描述符因素考虑进去,那么正确的表述应该是我们如果调用一个属性,那么其顺序是优先从实例的 __dict__ 里查找,然后如果没有查找到的话,那么一次查询类字典,父类字典,直到彻底查不到为止。其中如果在类实例字典中的该属性是一个 Data descriptors ,那么无论实例字典中存在该属性与否,无条件走描述符协议进行调用,在类实例字典中的该属性是一个 Non-Data descriptors ,那么优先调用实例字典中的属性值而不触发描述符协议,如果实例字典中不存在该属性值,那么触发 Non-Data descriptor 的描述符协议。回到之前的问题,我们即使在 __set__ 将具体的属性写入实例字典中,但是由于类字典中存在着 Data descriptors ,因此,我们在调用 math 属性时,依旧会触发描述符协议。u003Cu002Fpu003Eu003Cpu003E2.经过改良的做法,利用 dict 的 key 唯一性,将具体的值与实例进行绑定,但是同时带来了内存泄露的问题。那么为什么会造成内存泄露呢,首先复习下我们的 dict 的特性,dict 最重要的一个特性,就是凡可 hash 的对象皆可为 key ,dict 通过利用的 hash 值的唯一性(严格意义上来讲并不是唯一,而是其 hash 值碰撞几率极小,近似认定其唯一)来保证 key 的不重复性,同时(敲黑板,重点来了),dict 中的 key 引用是强引用类型,会造成对应对象的引用计数的增加,可能造成对象无法被 gc ,从而产生内存泄露。那么这里该怎么解决呢?两种方法u003Cu002Fpu003Eu003Cpu003E第一种:u003Cu002Fpu003Eu003Cpreu003Eclass Grad(object):u003Cbru003E def __init__(self):u003Cbru003E import weakrefu003Cbru003E self._grade_pool = weakref.WeakKeyDictionary()u003Cbru003E def __get__(self, instance, owner):u003Cbru003E return self._grade_pool.get(instance, None)u003Cbru003E def __set__(self, instance, value):u003Cbru003E if 0 <= value=”” <=”100:” _grade_pool=”self.__dict__.setdefault(‘_grade_pool’,” {})=”” _grade_pool[instance]=”value” else:=”” raise=”” valueerror(“fuck”)<=”” code=””>u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003Eweakref 库中的 WeakKeyDictionary 所产生的字典的 key 对于对象的引用是弱引用类型,其不会造成内存引用计数的增加,因此不会造成内存泄露。同理,如果我们为了避免 value 对于对象的强引用,我们可以使用 WeakValueDictionary 。u003Cu002Fpu003Eu003Cpu003E第二种:在 Python 3.6 中,实现的 PEP 487 提案,为描述符新增加了一个协议,我们可以用其来绑定对应的对象:u003Cu002Fpu003Eu003Cpreu003Eclass Grad(object):u003Cbru003E def __get__(self, instance, owner):u003Cbru003E return instance.__dict__[self.key]u003Cbru003E def __set__(self, instance, value):u003Cbru003E if 0 <= value=”” <=”100:” instance.__dict__[self.key]=”value” else:=”” raise=”” valueerror(“fuck”)=”” def=”” __set_name__(self,=”” owner,=”” name):=”” self.key=”nameu003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E这道题涉及的东西比较多,这里给出一点参考链接,invoking-descriptors , Descriptor HowTo Guide , PEP 487 , what`s new in Python 3.6 。u003Cu002Fpu003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp3.pstatp.comu002Flargeu002Fdfic-imagehandleru002F14234cde-e3d4-4b67-95ac-1e9fdd922d77″ img_width=”1023″ img_height=”682″ alt=”觉得 Python 太“简单了”,这些题你能答对几个?” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Cpu003E5.Python 继承机制u003Cu002Fpu003Eu003Cpu003E描述u003Cu002Fpu003Eu003Cpu003E试求出以下代码的输出结果。u003Cu002Fpu003Eu003Cpreu003Eclass Init(object):u003Cbru003E def __init__(self, value):u003Cbru003E self.val = valueu003Cbru003Eclass Add2(Init):u003Cbru003E def __init__(self, val):u003Cbru003E super(Add2, self).__init__(val)u003Cbru003E self.val += 2u003Cbru003Eclass Mul5(Init):u003Cbru003E def __init__(self, val):u003Cbru003E super(Mul5, self).__init__(val)u003Cbru003E self.val *= 5u003Cbru003Eclass Pro(Mul5, Add2):u003Cbru003E passu003Cbru003Eclass Incr(Pro):u003Cbru003E csup = super(Pro)u003Cbru003E def __init__(self, val):u003Cbru003E self.csup.__init__(val)u003Cbru003E self.val += 1u003Cbru003Ep = Incr(5)u003Cbru003Eprint(p.val)u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E答案u003Cu002Fpu003Eu003Cpu003E输出是 36 ,具体可以参考 New-style Classes , multiple-inheritanceu003Cu002Fpu003Eu003Cpu003E6. Python 特殊方法u003Cu002Fpu003Eu003Cpu003E描述u003Cu002Fpu003Eu003Cpu003E我写了一个通过重载 new 方法来实现单例模式的类。u003Cu002Fpu003Eu003Cpreu003Eclass Singleton(object):u003Cbru003E _instance = Noneu003Cbru003E def __new__(cls, *args, **kwargs):u003Cbru003E if cls._instance:u003Cbru003E return cls._instanceu003Cbru003E cls._isntance = cv = object.__new__(cls, *args, **kwargs)u003Cbru003E return cvu003Cbru003Esin1 = Singleton()u003Cbru003Esin2 = Singleton()u003Cbru003Eprint(sin1 is sin2)u003Cbru003E# output: Trueu003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E现在我有一堆类要实现为单例模式,所以我打算照葫芦画瓢写一个元类,这样可以让代码复用:u003Cu002Fpu003Eu003Cpreu003Eclass SingleMeta(type):u003Cbru003E def __init__(cls, name, bases, dict):u003Cbru003E cls._instance = Noneu003Cbru003E __new__o = cls.__new__u003Cbru003E def __new__(cls, *args, **kwargs):u003Cbru003E if cls._instance:u003Cbru003E return cls._instanceu003Cbru003E cls._instance = cv = __new__o(cls, *args, **kwargs)u003Cbru003E return cvu003Cbru003E cls.__new__ = __new__ou003Cbru003Eclass A(object):u003Cbru003E __metaclass__ = SingleMetau003Cbru003Ea1 = A() # what`s the fucku003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E之前用这种方法给 __getattribute__ 打补丁的,下面这段代码能够捕获一切属性调用并打印参数u003Cu002Fpu003Eu003Cpreu003Eclass TraceAttribute(type):u003Cbru003E def __init__(cls, name, bases, dict):u003Cbru003E __getattribute__o = cls.__getattribute__u003Cbru003E def __getattribute__(self, *args, **kwargs):u003Cbru003E print(‘__getattribute__:’, args, kwargs)u003Cbru003E return __getattribute__o(self, *args, **kwargs)u003Cbru003E cls.__getattribute__ = __getattribute__u003Cbru003Eclass A(object): # Python 3 是 class A(object,metaclass=TraceAttribute):u003Cbru003E __metaclass__ = TraceAttributeu003Cbru003E a = 1u003Cbru003E b = 2u003Cbru003Ea = A()u003Cbru003Ea.au003Cbru003E# output: __getattribute__:(‘a’,){}u003Cbru003Ea.bu003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E试解释为什么给 getattribute 打补丁成功,而 new 打补丁失败。u003Cu002Fpu003Eu003Cpu003E如果我坚持使用元类给 new 打补丁来实现单例模式,应该怎么修改?u003Cu002Fpu003Eu003Cpu003E答案u003Cu002Fpu003Eu003Cpu003E其实这是最气人的一点,类里的 __new__ 是一个 staticmethod 因此替换的时候必须以 staticmethod 进行替换。答案如下:u003Cu002Fpu003Eu003Cpreu003Eclass SingleMeta(type):u003Cbru003E def __init__(cls, name, bases, dict):u003Cbru003E cls._instance = Noneu003Cbru003E __new__o = cls.__new__u003Cbru003E @staticmethodu003Cbru003E def __new__(cls, *args, **kwargs):u003Cbru003E if cls._instance:u003Cbru003E return cls._instanceu003Cbru003E cls._instance = cv = __new__o(cls, *args, **kwargs)u003Cbru003E return cvu003Cbru003E cls.__new__ = __new__ou003Cbru003Eclass A(object):u003Cbru003E __metaclass__ = SingleMetau003Cbru003Eprint(A() is A()) # output: Trueu003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E##结语u003Cu002Fpu003Eu003Cpu003E说实话 Python 的动态特性可以让其用众多 black magic 去实现一些很舒服的功能,当然这也对我们对语言特性及坑的掌握也变得更严格了,愿各位 Pythoner 没事阅读官方文档,早日达到 装逼如风,常伴吾身 的境界u003Cu002Fpu003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fdfic-imagehandleru002F19207dc8-c821-4af1-958a-218b45f41856″ img_width=”1200″ img_height=”900″ alt=”觉得 Python 太“简单了”,这些题你能答对几个?” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Cu002Fdivu003E”

原文始发于:觉得 Python 太“简单了”,这些题你能答对几个?

主题测试文章,只做测试使用。发布者:醒百年,转转请注明出处:http://www.cxybcw.com/13574.html

联系我们

13687733322

在线咨询:点击这里给我发消息

邮件:1877088071@qq.com

工作时间:周一至周五,9:30-18:30,节假日休息

QR code