python面试题(持续)

python语言特性(数据结构、语法结构)

1. range和xrange

python2.x中,range和xrange有什么区别?

for i in range(0, 20)

for i in xrange(0, 20)

答:

  • 两者都用于for循环

  • range一开始就生成完整的list;xrange返回一个xrange object,且这个对象是iterable,要想返回list,需要类型转换list(xrange(1, 4))

由于xrange object是按需生成单个元素,而不是像range那样创建整个list。实际上,xrange由于是在循环中被调用时才会生成元素,因此无论循环多少次,只有当前一个元素占用了内存空间,且每次循环占用的都是相同的单个元素空间。因此,我们可以粗略认为,相同n个元素,range占用的空间是xrange的n倍。因此,在循环很大的情况下,xrange的高效率和快速将表现的很明显:占用的内存空间将更小,速度更快,内存性能更好。测试一下range和xrange的执行时间。

# python2环境下执行
import timeit

print(timeit.timeit('for i in range(10000000): pass',number=1)) 
# 0.49290895462036133

print(timeit.timeit('for i in xrange(10000000): pass',number=1))
# 0.2595210075378418

在大量循环的条件下,可以看到xrange的高效率是很明显的。

  • 在python3中,移除了range的实现,保留了xrange的实现,并将xrange重新命名成range
dir(range(1, 4))   

list(range(1, 4))  # range对象类型转换成list

range object在Python3里增加了新的attributes,’count’, ‘index’, ‘start’, ‘step’, ‘stop’,且能支持slicing。Python 3的range()在xrange()的基础上变得更强大了。

2. 迭代器和生成器

2.1 定义

stack overflow的解答

容器、迭代对象、迭代器、生成器、生成器表达式

2.2 生成器的创建

问: 将列表生成式中[]改成()之后,数据结构是否改变?
答:是,从列表变为生成器

L = [x*x for x in range(10)]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

g = (x*x for x in range(10))
g
# <generator object <genexpr> at 0x11073bfc0>

通过列表生成式,可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含百万元素的列表,不仅占用很大的内存空间,如果我们只需要访问前面的几个元素,后面大部分元素所占的空间都是浪费的。因此,我们没必要创建完整的列表(节省大量内存空间)。在Python中,我们可以采用生成器generator,边循环边计算的机制。

3. 协程

简单点说,协程是进程和线程的升级版。进程和线程都面临着内核态和用户态切换的问题,而耗费许多切换时间;而协程就是用户自己控制切换的时机,不再需要陷入系统的内核态。

Python里最常见的yield就是协程的思想,可以查看生成器-yield那一节。

4. lambda表达式

lambda一个匿名函数,与后面的函数式编程联系紧密。知乎-lambda

5. 函数式编程(python内置高阶函数reduce、map、filter)

函数式编程的解释

  • filter函数的功能相当于过滤器。调用一个布尔函数bool_func来遍历seq中的每个元素,返回一个使bool_seq返回值为true的元素序列。
a = [1,2,3,4,5,6,7]
b = filter(lambda x: x > 5, a)

print(b)
# [6,7]
  • map是对一个序列的每个项依次执行函数,下面是对一个序列每个项都乘以2:
a = map(lambda x:x*2, [1,2,3])
list(a)
# [2, 4, 6]
  • reduce是对一个序列的相邻项两两调用函数。下面是求3的阶乘:
reduce(lambda x,y:x*y, range(1,4))
# 6

6. Python里的浅拷贝和深拷贝

赋值(引用),copy(),deepcopy()的区别

import copy

a = [1, 2, 3, 4, ['a', 'b']]  # 创建原始对象

b = a                         # 赋值,对内存中对象的引用
c = copy.copy(a)              # 对象拷贝,浅拷贝
d = copy.deepcopy(a)          # 对象拷贝,深拷贝

a.append(5)                   # 修改对象a
a[4].append('c')              # 修改对象a中的['a', 'b']数组对象

print('a = ', a)
print('b = ', b)
print('c = ', c)
print('d = ', d)

'''
输出结果:
a =  [1, 2, 3, 4, ['a', 'b', 'c'], 5]
b =  [1, 2, 3, 4, ['a', 'b', 'c'], 5]
c =  [1, 2, 3, 4, ['a', 'b', 'c']]
d =  [1, 2, 3, 4, ['a', 'b']]
'''

