36 Python 让你对Python绝望的几个题

Python - 让你感觉什么都不会的几个题…

题目1 - 4来源:

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

题目5来源:

https://segmentfault.com/a/1190000000618513,

https://www.toptal.com/python/interview-questions

看了这几个题,差点对Python绝望…慎入…

1 列表生成器

#!/usr/bin/python
# -*- coding: utf-8 -*-
class Foo(object):
    x = 1
    y = [x for _ in xrange(10)]
    z = (x for _ in xrange(10))

print Foo.x         #1
print Foo.y         #[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
a = Foo.z   
print a             #<generator object <genexpr> at 0x0000000002FC0990>
try:
    print a.next()  #global name 'x' is not defined,why?
except Exception,e:
    print e

为什么x没有定义?

这个是变量作用域的问题,在 z =( x for _ in xrange (10)) 中 x 是一个 generator(生成器) ,在 generator 中变量有自己的作用域,与其它作用域空间相互隔离。因此会出现NameError:name ’ x ’ is not defined

官方解释

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 Foo(object):
    x = 1
    y = [x for _ in xrange(10)]                #列表解析不会报错哦...
    z1 = (Foo.x for _ in xrange(10))           #解决方法1
    z2 = (lambda x: (x for _ in xrange(10)))(x)#解决方法2,lambda不会隔离作用域空间 

a = Foo.z1   
print a             #<generator object <genexpr> at 0x0000000002FC09D8>
print a.next()      #1
a = Foo.z2  
print a             #<generator object <genexpr> at 0x0000000002FC0A20>
print a.next()      #0

2 Python调用机制

class A(object):
    def __call__(self):
        print("__call__ from A!")

a = A()
a()                    #__call__ from A
a.__call__ = lambda : "__call__ from lambda"
print a.__call__()     #__call__ from lambda 
a()                    #__call__ from A

为什么a()和a.__call__ ()的结果不一样…

在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)

当执行a.__call__ = lambda :"__call__ from lambda "时,在a.__dict__中新增了key为'__call__'的成员,
但是当执行a()时,因为涉及特殊方法的调用,不会从 a.__dict__中寻找属性,而是从type(a).__dict__中寻找属性。
因此,就会出现如上所述的情况。

a = A()
print a.__dict__       #{}
a()                    #__call__ from A
a.__call__ = lambda : "__call__ from lambda"
print a.__dict__       #{'__call__': <function <lambda> at 0x0000000002F6E908>},相当于实例方法
print a.__call__()     #__call__ from lambda,从实例字典中查找的 
a()                    #__call__ from A,从类字典中查找的

3 描述符

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

这个问题见过,直接写出了最终答案。详细解释见http://blog.csdn.net/lis_12/article/details/53453665

class descript(object):
    def __init__(self,var_name):
        self.var_name = var_name

    def __get__(self,obj,type):
        return obj.__dict__.get(self.var_name)

    def __set__(self,obj,value):
        print value
        if not(0 <= value <= 100):
            raise Exception('value in [0,100]')
        obj.__dict__[self.var_name] = value

class Foo(object):
    math = descript('x')
    def __init__(self,math = 0):
        self.math = math

f = Foo(10)
f.math = 101

4 Python继承机制

#!/usr/bin/python
# -*- coding: utf-8 -*-
class Init(object):
    def __init__(self, value):
        print "Init"
        self.val = value

class Add2(Init):
    def __init__(self, val):
        print "Add2"
        super(Add2, self).__init__(val)
        self.val += 2

class Mul5(Init):
    def __init__(self, val):
        print "Mul5"
        super(Mul5, self).__init__(val)
        self.val *= 5

class Pro(Mul5, Add2):
    def __init__(self,val):  #与pass基本一致,多了个打印语句
        print "Pro __init__"
        super(Pro,self).__init__(val)

class Incr(Pro):
    csup = super(Pro)              #super(type) -> unbound super object
    def __init__(self, val):
        print self.csup,type(self.csup)
        self.csup.__init__(val)   #与super(Incr,self).__init(val)等价
        self.val += 1

p = Incr(5)
print p.val

答案,36。

多重继承还是值得深究的,研究过,也会…哈哈

详细解释见

  1. MRO - http://blog.csdn.net/lis_12/article/details/52859376
  2. super()的用法 - http://blog.csdn.net/lis_12/article/details/52870728

5 以下代码的输出是什么?

问题1

class Parent(object):
    x = 1

class Child1(Parent):
    pass

