python面试高频问题

1 Python的函数参数传递

看两个例子:

 a = 1
 def fun(a):
     a = 2
 fun(a)
 print a  # 1
 a = []
 def fun(a):
     a.append(1)
 fun(a)
 print a  # [1]

所有的变量都可以理解是内存中一个对象的“引用”,或者,也可以看似c中void*的感觉。

通过id来看引用a的内存地址可以比较理解:

 a = 1
 def fun(a):
     print "func_in",id(a)   # func_in 41322472
     a = 2
     print "re-point",id(a), id(2)   # re-point 41322448 41322448
 print "func_out",id(a), id(1)  # func_out 41322472 41322472
 fun(a)
 print a  # 1

注:具体的值在不同电脑上运行时可能不同。

可以看到,在执行完a = 2之后,a引用中保存的值,即内存地址发生变化,由原来1对象的所在的地址变成了2这个实体对象的内存地址。

而第2个例子a引用保存的内存值就不会发生变化:

 a = []
 def fun(a):
     print "func_in",id(a)  # func_in 53629256
     a.append(1)
 print "func_out",id(a)     # func_out 53629256
 fun(a)
 print a  # [1]

这里记住的是类型是属于对象的,而不是变量。而对象有两种,“可更改”(mutable)与“不可更改”(immutable)对象。在python中,strings, tuples, 和numbers是不可更改的对象,而 list, dict, set 等则是可以修改的对象。(这就是这个问题的重点)

当一个引用传递给函数的时候,函数自动复制一份引用,这个函数里的引用和外边的引用没有半毛关系了.所以第一个例子里函数把引用指向了一个不可变对象,当函数返回的时候,外面的引用没半毛感觉.而第二个例子就不一样了,函数内的引用指向的是可变对象,对它的操作就和定位了指针地址一样,在内存里进行修改.

2 Python中的元类(metaclass)

元类是创建类的类。在python中,一切皆对象,类也不例外。 当我们用class关键字定义类的时候,python实际上会执行它然后生成一个对象。既然是对象,就可以进行赋值,拷贝,添加属性,作为函数参数等。使用元类允许我们控制类的生成,比如修改类的属性,检查属性的合法性等。

在Python中,有两种创建类的方式,一种是平常我们使用的使用class关键字创建类:

 class MyClass:    # python2中新式类要显示继承object
     pass

还有一种方式是使用type函数创建,平常我们一般使用type查看对象的类型,实际上type还有一个重要的功能就是创建类

 Docstring:
 type(object_or_name, bases, dict)
 type(object) -> the object's type
 type(name, bases, dict) -> a new type

上边MyClass的定义用type创建可以这么写: MyClass = type('Myclass', (), {})

对于有继承关系和属性的类来说,可以使用如下等价定义:

 # 加上继承
 class Base:
     pass
 ​
 class Child(Base):
     pass
 # 等价定义
 Child = type('Child', (Base,), {})      # 注意Base后要加上逗号否则就不是tuple了
 ​
 # 加上属性
 class ChildWithAttr(Base):
     bar = True
 ​
 # 等价定义
 ChildWithAttr = type('ChildWithAttr', (Base,), {'bar': True})
 ​
 # 加上方法
 class ChildWithMethod(Base):
     bar = True
 ​
     def hello(self):
         print('hello')
 ​
 def hello(self):
     print('hello')
 ​
 # 等价定义
 ChildWithMethod = type('ChildWithMethod', (Base,), {'bar': True, 'hello': hello})

使用元类也有一下一些好处:

  • 意图更加明确。当然你的metaclass名字要起好。

  • 面向对象。可以隐式继承到子类。

  • 可以更好地组织代码,更易读。

  • 可以用newinit,call等方法更好地控制。

元类的一些应用(单例,ORM, abc模块等)

ORM是”Object Relational Mapping”的缩写,叫做对象-关系映射,用来把关系数据的一行映射成一个对象,一个表对应成一个类,这样就免去了直接使用SQL语句的麻烦,使用起来更加符合程序员的思维习惯。ORM框架里所有的类都是动态定义的,由使用类的用户决定有哪些字段,这个时候就只能用元类来实现了。