参考:Python学习笔记(5):赋值、浅拷贝、深拷贝

7. Python2和Python3的区别

Python2和3的区别

8. is,is not, ==, =

is、is not,对比内存地址(id)。

==,对比值,包括数值和布尔值。

=,赋值。

9. 闭包

闭包(closure)是函数式编程重要的语法结构。闭包是一种代码的组织结构,它同样提高了代码的复用性。

内层函数引用了其外部作用域的变量(参数),然后返回内层函数的情况,称为闭包(Closure)。创建一个闭包必须满足以下几点:

  • 必须有一个内嵌函数
  • 内嵌函数必须引用外部函数中的变量,外层空间中被引用的变量叫做内层函数的环境变量
  • 外部函数的返回值必须是内嵌函数
  • 环境变量和内层非全局函数一起构成了闭包

函数运行后并不会被撤销:当函数运行完后,并不被销毁,而是继续留在内存空间里。这个功能类似类里的类变量,只不过迁移到了函数上。

# 简单理解闭包:内层函数会记住其外层作用域的事实,即内层函数会记住外层函数的变量

nums_in_global=[15,2,3,9,3.2]          # 声明一个全局变量

def foo1(nums_in_function):            # 设计一个外层函数
    print('nums_in_function此时在foo1中,可以被访问:', nums_in_function)   
    def foo2():                        
        return max(nums_in_function)   
        #return max(nums_in_global)    
    return foo2                        

foo1([5,3,8])()                               

# print(nums_in_function)              

闭包就像个空心球一样,你知道外面和里面,但你不知道中间是什么样。

10. Python的各种推导式(列表推导式、字典推导式、集合推导式)

http://blog.youkuaiyun.com/yjk13703623757/article/details/79490476

11. Python自省

自省就是面向对象的语言所写的程序在运行时,就能知道对象的类型。也就是程序运行时能够获得对象的类型。比如type(),dir(),getattr(),hasattr(),isinstance()。

a = [1, 2, 3]
b = {'a':1, 'b':2, 'c':3}
c = True

print(type(a), type(b), type(c)) 
print(isinstance(a, list))  

# <class 'list'> <class 'dict'> <class 'bool'>
# True

12. 字符串格式化:%和.format

对于%,最烦人的是它无法同时传递一个变量和元组。你可能会想下面的代码不会有什么问题:

name = 123

print("hi there %s" %name)

但是,如果name恰好是元祖(1,2,3),它将会抛出一个TypeError异常。

name = (1,2,3)

print("hi there %s" %name)

# TypeError: not all arguments converted during string formatting

为了保证它总是正确的,你必须如下做,但是有点丑。

name = (1,2,3)

print("hi there %s" %(name, ))   # 提供一个单元素的元组而不是一个参数

.format在许多方面看起来更便利,它没有这些问题,format好看多了。

name = (1,2,3)

print("hi there {}".format(name))

format的具体用法:python之字符串格式化format

13. GIL全局解释器锁

线程全局锁(Global Interpreter Lock),即Python为了保证线程安全而采取独立线程运行的限制。即在同一时间,一个CPU核只能运行一个线程。对于IO密集型任务,python的多线程起作用;但对于CPU密集型任务,python的多线程几乎占不到任何优势,还有可能因为互相争夺资源而变慢。见Python最难的问题

解决办法:多进程和协程(协程也只是单核CPU,但是能减小切换代价提升性能)。

14. Python中的作用域

在Python中,一个变量的作用域总是由代码中被赋值的地方所决定的。

当遇到一个变量,Python会按照下面的顺序进行搜索:本地作用域(Local)→ 当前作用域被嵌入的本地作用域(Enclosing locals)→ 全局/模块作用域(Global)→ 内置作用域(Built-in)

15. Python中read,readline,readlines的区别

  • read,读取整个文件
  • readline,读取下一行,使用生成器方法
  • readlines,读取整个文件到一个迭代器以供我们遍历

详见:http://blog.youkuaiyun.com/yjk13703623757/article/details/79502998

16. Python中单下划线和双下划线的区别

class MyClass():
    def __init__(self):
        self.__superprivate = "Hello"
        self._semiprivate = ", world!"

mc = MyClass()

