Python 面试题学习
Python的函数参数传递
- 在Python中,strings,tuples=('abc',123,2.2,'join),numbers 是不可更改的对象。
- list=['abc',123,2.23,'john'],dict等则是可以修改的对象。
示例:
a = 1 def fun(a): a = 2 fun(a) print a # 1
a = [] def fun(a): a.append(1) fun(a) print a # [1]
@staticmethod和@classmethod
- python有3个方法,即静态方法(staticmethod),类方法(classmethod)和实例方法。
def foo(x): print "executing foo(%s)"%(x) class A(object): def foo(self,x): print "executing foo(%s,%s)"%(self,x) @classmethod def class_foo(cls,x): print "executing class_foo(%s,%s)"%(cls,x) @staticmethod def static_foo(x): print "executing static_foo(%s)"%x a=A()
这个self和cls是对类或者实例的绑定,对于一般的函数来说我们可以这么调用foo(x)
,这个函数就是最常用的,它的工作跟任何东西(类,实例)无关.对于实例方法,我们知道在类里每次定义方法的时候都需要绑定这个实例,就是foo(self, x)
,为什么要这么做呢?因为实例方法的调用离不开实例,我们需要把实例自己传给函数,调用的时候是这样的a.foo(x)
(其实是foo(a, x)
).类方法一样,只不过它传递的是类而不是实例,A.class_foo(x)
.注意这里的self和cls可以替换别的参数,但是python的约定是这俩,还是不要改的好.
对于静态方法其实和普通的方法一样,不需要对谁进行绑定,唯一的区别是调用的时候需要使用a.static_foo(x)
或者A.static_foo(x)
来调用.
类变量和实例变量???????
class Person: name=[] p1=Person() p2=Person() p1.name.append(1) print p1.name # [1] print p2.name # [1] print Person.name # [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 t1 = Test('jack') print Test.num_of_instance t2 = Test('lucy') print t1.name , t1.num_of_instance print t2.name , t2.num_of_instance
Python自省
- 自省(让对象告诉我们他是什么),用于实现在运行时获取未知对象的信息。
访问对象属性:
- hasattr(obj, attr) 这个方法用于检查obj是否有一个名为attr的值的属性,返回一个布尔值。
- getattr(obj, attr) 调用这个方法将返回obj中名为attr值的属性的值,例如如果attr为’bar’,则返回obj.bar。
- setattr(obj, attr, val) 调用这个方法将返回obj中名为attr值的属性的值,例如如果attr为’bar’,则返回obj.bar。
推导式
- 列表推导式(list) 提供一种方便的列表创建方法,返回一个列表。格式:用中括号括起来,中间用for语句,后面跟if语句用作判读,满足条件的传到for语句前面用作构建先的列表.
>>> li=[i*2 for i in range(10) if i % 2 == 0] >>> print (li) [0, 4, 8, 12, 16] >>>
- 字典推导式(dic) 格式:d = {key: value for (key, value) in iterable}
>>> mca={"a":1, "b":2, "c":3, "d":4} >>> dicts={v:k for k,v in mca.items()} >>> print (dicts) {1: 'a', 2: 'b', 3: 'c', 4: 'd'} >>>
- 集合推导式(跟列表推导式的区别:1.不使用中括号,使用大括号;2.结果中无重复;3.结果是一个set()集合,集合里面是一个序列)
>>> squared={i*2 for i in [1,1,2]} >>> print (squared) set([2, 4]) >>>
Python中单下划线和双下划线??????
_foo
:
- 在一个模块中以单下划线开头的变量和函数被默认当作内部函数,如果使用 from a_module import * 导入时,这部分变量和函数不会被导入。不过值得注意的是,如果使用 import a_module 这样导入模块,仍然可以用 a_module._some_var 这样的形式访问到这样的对象。
- 一种约定,用来指定变量私有.程序员用来指定私有变量的一种方式.
__foo
:
- 这个有真正的意义:解析器用
_classname__foo
来代替这个名字,以区别和其他类相同的命名. -
双下划线开头的命名形式在 Python 的类成员中使用表示名字改编 (Name Mangling),即如果有一 Test 类里有一成员 __x,那么 dir(Test) 时会看到 _Test__x 而非 __x。这是为了避免该成员的名称与子类中的名称冲突。但要注意这要求该名称末尾没有下划线.
__foo__
:
- 一种约定,Python内部的名字,用来区别其他用户自定义的命名,以防冲突.
-
双下划线开头双下划线结尾的是一些 Python 的“魔术”对象,如类成员的 __init__、__del__、__add__、__getitem__ 等,以及全局的 __file__、__name__ 等。 Python 官方推荐永远不要将这样的命名方式应用于自己的变量或函数,而是按照文档说明来使用。
>>> class MyClass(): ... def __init__(self): ... self.__superprivate = "Hello" ... self._semiprivate = ", world!" ... >>> mc = MyClass() >>> print mc.__superprivate Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: myClass instance has no attribute '__superprivate' >>> print mc._semiprivate , world! >>> print mc.__dict__ {'_MyClass__superprivate': 'Hello', '_semiprivate': ', world!'}
字符串格式化:%和.format
对于%
最烦人的是它无法同时传递一个变量和元组.
name = (1,2,3) print ("hi there %s" % name) # 报错 print ("hi there %s" % (name,)) # 提供一个单元素的数组而不是一个参数
.format通过{}和.来代替%
- 通过位置:
print ("{0},{1}".format('lujf',123))
- 通过关键字参数
print ("{name},{age}".format(name='lujf',age=23))
- 通过对象属性???????
class Person: def __init__(self,name,age): self.name,self.age = name,age def __str__(self): return 'This guy is {self.name},is {self.age} old'.format(self=self)
- 通过下标
p = ['lujf',23] print ('{0[0]},{0[1]}'.format(p))
迭代器和生成器
迭代器:
- 迭代器是访问集合元素的一种方式。迭代器对象从集合的第一个元素开始访问,知道所有的元素被访问完结束。迭代器只能往前不会后退。
- 对于原生支持随机访问的数据结构(如tuple、list),迭代器和经典for循环的索引访问相比并无优势,反而丢失了索引值(可以使用内建函数enumerate()找回这个索引值)。但对于无法随机访问的数据结构(比如set)而言,迭代器是唯一的访问元素的方式。
- 迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素。迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件,或是斐波那契数列等等
-
迭代器更大的功劳是提供了一个统一的访问集合的接口,只要定义了__iter__()方法对象,就可以使用迭代器访问。
迭代器有两个基本的方法
- next方法:返回迭代器的下一个元素
- __iter__方法:返回迭代器对象本身
class Fab(object): def __init__(self, max): self.max = max self.n, self.a, self.b = 0, 0, 1 def __iter__(self): return self def next(self): if self.n < self.max: r = self.b self.a, self.b = self.b, self.a + self.b self.n = self.n + 1 return r raise StopIteration() #python处理迭代器越界是抛出StopIteration异常
使用迭代器:
- 使用内建的工厂函数iter(iterable)可以获取迭代器对象:
>>> lst = range(5) >>> it = iter(lst) >>> it <listiterator object at 0x01A63110>
- 使用next()方法可以访问下一个元素:
>>> it.next() 0 >>> it.next() 1 >>> it.next() 2
事实上,因为迭代器如此普遍,python专门为for关键字做了迭代器的语法糖。在for循环中,Python将自动调用工厂函数iter()获得迭代器,自动调用next()获取元素,还完成了检查StopIteration异常的工作。
生成器:
- 带有 yield 的函数在 Python 中被称之为 generator(生成器)。
def fab(max): n, a, b = 0, 0, 1 while n < max: yield b a, b = b, a + b n = n + 1
简单地讲,yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator,调用 fab(5) 不会执行 fab 函数,而是返回一个 iterable 对象!在 for 循环执行时,每次循环都会执行 fab 函数内部的代码,执行到 yield b 时,fab 函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。
- return的作用
在一个生成器中,如果没有return,则默认执行到函数完毕;如果遇到return,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代。
*args and **kwargs
如果我们不确定往一个函数中传入多少参数,或者我们希望以元组(tuple)或者列表(list)的形式传参数的时候,我们可以使用*args 。当函数的参数前面有一个星号*号的时候表示这是一个可变的位置参数。星号*把序列或者集合解包(unpack)成位置参数。
def print_everything(*args): for count, thing in enumerate(args): #enumerate()为python内置函数 print('{},{}'.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(): #Python 字典(Dictionary) items()方法 print ('{} = {}'.format(name, value)) table_things(apple = 'fruit', cabbage = 'vegetable') #输出 #apple = fruit #cabbage = vegetable
面向切面编程AOP和装饰器
# -*- coding: utf-8 -*- import time def foo(): print 'in foo()' # 定义一个计时器,传入一个,并返回另一个附加了计时功能的方法 def timeit(func): # 定义一个内嵌的包装函数,给传入的函数加上计时功能的包装 def wrapper(): start = time.clock() func() end = time.clock() print ('used:', end - start) # 将包装后的函数返回 return wrapper foo = timeit(foo) foo()
在在这个例子中,函数进入和退出时需要计 时,这被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。
Python重载(python 自然就不需要函数重载)
函数重载主要是为了解决两个问题:
- 可变参数类型(因为 python 可以接受任何类型的参数,如果函数的功能相同,那么不同的参数类型在 python 中很可能是相同的代码,没有必要做成两个不同函数。)
- 可变参数个数(缺省参数)
示例:
def f(a,L=[]): L.append(a) print L
f(1) #[1]
f(2) #[1,2]
缺省参数在python中是与函数绑定在一起的。
也就是说,一个函数中定义了一个缺省参数,那么这个参数会随着被调用而改变。(一个坑)
在一次调用中改变了缺省参数的值,可能会影响到这个函数的另外一次调用。
__new__ 和__init__的区别
lass Book(object): def __new__(cls, title): print'__new__' return super(Book, cls).__new__(cls) def __init__(self, title): print'__init__' super(Book, self).__init__(self) self.title = title b = Book('The Django Book') print(b.title)
官方文档:
- __init__是当实例对象创建完成后被调用的,然后设置对象属性的一些初始值。
- __new__是在实例创建之前被调用的,因为它的任务就是创建实例然后返回该实例,是个静态方法
也就是,__new__在__init__之前被调用,__new__的返回值(实例)将传递给__init__方法的第一个参数,然后__init__给这个实例设置一些参数。
Python中的作用域
- Python 中,一个变量的作用域总是由在代码中被赋值的地方所决定的。
- 当 Python 遇到一个变量的话他会按照这样的顺序进行搜索:本地作用域(Local)→当前作用域被嵌入的本地作用域(Enclosing locals)→全局/模块作用域(Global)→内置作用域(Built-in)
GIL线程全局锁
- 线程全局锁(Global Interpreter Lock),即Python为了保证线程安全而采取的独立线程运行的限制,说白了就是一个核只能在同一时间运行一个线程.
协程
简单点说协程是进程和线程的升级版,进程和线程都面临着内核态和用户态的切换问题而耗费许多切换时间,而协程就是用户自己控制切换的时机,不再需要陷入系统的内核态.
lambda函数
- 简单来说,编程中提到的 lambda 表达式,通常是在需要一个函数,但是又不想费神去命名一个函数的场合下使用,也就是指匿名函数。
map( lambda x: x*x, [y for y in range(10)] )
def sq(x): return x * x map(sq, [y for y in range(10)])
- map()函数接收两个参数,一个是函数,一个是序列,map将传入的函数依次作用到序列的每个元素,并把结果作为新的list返回。
Python 引用和copy(),deepcopy()的区别
Python中对象之间的赋值是按引用传递的,如果要拷贝对象需要使用标准模板中的copy
- 赋值:Python中对象之间的赋值是按引用传递的
- copy.copy:浅拷贝,只拷贝父对象,不拷贝父对象的子对象。
- 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 的 is 和 ==
python中的对象包含三要素:id、type、value
其中 id 用来唯一标识一个对象。
- is是对比地址
- ==是对比值
read,readline 和 readlines
- read 读取整个文件。
- readline 读取下一行,使用生成器方法。
- readlines 读取整个文件到一个迭代器以供我们遍历。
f = open('poem.txt','r') a = f.read() print (a)
f = open('poem.txt','r') result = list() for line in open('poem.txt'): line = f.readline() print (line) result.append(line) print (result) f.close()
f = open('poem.txt', 'r') #以读方式打开文件 result = list() for line in f.readlines(): #依次读取每行 line = line.strip() #去掉每行头尾空白 if not len(line) or line.startswith('#'): #判断是否是空行或注释行 continue #是的话,跳过不处理 result.append(line) #保存 result.sort() #排序结果 print result
Python的垃圾回收机制 (garbage collection)
Python中的垃圾回收是以引用计数为主,标记-清除和分代收集为辅。
- 引用计数:Python在内存中存储每个对象的引用计数,如果计数变成0,该对象就会消失,分配给该对象的内存就会释放出来。
- 标记-清除:一些容器对象,比如list、dict、tuple,instance等可能会出现引用循环,对于这些循环,垃圾回收器会定时回收这些循环(对象之间通过引用(指针)连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边)。
- 分代收集:Python把内存根据对象存活时间划分为三代,对象创建之后,垃圾回收器会分配它们所属的代。每个对象都会被分配一个代,而被分配更年轻的代是被优先处理的,因此越晚创建的对象越容易被回收。
说明os,sys模块的不同
os模块负责程序与操作系统的交互,提供了访问操作系统底层的接口。
sys模块负责程序与Python解释器的交互,提供了一系列的函数和变量用户操作Python运行时的环境。
lambda表达式
简单来说,lambda表达式通常是当你需要使用一个函数,但是又不想费脑袋去命名一个函数的时候使用,也就是通常所说的匿名函数。
#示例 f = lambda x,y,z: x + y + z print(f(1,2,3)) L = {'f1':(lambda x,y: x**2 + y**2), 'f2':(lambda x,y: x**3 + y**3), 'f3':(lambda x,y: x**4 + y**3)} print(L['f2'](3,2))