3 @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.这个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)来调用.

\ 实例方法 类方法 静态方法
a = A() a.foo(x) a.class_foo(x) a.static_foo(x)
A 不可用 A.class_foo(x) A.static_foo(x)

4 类变量和实例变量

类变量:

是可在类的所有实例之间共享的值(也就是说,它们不是单独分配给每个实例的)。例如下例中,num_of_instance 就是类变量,用于跟踪存在着多少个Test 的实例。

实例变量:

实例化之后,每个实例单独拥有的变量。

 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

补充的例子

 class Person:
     name="aaa"
 ​
 p1=Person()
 p2=Person()
 p1.name="bbb"
 print p1.name  # bbb
 print p2.name  # aaa
 print Person.name  # aaa

这里p1.name="bbb"是实例调用了类变量,这其实和上面第一个问题一样,就是函数传参的问题,p1.name一开始是指向的类变量name="aaa",但是在实例的作用域里把类变量的引用改变了,就变成了一个实例变量,self.name不再引用Person的类变量name了.

可以看看下面的例子:

 class Person:
     name=[]
 ​
 p1=Person()
 p2=Person()
 p1.name.append(1)
 print p1.name  # [1]
 print p2.name  # [1]
 print Person.name  # [1]

5 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)  # True

6 字典推导式

可能你见过列表推导时,却没有见过字典推导式,在2.7中才加入的:

 d = {key: value for (key, value) in iterable}

7 Python中单下划线和双下划线

 >>> class MyClass():
 ...     def __init__(self):
 ...             self.__superprivate = "Hello"
 ...             self._semiprivate = ", world!"
 ...
 >>> mc = MyClass()
 >>> print mc.__superprivate
 Traceback (most recent call last):
   File "", line 1, in 
 AttributeError: myClass instance has no attribute '__superprivate'
 >>> print mc._semiprivate
 , world!
 >>> print mc.__dict__
 {'_MyClass__superprivate': 'Hello', '_semiprivate': ', world!'}

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

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

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

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

.format在许多方面看起来更便利.对于%最烦人的是它无法同时传递一个变量和元组.你可能会想下面的代码不会有什么问题:

 "hi there %s" % name

但是,如果name恰好是(1,2,3),它将会抛出一个TypeError异常.为了保证它总是正确的,你必须这样做:

 "hi there %s" % (name,)   # 提供一个单元素的数组而不是一个参数
 tu = (12,45,22222,103,6)
 print '{0} {2} {1} {2} {3} {2} {4} {2}'.format(*tu)
 # 12 22222 45 22222 103 22222 6 22222
 num = [12,45,78]
 print map('the number is {}'.format,num)
 # ['the number is 12', 'the number is 45', 'the number is 78']

9 迭代器和生成器

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

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

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

10 可变参数和关键字参数

当你不确定你的函数里将要传递多少参数时你可以用*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')
 cabbage = vegetable
 apple = fruit

你也可以混着用.命名参数首先获得参数值然后所有的其他参数都传递给*args**kwargs.命名参数在列表的最前端.例如:

 def table_things(titlestring, **kwargs)

*args**kwargs可以同时在函数的定义中,但是*args必须在**kwargs前面.

当调用函数时你也可以用***语法.例如:

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

就像你看到的一样,它可以传递列表(或者元组)的每一项并把它们解包.注意必须与它们在函数里的参数相吻合.当然,你也可以在函数定义或者函数调用时用*.

11 面向切面编程AOP和装饰器

AOP 即面向切面编程,指扩展功能不修改源代码,将功能代码从业务逻辑代码中分离出来。

装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

装饰器 本身也是一个函数 ,这个函数以闭包的形式定义,在使用这个装饰器函数时,在被装饰的函数的前一行,使用 @装饰器函数名 形式来装饰。

