代码阅读理解

10. 代码阅读与编写

10.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

考察点
这个问题考察的是你对类属性的理解,x是Parent 的类属性,Child1 和 Child2 虽然继承了Parent, 但他们两个都没有x这个属性,这一点可以通过print(Child1.__dict__)来证明,既然Child1 和 Child2 没有x属性,为什么能输出Child1.x呢? 这是因为当在Child1中招不到x这个属性时就会去它的父类中去寻找,这样就找打了Parent.x

执行了Child1.x = 2 , 就相当于为Child1 这个类增加了x属性,下次输出Child1.x时结果就是2,但 Child2 仍然没有x属性,因此得到依然是Parent.x 的值

10.2 闭包理解考察

难度指数: ★★★
重要指数: ★★★★★

题目要求
以下的代码的输出将是什么? 说出你的答案并解释?

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

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

有些人可能会回答[0, 2, 4, 6], 有些人则干脆不知道如何回答,正确的答案是[6, 6, 6, 6]

这个题目考察了lambda的用法,和闭包的概念,且听我娓娓道来。

  1. multipliers函数返回了一个列表,列表里有几个元素呢?答案是4个,列表推导式的for循环执行了4次,因此列表的长度是4
  2. 第一步中生成的列表里的元素都是什么类型的呢?答案是函数,lambda表达式的结果是一个函数,这个函数有一个参数是x, 返回值是x * i
  3. 列表里有4个函数,函数返回值是x * i, x要等到调用执行函数时才能确定,而i是已经确定的,关键是i是多少?
lst = multipliers()
print(lst)      # <function multipliers.<locals>.<listcomp>.<lambda> at 0x00000254D6C85BF8>

这段代码是上面三点说明的一个印证,现在的关键是确定这4个函数中i的值是多少,有人坚持认为lst[0]这个函数中i的值是0, lst[1]这个函数中的i是1, 因为创建这个函数的过程中,i参与了for循环,i的值就是从0 变化到 3,这样理解只对了一半,我们要明白,生成的这4个函数都是闭包,而闭包内的i是对外部作用域里i的一个引用,当for循环结束时,i的值已经变成3, 因此这4个函数内的i都是3。

解决了multipliers的返回值和列表里函数内部的i的取值问题,再来看最后一步

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

仍然是一个列表推导式,for循环过程中,对multipliers函数返回的列表进行遍历,m就是列表里的函数,传入的x = 2, 而i等于3, 2*3 = 6, 因此最终结果是[6, 6, 6, 6]

10.3 遍历文件夹

难度指数: ★★
重要指数: ★★★★

题目要求:
实现下面的函数

import os

def print_directory_contents(path):
    """
    输出文件夹中的文件路径,如果文件夹中存在文件夹,则向深层遍历
    :param path:
    :return:
    """
    pass

工作中,遍历文件夹的操作还是比较常见的,这个题目考察的是你对os.path这个模块的掌握以及对递归函数的理解运用能力,这种题目,只有靠多练了

import os

def print_directory_contents(path):
    """
    输出文件夹中的文件路径,如果文件夹中存在文件夹,则向深层遍历
    :param path:
    :return:
    """
    for chlid in os.listdir(path):
        chlid_path = os.path.join(path, chlid)

        if os.path.isdir(chlid_path):       # 如果chlid_path 是文件夹
            print_directory_contents(chlid_path)
        else:
            print(chlid_path)
  1. os.listdir 返回一个文件夹下的所有文件,包括文件夹的名字
  2. os.path.join 拼接两个文件地址
  3. os.path.isdir 判断地址是否为一个文件夹

这三个函数一定要掌握

10.4 推导式考察

难度指数: ★★★
重要指数: ★★★★

题目要求

阅读下面的代码,写出A0,A1至An的最终值

A0 = dict(zip(('a','b','c','d','e'),(1,2,3,4,5)))
A1 = range(10)
A2 = [i for i in A1 if i in A0]
A3 = [A0[s] for s in A0]
A4 = [i for i in A1 if i in A3]
A5 = {i:i*i for i in A1}
A6 = [[i,i*i] for i in A1]

这个题目要考察的,是你对推导式的理解,以及zip,range 这两个内置函数的理解

对A0的理解
先来看这行代码

print(dict([(3, 4)]))    # {3: 4} 

dict 函数可以将一个可迭代对象转为字典,只要能满足下面的形式

dict(iterable) -> new dictionary initialized as if via:
    d = {}
    for k, v in iterable:
        d[k] = v

[(3, 4)],这个列表里的元素是元组,且元组里有两个元素,满足for k, v in iterable这样的语句要求。

zip(('a','b','c','d','e'),(1,2,3,4,5)) 返回的,也是一个可迭代对象

for key, value in zip(('a','b','c','d','e'),(1,2,3,4,5)):
    print(key, value)

也满足dict函数的参数要求,因此可以转换为字典,其结果是{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}

A1的理解
python2中range函数返回的是一个列表,但在python3中,是一个生成器,print出来的结果就是 range(10)

A2的理解
[i for i in A1 if i in A0] , 要拆分三部分来看
第一部分, i
第二部分, for i in A1
第三部分, if i in A0

对A1 进行比例,如果 i 在A0中,那么就放入到列表中, 理解的顺序是2, 3, 1, 由于字典的key都是字符串,第二部分循环时,i的值都是int类型,不在字典中,A2 的结果空列表

A3的理解
[A0[s] for s in A0], 拆成两部分
第一部分, A0[s]
第二部分, for s in A0