class Child2(Parent):
    pass

print Parent.x, Child1.x, Child2.x
Child1.x = 2
print Parent.x, Child1.x, Child2.x
Parent.x = 3
print Parent.x, Child1.x, Child2.x

答案

以上代码的输出是

1 1 1
1 2 1
3 2 3

最后一行的输出是 3 2 3 而不是 3 2 1。为什么改变了 Parent.x 的值还会改变 Child2.x 的值,但是 Child1.x 值却没有改变?

在Python 中,类变量在内部是作为字典处理的。如果一个变量的名字没有在当前类的字典中发现,将按照MRO在其父类中查找,直到被引用的变量被找到(如果这个被引用的变量名既没有在自己所在的类又没有在父类中找到,会引发 AttributeError 异常 )。

因此,第一个 print 语句的输出是 1 1 1

如果任何它的子类重写了该值(如执行语句 Child1.x = 2),该值仅在子类中被改变(相当于给Child1添加了名为’x’的类属性)。这就是为什么第二个 print 语句的输出是 1 2 1

如果该值在父类中被改变(如执行语句 Parent.x = 3),这个改变会影响到任何未重写该值的子类(在这个示例中被影响的子类是 Child2)。这就是为什么第三个 print 输出是 3 2 3

问题2

list = ['a', 'b', 'c', 'd', 'e']
print list[10:]

答案: [],并且不会触发IndexError异常。

当访问一个超过列表索引值的成员将导致 IndexError(如访问上述列表的 list[10])。但是,试图访问一个列表的以超出列表成员数作为开始索引的切片将不会导致 IndexError,仅会返回一个空列表。

问题3

def multipliers():
    return [lambda x : i * x for i in range(4)]

print [m(2) for m in multipliers()]

详解见http://blog.csdn.net/lis_12/article/details/52915711

如何修改 multipliers 的定义来产生期望的结果

答案

以上代码的输出是 [6, 6, 6, 6] (而不是 [0, 2, 4, 6])。

原因是 Python 的闭包的后期绑定导致的 late binding,这意味着在闭包中的变量是在内部函数被调用的时候被查找。当multipliers() 返回的函数被调用,i 的值是在被调用时的作用域中查找,到那时,for 循环已经完成,i 最后的值是 3,因此,每个返回的函数中i的值都是 3。

(顺便说下,正如在 The Hitchhiker’s Guide to Python 中指出的,一个 lambda 表达式创建的函数不是特殊的,和使用一个普通的 def 创建的函数是一样的。)

这里有两种方法解决这个问题。

最普遍的解决方案是创建一个闭包,通过使用默认参数立即绑定它的参数。如:

def multipliers():
    return [lambda x, i=i : i * x for i in range(4)]

另外一个选择是,你可以使用 functools.partial 函数:

'''这个有点麻烦- -'''
from functools import partial
from operator import mul

def multipliers():
    return [partial(mul, i) for i in range(4)]

问题4

尽量不要把可变类型设为默认参数…详解见http://blog.csdn.net/lis_12/article/details/52693333

def extendList(val, list_var=[]):
    list_var.append(val)
    return list_var

list1 = extendList(10)
list2 = extendList(123,[])
list3 = extendList('a')

print "list1 = %s" % list1
print "list2 = %s" % list2
print "list3 = %s" % list3

以上代码的输出为:

list1 = [10, 'a']
list2 = [123]
list3 = [10, 'a']

许多人会认为 list1 应该等于 [10]list3 应该等于 ['a']list_varextendList 被调用时会被设置成默认值 []

但是,实际情况是默认列表list_var仅在函数被定义时创建一次。当调用extendList()且没有指定list_var参数时,使用的是同一个列表。

因此,list1list3 操作的是同一个列表。而 list2操作的是它创建的列表。

extendList()函数可做如下修改

def extendList(val, list_var=None):
    if list_var is None:
        list_var = []
    list_var.append(val)
    return list_var
list1 = extendList(10)
list2 = extendList(123,[])
list3 = extendList('a')
print "list1 = %s" % list1
print "list2 = %s" % list2
print "list3 = %s" % list3
'''
list1 = [10]
list2 = [123]
list3 = ['a']
'''

总结…

是谁说Python简单的…直接打死得了!!!


转载请标明出处,原文地址(http://blog.csdn.net/lis_12/article/details/54633328).

如果觉得本文对您有帮助,请点击‘顶’支持一下,您的支持是我写作最大的动力,谢谢。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值