闭包是指在一个函数中定义了一个另外一个函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用,这样就构成了一个闭包。 闭包的使用,可以隐藏内部函数的工作细节,只给外部使用者提供一个可以执行的内部函数的引用。

 #实现一百万次的累加
 import time
 def count_time(func):
     def wrapper():      #wrapper 装饰
         start = time.time()
         func()
         end = time.time()
         print('共计执行:%s 秒'%(end - start)) # 使用%d显示,取整后是0秒,因为不到一秒
     return wrapper
 ​
 @count_time     # 这实际就相当于解决方法3中的 my_count = count_tiem(my_count)
 def my_count():
     s = 0
     for i in range(10000001):
         s += i
     print('sum : ', s)
 ​
 my_count()

这样实现的好处是,定义好了闭包函数后。只需要通过 @xxx 形式的装饰器语法,将 @xxx 加到要装饰的函数前即可。使用者在使用时,根本不需要知道被装饰了。只需要知道原来的函数功能是什么即可。在执行 @xxx 时 ,实际就是将 原函数传递到闭包中,然后原函数的引用指向闭包返回的装饰过的内部函数的引用。

12 鸭子类型

“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”

我们并不关心对象是什么类型,到底是不是鸭子,只关心行为。

利用鸭子类型的思想,不必借助超类型的帮助,就能轻松地在动态类型语言中实现一个原则:“面向接口编程,而不是面向实现编程”。例如,一个对象若有push和pop方法,并且这些方法提供了正确的实现,它就可以被当作栈来使用。一个对象如果有length属性,也可以依照下标来存取属性(最好还要拥有slice和splice等方法),这个对象就可以被当作数组来使用。

比如在python中,有很多file-like的东西,比如StringIO,GzipFile,socket。它们有很多相同的方法,我们把它们当作文件使用。

又比如list.extend()方法中,我们并不关心它的参数是不是list,只要它是可迭代的,所以它的参数可以是list/tuple/dict/字符串/生成器等.

鸭子类型在动态语言中经常使用,非常灵活,使得python不像java那样专门去弄一大堆的设计模式。

13 Python中重载

函数重载主要是为了解决两个问题。

  1. 可变参数类型。

  2. 可变参数个数。

另外,一个基本的设计原则是,仅仅当两个函数除了参数类型和参数个数不同以外,其功能是完全相同的,此时才使用函数重载,如果两个函数的功能其实不同,那么不应当使用重载,而应当使用一个名字不同的函数。

好吧,那么对于情况 1 ,函数功能相同,但是参数类型不同,python 如何处理?答案是根本不需要处理,因为 python 可以接受任何类型的参数,如果函数的功能相同,那么不同的参数类型在 python 中很可能是相同的代码,没有必要做成两个不同函数。

那么对于情况 2 ,函数功能相同,但参数个数不同,python 如何处理?大家知道,答案就是缺省参数。对那些缺少的参数设定为缺省参数即可解决问题。因为你假设函数功能相同,那么那些缺少的参数终归是需要用的。

好了,鉴于情况 1 跟 情况 2 都有了解决方案,python 自然就不需要函数重载了。

14 新式类和旧式类的区别

  1. 定义方式不同

在Python 2.x 版本中,默认类都是旧式类,除非显式继承object。在Python 3.x 版本中,默认类就是新式类,无需显示继承object。

在Python 2.x 中,定义旧式类的方式:

 class A:  # A是旧式类,因为没有显示继承object
     pass
 ​
 class B(A):  # B是旧式类,因为B的基类A是旧式类
     pass

定义新式类的方式:

 class A(object):  # A是新式类,因为显式继承object
     pass
 ​
 class B(A):  # B是新式类,因为B的基类A是新式类
     pass

2. 保持class与type的统一

对新式类的实例执行a.__class__与type(a)的结果是一致的,对于旧式类来说就不一样了。

3.对于多重继承的属性搜索顺序不一样

新式类是采用广度优先搜索,旧式类采用深度优先搜索。

一个旧式类的深度优先的例子

 class A():
     def foo1(self):
         print "A"
 class B(A):
     def foo2(self):
         pass
 class C(A):
     def foo1(self):
         print "C"
 class D(B, C):
     pass
 ​
 d = D()
 d.foo1()
 ​
 # A

按照经典类的查找顺序从左到右深度优先的规则,在访问d.foo1()的时候,D这个类是没有的..那么往上查找,先找到B,里面没有,深度优先,访问A,找到了foo1(),所以这时候调用的是A的foo1(),从而导致C重写的foo1()被绕过

 