print(mc.__dict__)          
# {'_MyClass__superprivate': 'Hello', '_semiprivate': ', world!'}

print(mc._semiprivate)      
# , world!

#print(mc.__superprivate)   
# AttributeError: 'MyClass' object has no attribute '__superprivate'

print(mc._MyClass__superprivate) 
# Hello
  • __foo__:一种约定。Python内部的名字,用来区别用户自定义的其他命名,以防冲突。例如__init__()__del__()__call__()这些特殊方法。

  • _foo:一种约定。程序员用来指定私有变量的一种方式,不能用from module import *导入,其他方面和公有变量的一样。

  • __foo:这个有真正的意义。python解释器用_classname__foo来代替这个名字,以区别和其他类相同的命名。它无法直接像公有成员一样随便访问,而是通过对象名._类名__方法 这样的方式访问。

详见:

17. *args,**kwargs参数是什么?

使用用*args**kwargs是为了方便,而不是必须要使用它们。

  • 当不确定你的函数里将要传递多少参数时,你可以使用位置参数包裹*args,它可以传递任意数量的位置参数
def print_everything(*args):
    for count, thing in enumerate(args):
        print('{0}. {1}'.format(count, thing))

print_everything('apple', 'banana', 'cabbage')

'''
0. apple
1. banana
2. cabbage
'''
  • 同理,关键字参数包裹**kwargs允许你使用没有事先定义的关键字参数
def table_things(**kwargs):
    for name, value in kwargs.items():
        print('{0} = {1}'.format(name, value))

table_things(apple = 'fruit', cabbage = 'vegetable')

'''
apple = fruit
cabbage = vegetable
'''
  • 位置参数、关键字参数、位置参数包裹、关键字参数包裹的混合使用

位置参数首先获得参数值,然后其他所有的参数都传递给*args**kwargs。位置参数在参数的最前端,*args**kwargs可以同时在传参时使用,但是*args必须在**kwargs前面。

func(positional_args, keyword_args, *tuple_nonkw_args, **dict_kw_args)

'''
positional_args:位置参数
keyword_args:关键字参数
*tuple_nonkw_args:非关键字不定长参数
**dict_kw_args:关键字不定长参数
'''
  • 调用函数时,我们也可以使用***语法
def print_three_things(a, b, c):
    print('a = {0}, b = {1}, c = {2}'.format(a, b, c))

mylist = ['aardvark', 'baboon', 'cat']

print_three_things(*mylist)

# a = aardvark, b = baboon, c = cat

*可以传递列表(或者元组)的每一项并把它们解包,这必须与它们在函数里的参数相吻合。

stackoverflow上的解释:https://stackoverflow.com/questions/3394835/args-and-kwargs

18. Python中类变量和实例变量的区别

  • 类变量:在类的所有实例之间共享的变量值(也就是说,类变量不是单独分配给1个实例的)。
class Test(object):  
    num_of_instance = 0  
    def __init__(self, name):  
        self.name = name  
        Test.num_of_instance += 1  

if __name__ == '__main__':  
    print(Test.num_of_instance)   # 类变量0
    t1 = Test('jack')  
    print(Test.num_of_instance)   # 1
    t2 = Test('lucy')  
    print(t1.name, t1.num_of_instance)  # jack 2
    print(t2.name, t2.num_of_instance)  # lucy 2

# num_of_instance就是类变量,用于跟踪存在着多少个Test的实例。
  • 实例变量:类实例化之后,实例单独拥有的变量。
class Person():
    name="aaa"

p1=Person()

p1.name="bbb"
print(p1.name)      # 修改实例p1的变量name值,更新为bbb

print(Person.name)  # 类变量name的值为aaa

p1.name="bbb" 是实例调用了类变量,也就是函数传参的问题,p1.name一开始是指向的类变量name="aaa",后来改变了实例作用域里的类变量引用,p1.name就变成了一个实例变量,self.name不再引用Person的类变量name了。

下面是类变量引用实例作用域里的实例变量。

class Person:
    name=[]

p1=Person()
p2=Person()
print(p1.name)

p1.name.append(1)
print(p1.name)      # [1]
print(p2.name)      # [1]
print(Person.name)  # [1]

p1.name.append(2)
print(p1.name)      # [1, 2]
print(p2.name)      # [1, 2]
print(Person.name)  # [1, 2]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值