对A0进行遍历,获取A0[S]的值放入列表中,理解的顺序是 2 1, 结果过是[1, 2, 3, 4, 5]

A4的理解
[i for i in A1 if i in A3], 拆成三部分
第一部分, i
第二部分, for i in A1
第三部分, if i in A3

对A1进行遍历,如果i 在A3中,则将i放入列表中, 理解顺序是2 3 1, 第二部分进行for循环时产生的整数从0到9,只有1到5在A3中,因此最终结果是 [1, 2, 3, 4, 5]

A5的理解
{i:ii for i in A1} 这是一个字典推导式,拆成两部分理解
第一部分, i:i
i
第二部分, for i in A1

对A1进行遍历,将key-value对 i: i*i方法字典中,理解顺序是2 1, 遍历过程中,i从0变化到9, 以i等于5为例,5:5*5 放入字典中,key=5, value=25, 最终结果是{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}

A6的理解
[[i,ii] for i in A1] ,拆成两部分理解
第一部分, [i,i
i]
第二部分, for i in A1
对A1 进行遍历,i从0变化到9, 以i= 5为例,生成的数据是[5, 25], 将[5, 25]放入列表中,最终结果是[[0, 0], [1, 1], [2, 4], [3, 9], [4, 16], [5, 25], [6, 36], [7, 49], [8, 64], [9, 81]]

10.5 可变对象做函数默认参数

难度指数: ★★★
重要指数: ★★★★

题目要求:

下面代码会输出什么?

def f(x, lst=[]):
    for i in range(x):
        lst.append(i*i)

    print(lst)

f(2)
f(3, [3, 2, 1])
f(3)

f(2) 输出 [0, 1]
f(3, [3, 2, 1]) 输出 [3, 2, 1, 0, 1, 4]
这两个都比较好理解,难理解的是f(3),不懂的人认为应该输出[0, 1, 4], 但实际结果是[0, 1, 0, 1, 4] , 多出了0 和 1, 原因在当可变对象作为函数的参数的默认值时,只会在函数定义的时候创建一次,此后,如果这个如果你不传这个参数,那么就使用默认的参数值,但这个值只创建一次,f(2) 和 f(3) 两次调用时用的都是同一个列表,f(2)执行时已经向列表里添加0,1, f(3) 执行时,又加入了0 ,1 , 4 ,因此最终结果是[0, 1, 0, 1, 4]

10.6 多继承与super考察

难度指数: ★★★
重要指数: ★★★★

题目要求:

写出下面代码的执行结果

class A(object):
    def go(self):
        print("go A go!")

    def stop(self):
        print("stop A stop!")

    def pause(self):
        raise Exception("Not Implemented")

class B(A):
    def go(self):
        super(B, self).go()
        print("go B go!")

class C(A):
    def go(self):
        super(C, self).go()
        print("go C go!")

    def stop(self):
        super(C, self).stop()
        print("stop C stop!")

class D(B,C):
    def go(self):
        super(D, self).go()
        print("go D go!")

    def stop(self):
        super(D, self).stop()
        print("stop D stop!")

    def pause(self):
        print("wait D wait!")

class E(B,C): pass

a = A()
b = B()
c = C()
d = D()
e = E()

# 说明下列代码的输出结果
a.go()  
b.go()
c.go()
d.go()
e.go()

a.stop()
b.stop()
c.stop()
d.stop()
e.stop()

a.pause()
b.pause()
c.pause()
d.pause()
e.pause()

这个题目考察的是多继承与super用法,首先要明确,这个继承关系里出现了菱形继承
A
B C
D/E

先来看go方法调用

  1. a.go() 输出结果go A go!, 没有什么疑问
  2. b.go() 输出【go A go!】 【go B go!】 先使用super调用了父类A的go方法,然后输出go B go!
  3. c.go() 输出【go A go!】【go C go!】,原理同b.go()
  4. d.go() ,这一步是容易出错的地方,D继承了B和C, 使用super调用父类的go方法时,会调用B和C的go方法,因此一定会输出【go B go!】【go C go!】,但B和C的go方法里又调用了A的go方法,那么要输出两次【go A go!】么,答案是否定的,只会输出一次,而正是super的作用,super解决了菱形继承时父类方法重复调用的问题,尽管B 和 C都调用了A的go,但只会执行一次,最后输出【go D go!】
  5. e.go(), e自身没有实现go方法,因此会执行B和C的go方法,输出【go A go!】【go B go!】【go C go!】

再来看stop方法调用

  1. a.stop() 输出 【stop A stop!】
  2. b.stop(), B没有实现stop方法,会直接调用父类A的stop方法输出 【stop A stop!】
  3. c.stop(), 输出 【stop A stop!】 【stop C stop!】
  4. d.stop(), 输出 【stop A stop!】 【stop C stop!】 【stop D stop!】
  5. e.stop(), 输出 【stop A stop!】 【stop C stop!】

最后来看pause方法调用

  1. a.pause() 会引发异常,函数里使用raise抛出异常
  2. b.pause() ,B没有实现pause方法,会调用父类的pause方法,同样会引发异常
  3. c.pause() 引发异常,道理同b.pause()
  4. d.pause() 输出wait D wait!
  5. e.pause() 引发异常,道理同b.pause()

扫描关注, 与我技术互动

QQ交流群: 211426309

加入知识星球, 每天收获更多精彩内容

分享日常研究的python技术和遇到的问题及解决方案