15 __new____init__的区别

  1. __new__是一个静态方法,而__init__是一个实例方法.

  2. __new__方法会返回一个创建的实例,而__init__什么都不返回.

  3. 只有在__new__返回一个cls的实例时后面的__init__才能被调用.

  4. 当创建一个新实例时调用__new__,初始化一个实例时用__init__.

  5. init是当实例对象创建完成后被调用的,然后设置对象属性的一些初始值。

ps: __metaclass__是创建类时起作用.所以我们可以分别使用__metaclass__,__new____init__来分别在类创建,实例创建和实例初始化的时候做一些小手脚.

16 单例模式

单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。

__new__()__init__()之前被调用,用于生成实例对象。利用这个方法和类的属性的特点可以实现设计模式的单例模式。单例模式是指创建唯一对象,单例模式设计的类只能实例 这个绝对常考啊.绝对要记住1~2个方法,当时面试官是让手写的.

1 使用__new__方法

 class Singleton(object):
     def __new__(cls, *args, **kw):
         if not hasattr(cls, '_instance'):
             orig = super(Singleton, cls)
             cls._instance = orig.__new__(cls, *args, **kw)
         return cls._instance
 ​
 class MyClass(Singleton):
     a = 1

2 共享属性

创建实例时把所有实例的__dict__指向同一个字典,这样它们具有相同的属性和方法.

 ​
 class Borg(object):
     _state = {}
     def __new__(cls, *args, **kw):
         ob = super(Borg, cls).__new__(cls, *args, **kw)
         ob.__dict__ = cls._state
         return ob
 ​
 class MyClass2(Borg):
     a = 1

3 装饰器版本

 def singleton(cls):
     instances = {}
     def getinstance(*args, **kw):
         if cls not in instances:
             instances[cls] = cls(*args, **kw)
         return instances[cls]
     return getinstance
 ​
 @singleton
 class MyClass:
   ...

4 import方法

作为python的模块是天然的单例模式

 # mysingleton.py
 class My_Singleton(object):
     def foo(self):
         pass
 ​
 my_singleton = My_Singleton()
 ​
 # to use
 from mysingleton import my_singleton
 ​
 my_singleton.foo()
 ​

17 Python中的作用域

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

当 Python 遇到一个变量的话他会按照这样的顺序进行搜索:

本地作用域(Local)→闭包函数外的函数中 (Enclosing)→全局/模块作用域(Global)→内置作用域(Built-in)

 #dir 为python内建函数
 dir = 1 # Global
 def outer():
     dir = 2  # Enclosing
     def inner():
         dir = 3 # Local
         return dir
     return inner
 print outer()() # 输出3

18 GIL线程全局锁

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

在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势。GIL本质就是一把互斥锁,将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。

何为线程

每个进程有一个地址空间,而且默认就有一个控制线程。如果把一个进程比喻为一个车间的工作过程那么线程就是车间里的一个一个流水线。进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位。多线程(即多个控制线程)的概念是,在一个进程中存在多个控制线程,多个控制线程共享该进程的地址空间(资源)。

创建进程的开销要远大于线程,开进程相当于建一个车间,而开线程相当于建一条流水线

线程和进程的区别

1、线程共享创建它的进程的地址空间;进程有自己的地址空间。

2、线程可以直接访问其进程的数据段;进程有它们自己的父进程数据段的副本。

3、线程可以直接与进程的其他线程通信;进程必须使用进程间通信来与兄弟进程通信。

4、新线程很容易创建;新进程需要复制父进程。

5、线程可以对同一进程的线程进行相当大的控制;进程只能对子进程执行控制。

6、对主线程的更改(取消、优先级更改等)可能会影响该进程的其他线程的行为;对父进程的更改不会影响子进程。

多线程的优点

多线程和多进程相同指的是,在一个进程中开启多个线程

1)多线程共享一个进程的地址空间(资源)

2) 线程比进程更轻量级,线程比进程更容易创建可撤销,在许多操作系统中,创建一个线程比创建一个进程要快10-100倍,在有大量线程需要动态和快速修改时,这一特性很有用

3) 若多个线程都是cpu密集型的,那么并不能获得性能上的增强,但是如果存在大量的计算和大量的I/O处理,拥有多个线程允许这些活动彼此重叠运行,从而会加快程序执行的速度。

4) 在多cpu系统中,为了最大限度的利用多核,可以开启多个线程,比开进程开销要小的多。(这一条并不适用于python)

GIL与多线程

有了GIL的存在,同一时刻同一进程中只有一个线程被执行。现在的计算机基本上都是多核,python对于计算密集型的任务开多线程的效率并不能带来多大性能上的提升,甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。多cpu,意味着可以有多个核并行完成计算,所以多核提升的是计算性能,每个cpu一旦遇到I/O阻塞,仍然需要等待,所以多核对I/O操作没什么用处 。

19 协程

协程是一种用户态的轻量级线程。

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

协程不同于线程的是,线程是抢占式的调度,而协程是协同式的调度,也就是说,协程需要自己做调度。

Python里最常见的yield就是协程的思想!

协程的好处:

无需线程上下文切换的开销

无需原子操作锁定及同步的开销

方便切换控制流,简化编程模型

高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。

缺点:

无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。

进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序。

20 闭包

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

当一个内嵌函数引用其外部作作用域的变量,我们就会得到一个闭包. 总结一下,创建一个闭包必须满足以下几点:

  1. 必须有一个内嵌函数

  2. 内嵌函数必须引用外部函数中的变量

  3. 外部函数的返回值必须是内嵌函数

一般来说,当对象中只有一个方法时,这时使用闭包是更好的选择。

 def make_multiplier_of(n):
     def multiplier(x):
         return x * n
     return multiplier
 ​
 times3 = make_multiplier_of(3)
 times5 = make_multiplier_of(5)
 ​
 print(times3(9))# Output: 27
 print(times5(3))# Output: 15
 print(times5(times3(2))# Output: 30

闭包避免了使用全局变量,此外,闭包允许将函数与其所操作的某些数据(环境)关连起来。这一点与面向对象编程是非常类似的,在面对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。

重点是函数运行后并不会被撤销,就像16题的instance字典一样,当函数运行完后,instance并不被销毁,而是继续留在内存空间里.这个功能类似类里的类变量,只不过迁移到了函数上.

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

21 lambda函数

其实就是一个匿名函数,是一种简单的在同一行中定义函数的方法。lambda表达式只允许包含一个表达式,不能包含复杂语句,该表达式的运算结果就是函数的返回值

 #arg1/arg2/arg3为函数的**参数(函数输入)**,表达式相当于**函数体**,运算结果是表达式的运算结果
 lambda arg1,arg2,arg3… :<表达式>
 lambda x, y: x*y  #函数输入是x和y,输出是它们的积x*y
 ambda *args: sum(args) #输入是任意个数的参数,输出是它们的和
 f=lambda a,b,c,d:a*b*c*d
 print(f(1,2,3,4))  

可以将lambda函数的用法扩展为以下几种:

1.将lambda函数赋值给一个变量,通过这个变量间接调用该lambda函数。

例如,执行语句add=lambda x, y: x+y,定义了加法函数lambda x, y: x+y,并将其赋值给变量add,这样变量add便成为具有加法功能的函数。例如,执行add(1,2),输出为3。

2.将lambda函数赋值给其他函数,从而将其他函数用该lambda函数替换。

例如,为了把标准库time中的函数sleep的功能屏蔽(Mock),我们可以在程序初始化时调用:time.sleep=lambda x:None。这样,在后续代码中调用time库的sleep函数将不会执行原有的功能。例如,执行time.sleep(3)时,程序不会休眠3秒钟,而是什么都不做。

3.将lambda函数作为参数传递给其他函数。

函数的返回值也可以是函数。例如return lambda x, y: x+y返回一个加法函数。这时,lambda函数实际上是定义在某个函数内部的函数,称之为嵌套函数,或者内部函数。对应的,将包含嵌套函数的函数称之为外部函数。内部函数能够访问外部函数的局部变量,这个特性是闭包(Closure)编程的基础。

Python内置函数接受函数作为参数,典型的此类内置函数有:

filter函数 此时lambda函数用于指定过滤列表元素的条件。例如filter(lambda x: x % 3 == 0, [1, 2, 3])指定将列表[1,2,3]中能够被3整除的元素过滤出来,其结果是[3]。

sorted函数 此时lambda函数用于指定对列表中所有元素进行排序的准则。例如sorted([1, 2, 3, 4, 5, 6, 7, 8, 9], key=lambda x: abs(5-x))将列表[1, 2, 3, 4, 5, 6, 7, 8, 9]按照元素与5距离从小到大进行排序,其结果是[5, 4, 6, 3, 7, 2, 8, 1, 9]。

map函数 此时lambda函数用于指定对列表中每一个元素的共同操作。例如map(lambda x: x+1, [1, 2,3])将列表[1, 2, 3]中的元素分别加1,其结果[2, 3, 4]。

reduce函数 此时lambda函数用于指定列表中两两相邻元素的结合条件。例如reduce(lambda a, b: '{}, {}'.format(a, b), [1, 2, 3, 4, 5, 6, 7, 8, 9])将列表 [1, 2, 3, 4, 5, 6, 7, 8, 9]中的元素从左往右两两以逗号分隔的字符的形式依次结合起来,其结果是'1, 2, 3, 4, 5, 6, 7, 8, 9'。

22 Python函数式编程

python中函数式编程支持:

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

sorted函数对一个序列由小到大排序,下面是按照元素与5的距离从小到大排序

 >>> sorted([1, 2, 3, 4, 5, 6, 7, 8, 9], key=lambda x: abs(5-x))
 [5, 4, 6, 3, 7, 2, 8, 1, 9]

 

23 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']]

24 Python垃圾回收机制

Python GC主要使用引用计数(reference counting)来跟踪和回收垃圾。在引用计数的基础上,通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用问题,通过“分代回收”(generation collection)以空间换时间的方法提高垃圾回收效率。

1 引用计数

Python垃圾回收主要以引用计数为主,分代回收为辅。引用计数法的原理是每个对象维护一个ob_ref,用来记录当前对象被引用的次数,也就是来追踪到底有多少引用指向了这个对象,当发生以下四种情况的时候,该对象的引用计数器+1。 对象被创建、对象被引用、对象被作为参数,传到函数中 、对象作为一个元素,存储在容器中 与上述情况相对应,否则该对象的引用计数器-1。当指向该对象的内存的引用计数器为0的时候,该内存将会被Python虚拟机销毁。

引用计数法的优点

  • 高效

  • 运行期没有停顿 可以类比一下Ruby的垃圾回收机制,也就是 实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。

  • 对象有确定的生命周期

  • 易于实现

缺点

  • 维护引用计数消耗资源,维护引用计数的次数和引用赋值成正比,而不像mark and sweep等基本与回收的内存数量有关。

  • 无法解决循环引用的问题。A和B相互引用而再没有外部引用A与B中的任何一个,它们的引用计数都为1,但显然应该被回收。

2 标记-清除机制

基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标记的对象释放。

3 分代技术

分代回收的整体思想是:将系统中的所有内存块根据其存活时间划分为不同的集合,每个集合就成为一个“代”,垃圾收集频率随着“代”的存活时间的增大而减小,存活时间通常利用经过几次垃圾回收来度量。

Python默认定义了三代对象集合,索引数越大,对象存活时间越长。

举例: 当某些内存块M经过了3次垃圾收集的清洗之后还存活时,我们就将内存块M划到一个集合A中去,而新分配的内存都划分到集合B中去。当垃圾收集开始工作时,大多数情况都只对集合B进行垃圾回收,而对集合A进行垃圾回收要隔相当长一段时间后才进行,这就使得垃圾收集机制需要处理的内存少了,效率自然就提高了。在这个过程中,集合B中的某些内存块由于存活时间长而会被转移到集合A中,当然,集合A中实际上也存在一些垃圾,这些垃圾的回收会因为这种分代的机制而被延迟。

25 Python内存管理

Python的内存管理是由私有heap空间管理的。所有的Python对象和数据结构都在一个私有heap中。程序员没有访问该heap的权限,只有解释器才能对它进行操作。为Python的heap空间分配内存是由Python的内存管理模块进行的,其核心API会提供一些访问该模块的方法供程序员使用。Python有自带的垃圾回收系统,它回收并释放没有被使用的内存,让它们能够被其他程序使用。

26 Python的is

is是对比地址,==是对比值

27 read,readline和readlines

  • read 读取整个文件

  • readline 读取下一行,使用生成器方法

  • readlines 读取整个文件到一个迭代器以供我们遍历

28 Python2和3的区别

print

在 Python 2 中,print 是一条语句,而 Python3 中作为函数存在。

编码

Python2 的默认编码是 asscii,这也是导致 Python2 中经常遇到编码问题的原因之一。Python 3 默认采用了 UTF-8 作为默认编码,因此你不再需要在文件顶部写 # coding=utf-8 了。

除法运算

在python 2.x中/除法就跟我们熟悉的大多数语言,比如Java啊C啊差不多,整数相除的结果是一个整数,把小数部分完全忽略掉,浮点数除法会保留小数点的部分得到一个浮点数的结果。

在python 3.x中/除法不再这么做了,对于整数之间的相除,结果也会是浮点数。

旧式类与新式类

在Python 3中,没有旧式类,只有新式类,也就是说不用再像这样 class Foobar(object): pass 显式地子类化object 但是最好还是加上. 主要区别在于 old-style 是 classtype 类型而 new-style 是 type类型 xrange重命名为range

29 super init

super() 函数是用于调用父类(超类)的一个方法。

super 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。

 class Bird:
     def __init__(self):
           self.hungry = True
     def eat(self):
           if self.hungry:
                print 'Ahahahah'
           else:
                print 'No thanks!'
 ​
 class SongBird(Bird):
      def __init__(self):
           self.sound = 'Squawk'
      def sing(self):
           print self.sound
 ​
 sb = SongBird()
 sb.sing()    # 能正常输出
 sb.eat()     # 报错,因为 songBird 中没有 hungry 特性

那解决这个问题的办法有两种:

1、调用未绑定的超类构造方法(多用于旧版 python 阵营)

 class SongBird(Bird):
      def __init__(self):
           Bird.__init__(self)
           self.sound = 'Squawk'
      def sing(self):
           print self.song()

原理:在调用了一个实例的方法时,该方法的self参数会自动绑定到实例上(称为绑定方法);如果直接调用类的方法(比如Bird.init),那么就没有实例会被绑定,可以自由提供需要的self参数(未绑定方法)。

2、使用super函数(只在新式类中有用)

 class SongBird(Bird):
      def __init__(self):
           super(SongBird,self).__init__()
           self.sound = 'Squawk'
      def sing(self):
           print self.song()

原理:它会查找所有的超类,以及超类的超类,直到找到所需的特性为止。

经典的菱形继承案例,BC 继承 A,然后 D 继承 BC,创造一个 D 的对象。

      ---> B ---
 A --|          |--> D
      ---> C ---

使用 super() 可以很好地避免构造函数被调用两次。

 class A():
     def __init__(self):
         print('enter A')
         print('leave A')
 ​
 class B(A):
     def __init__(self):
         print('enter B')
         super().__init__()
         print('leave B')
 ​
 class C(A):
     def __init__(self):
         print('enter C')
         super().__init__()
         print('leave C')
 ​
 class D(B, C):
     def __init__(self):
         print('enter D')
         super().__init__()
         print('leave D')
 ​
 d = D()

执行结果是:

 enter D
 enter B
 enter C
 enter A
 leave A
 leave C
 leave B
 leave D

30 range and xrange

在python2中,range和xrange都在循环时使用,xrange内存性能更好。 for i in range(0, 20): for i in xrange(0, 20):

要生成很大的数字序列的时候,用xrange会比range性能优很多,因为不需要一上来就开辟一块很大的内存空间,这两个基本上都是在循环的时候用。

你可能感兴趣的:(秋招,python,秋招,面试)