python笔试题

作者: marvinxu | 来源:发表于2016-06-15 20:04 被阅读2455次

    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中的viod*的感觉

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

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

    如果还不明白的话,这里有更好的解释: http://stackoverflow.com/questions/986006/how-do-i-pass-a-variable-by-reference

    python中的元类(metaclass)

    这个非常的不常用,但是像ORM这种复杂的结构还是会需要的,详情请看:《深刻理解Python中的元类(metaclass)》

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

    更多关于这个问题:http://stackoverflow.com/questions/136097/what-is-the-difference-between-staticmethod-and-classmethod-in-python

    类变量和实例变量

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

    ==可以看看下面的例子: (need check)==
    ==python中list是mutable的类变量, 实例化之后也是mutable的, 所以对第一个实例的name操作, 也会引起类变量以及其它的实例中list的更改==

    ==如何避免==

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

    参考:http://stackoverflow.com/questions/6470428/catch-multiple-exceptions-in-one-line-except-block

    python自省

    这个也是python彪悍的特性.

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

    字典推导式:

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

    你可以用任何方式的迭代器(元组,列表,生成器..),只要可迭代对象的元素中有两个值.

    d = {value: foo(value) for value in sequence if bar(value)}
    
    def key_value_gen(k):
       yield chr(k+65)
       yield chr((k+13)%26+65)
    d = dict(map(key_value_gen, range(26)))
    

    python中单下划线和双下划线

    这篇文章讨论Python中下划线_的使用。跟Python中很多用法类似,下划线_的不同用法绝大部分(不全是)都是一种惯例约定。

    单下划线(_)

    主要有三种情况:

    1. 解释器中

    _符号是指交互解释器中最后一次执行语句的返回结果。这种用法最初出现在CPython解释器中,其他解释器后来也都跟进了。

    >>> _
    Traceback (most recent call last):
      File "", line 1, in 
    NameError: name '_' is not defined
    >>> 42
    >>> _
    42
    >>> 'alright!' if _ else ':('
    'alright!'
    >>> _
    'alright!'
    
    1. 作为名称使用

    这个跟上面有点类似。_用作被丢弃的名称。按照惯例,这样做可以让阅读你代码的人知道,这是个不会被使用的特定名称。举个例子,你可能无所谓一个循环计数的值:

    n = 42
    for _ in range(n):
        do_something()
    
    1. i18n

    _还可以被用作函数名。这种情况,单下划线经常被用作国际化和本地化字符串翻译查询的函数名。这种惯例好像起源于C语言。举个例子,在 Django documentation for translation 中你可能会看到:

    from django.utils.translation import ugettext as _
    from django.http import HttpResponse
    
    def my_view(request):
        output = _("Welcome to my site.")
        return HttpResponse(output)
    

    第二种和第三种用法会引起冲突,所以在任意代码块中,如果使用了_作i18n翻译查询函数,就应该避免再用作被丢弃的变量名。

    单下划线前缀的名称(例如_shahriar)

    以单下划线做前缀的名称指定了这个名称是“私有的”。在 有些 导入import * 的场景中,下一个使用你代码的人(或者你本人)会明白这个名称仅内部使用。Python documentation里面写道:

    a name prefixed with an underscore (e.g. _spam) should be treated as a non-public part of the API (whether it is a function, a method or a data member). It should be considered an implementation detail and subject to change without notice.

    之所以说在在 有些 import * 的场景,是因为导入时解释器确实对单下划线开头的名称做了处理。如果你这么写from <module/package> import *,任何以单下划线开头的名称都不会被导入,除非模块/包的__all__列表明确包含了这些名称。更多相关信息见““Importing * in Python”

    双下划线前缀的名称(例如__shahriar

    以双下划线做前缀的名称(特别是方法名)并不是一种惯例;它对解释器有特定含义。Python会改写这些名称,以免与子类中定义的名称产生冲突。Python documentation中提到,任何__spam这种形式(至少以两个下划线做开头,绝大部分都还有一个下划线做结尾)的标识符,都会文本上被替换为_classname__spam,其中classname是当前类名,并带上一个下划线做前缀。
    看下面这个例子:

    >>> class A(object):
    ...     def _internal_use(self):
    ...         pass
    ...     def __method_name(self):
    ...         pass
    ... 
    >>> dir(A())
    ['_A__method_name', ..., '_internal_use']
    

    正如所料,_internal_use没有变化,但__method_name被改写成了_ClassName__method_name。现在创建一个A的子类B(这可不是个好名字),就不会轻易的覆盖掉A中的__method_name了:

    >>> class B(A):
    ...     def __method_name(self):
    ...         pass
    ... 
    >>> dir(B())
    ['_A__method_name', '_B__method_name', ..., '_internal_use']
    

    这种特定的行为差不多等价于Java中的final方法和C++中的正常方法(非虚方法)。

    前后都带有双下划线的名称(例如__init__

    这些是Python的特殊方法名,这仅仅是一种惯例,一种确保Python系统中的名称不会跟用户自定义的名称发生冲突的方式。通常你可以覆写这些方法,在Python调用它们时,产生你想得到的行为。例如,当写一个类的时候经常会覆写__init__方法。
    你也可以写出自己的“特殊方法”名(但是别这么做):

    >>> class C(object):
    ...     def __mine__(self):
    ...         pass
    ...
    >>> dir(C)
    ... [..., '__mine__', ...]
    

    还是不要这样写方法名,只让Python定义的特殊方法名使用这种惯例吧。

    详情见:http://stackoverflow.com/questions/1301346/the-meaning-of-a-single-and-a-double-underscore-before-an-object-name-in-python

    或者: http://www.zhihu.com/question/19754941

    字符串格式化:%和.format

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

    hi there %s" % name

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

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

    但是有点丑..format就没有这些问题.你给的第二个问题也是这样,.format好看多了.

    你为什么不用它?

    不知道它(在读这个之前)
    为了和Python2.5兼容(譬如logging库建议使用%(issue #4))

    http://stackoverflow.com/questions/5082452/python-string-formatting-vs-format

    迭代器和生成器

    这个是stackoverflow里python排名第一的问题,值得一看: http://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do-in-python

    这是中文版: http://taizilongxu.gitbooks.io/stackoverflow-about-python/content/1/README.html

    Iterables

    当你创建了一个列表,你可以一个一个的读取它的每一项,这叫做iteration:

    >>> mylist = [1, 2, 3]
    >>> for i in mylist:
    ...    print(i)
    1
    2
    3
    

    Mylist是可迭代的.当你用列表推导式的时候,你就创建了一个列表,而这个列表也是可迭代的:

    >>> mylist = [x*x for x in range(3)]
    >>> for i in mylist:
    ...    print(i)
    0
    1
    4
    

    所有你可以用在for...in...语句中的都是可迭代的:比如lists,strings,files...因为这些可迭代的对象你可以随意的读取所以非常方便易用,但是你必须把它们的值放到内存里,当它们有很多值时就会消耗太多的内存.

    Generators

    生成器也是迭代器的一种,但是你只能迭代它们一次.原因很简单,因为它们不是全部存在内存里,它们只在要调用的时候在内存里生成:

    >>> mygenerator = (x*x for x in range(3))
    >>> for i in mygenerator:
    ...    print(i)
    0
    1
    4
    

    生成器和迭代器的区别就是用()代替[],还有你不能用for i in mygenerator第二次调用生成器:首先计算0,然后会在内存里丢掉0去计算1,直到计算完4.

    Yield

    Yield的用法和关键字return差不多,下面的函数将会返回一个生成器:

    >>> def createGenerator():
    ...    mylist = range(3)
    ...    for i in mylist:
    ...        yield i*i
    ...
    >>> mygenerator = createGenerator() # 创建生成器
    >>> print(mygenerator) # mygenerator is an object!
    <generator object createGenerator at 0xb7555c34>
    >>> for i in mygenerator:
    ...     print(i)
    0
    1
    4
    

    在这里这个例子好像没什么用,不过当你的函数要返回一个非常大的集合并且你希望只读一次的话,那么它就非常的方便了.

    要理解Yield你必须先理解当你调用函数的时候,函数里的代码并没有运行.函数仅仅返回生成器对象,这就是它最微妙的地方:-)

    然后呢,每当for语句迭代生成器的时候你的代码才会运转.

    现在,到了最难的部分:

    当for语句第一次调用函数里返回的生成器对象,函数里的代码就开始运作,直到碰到yield,然后会返回本次循环的第一个返回值.所以下一次调用也将运行一次循环然后返回下一个值,直到没有值可以返回.

    一旦函数运行并没有碰到yeild语句就认为生成器已经为空了.原因有可能是循环结束或者没有满足if/else之类的.

    Itertools你的好基友

    itertools模块包含了一些特殊的函数可以操作可迭代对象.有没有想过复制一个生成器?链接两个生成器?把嵌套列表里的值组织成一个列表?Map/Zip还不用创建另一个列表?

    来吧import itertools

    来一个例子?让我们看看4匹马比赛有多少个排名结果:

    >>> horses = [1, 2, 3, 4]
    >>> races = itertools.permutations(horses)
    >>> print(races)
    <itertools.permutations object at 0xb754f1dc>
    >>> print(list(itertools.permutations(horses)))
    [(1, 2, 3, 4),
     (1, 2, 4, 3),
     (1, 3, 2, 4),
     (1, 3, 4, 2),
     (1, 4, 2, 3),
     (1, 4, 3, 2),
     (2, 1, 3, 4),
     (2, 1, 4, 3),
     (2, 3, 1, 4),
     (2, 3, 4, 1),
     (2, 4, 1, 3),
     (2, 4, 3, 1),
     (3, 1, 2, 4),
     (3, 1, 4, 2),
     (3, 2, 1, 4),
     (3, 2, 4, 1),
     (3, 4, 1, 2),
     (3, 4, 2, 1),
     (4, 1, 2, 3),
     (4, 1, 3, 2),
     (4, 2, 1, 3),
     (4, 2, 3, 1),
     (4, 3, 1, 2),
     (4, 3, 2, 1)]
    

    理解迭代的内部机制

    迭代是可迭代对象(对应iter()方法)和迭代器(对应next()方法)的一个过程.可迭代对象就是任何你可以迭代的对象(废话啊).迭代器就是可以让你迭代可迭代对象的对象(有点绕口,意思就是这个意思)

    *args and **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')
    cabbage = vegetable
    apple = fruit
    

    *args和**kwargs 必须放在参数列表的后面。

    面向切面编程AOP和装饰器

    这个AOP一听起来有点懵,同学面阿里的时候就被问懵了…

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

    这个问题比较大,推荐: http://stackoverflow.com/questions/739654/how-can-i-make-a-chain-of-function-decorators-in-python

    中文: http://taizilongxu.gitbooks.io/stackoverflow-about-python/content/3/README.html

    • 看一个简单的例子
    # 字体变粗装饰器
    def makebold(fn):
        # 装饰器将返回新的函数
        def wrapper():
            # 在之前或者之后插入新的代码
            return "<b>" + fn() + "</b>"
        return wrapper
    
    # 斜体装饰器
    

    def makeitalic(fn):
    # 装饰器将返回新的函数
    def wrapper():
    # 在之前或者之后插入新的代码
    return "<i>" + fn() + "</i>"
    return wrapper

    @makebold
    @makeitalic
    def say():
    return "hello"

    print say()

    输出: <b><i>hello</i></b>

    这相当于

    def say():
    return "hello"
    say = makebold(makeitalic(say))

    print say()

    输出: <b><i>hello</i></b>

    - 用法:
        1. 传统用法是给外部的不可更改的库做扩展
        2. Django用装饰器管理缓存和试图的权限.
        3. Twisted用来修改异步函数的调用.
        4. etc.
    
    # 鸭子类型
    “当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
    
    我们并不关心对象是什么类型,到底是不是鸭子,只关心行为。
    
    比如在python中,有很多file-like的东西,比如StringIO,GzipFile,socket。它们有很多相同的方法,我们把它们当作文件使用。
    
    又比如list.extend()方法中,我们并不关心它的参数是不是list,只要它是可迭代的,所以它的参数可以是list/tuple/dict/字符串/生成器等.
    
    鸭子类型在动态语言中经常使用,非常灵活,使得python不想java那样专门去弄一大堆的设计模式。
    
    # Python中重载
    引自知乎:http://www.zhihu.com/question/20053359
    
    函数重载主要是为了解决两个问题:
    
      - 可变参数类型
      - 可变参数个数
    
    另外,一个基本的设计原则是,仅仅当两个函数除了参数类型和参数个数不同以外,其功能是完全相同的,此时才使用函数重载,如果两个函数的功能其实不同,那么不应当使用重载,而应当使用一个名字不同的函数。
    
    好吧,那么对于情况 1 ,函数功能相同,但是参数类型不同,python 如何处理?答案是根本不需要处理,因为 python 可以接受任何类型的参数,如果函数的功能相同,那么不同的参数类型在 python 中很可能是相同的代码,没有必要做成两个不同函数。
    
    那么对于情况 2 ,函数功能相同,但参数个数不同,python 如何处理?大家知道,答案就是缺省参数。对那些缺少的参数设定为缺省参数即可解决问题。因为你假设函数功能相同,那么那些缺少的参数终归是需要用的。
    
    好了,鉴于情况 1 跟 情况 2 都有了解决方案,==python 自然就不需要函数重载了==
    
    # 新式类与旧式类
    这个面试官问了,我说了老半天,不知道他问的真正意图是什么.
    
    stackoverflow(http://stackoverflow.com/questions/54867/what-is-the-difference-between-old-style-and-new-style-classes-in-python)
    
    这篇文章很好的介绍了新式类的特性: http://www.cnblogs.com/btchenguang/archive/2012/09/17/2689146.html
    
    简单的说,新式类是在创建的时候继承内置object对象(或者是从内置类型,如list,dict等),而经典类是直
    接声明的。使用dir()方法也可以看出新式类中定义很多新的属性和方法,而经典类好像就2个:
    
    新式类很早在2.2就出现了,所以旧式类完全是兼容的问题,Python3里的类全部都是新式类.这里有一个MRO问题可以了解下(新式类是广度优先,旧式类是深度优先),<Python核心编程>里讲的也很多.
    
    

    新式类

    class C(object):
    pass

    经典类

    class B:
    pass

    # `__new__`和`__init__`的区别
    这个`__new__`确实很少见到,先做了解吧.
    
    `__new__`是一个静态方法,而`__init__`是一个实例方法.
    
    `__new__`方法会返回一个创建的实例,而`__init__`什么都不返回.
    
    只有在`__new__`返回一个cls的实例时后面的`__init__`才能被调用.
    
    当创建一个新实例时调用`__new__`,初始化一个实例时用`__init__`.
    
    stackoverflow(http://stackoverflow.com/questions/674304/pythons-use-of-new-and-init)
    
    ps: `__metaclass__`是创建类时起作用.所以我们可以分别使用`__metaclass__`,`__new__`和`__init__`来分别在类创建,实例创建和实例初始化的时候做一些小手脚.
    
    # 单例模式
    ==这个绝对长考, 绝对要记住1~2个方法.==
    
    所谓单例,是指一个类的实例从始至终只能被创建一次。
    
    ## 使用`__new__`方法
    

    class Singleton(object):
    def new(cls,args,kwargs):
    if not hasattr(cls,'_inst'):
    cls._inst=super(Singleton,cls).new(cls,
    args,**kwargs)
    return cls._inst
    if name=='main':
    class A(Singleton):
    def init(self,s):
    self.s=s
    a=A('apple')
    b=A('banana')
    print id(a),a.s
    print id(b),b.s

    结果:
    

    29922256 banana
    29922256 banana

    通过`__new__`方法,将类的实例在创建的时候绑定到类属性`_inst`上。如果`cls._inst`为None,说明类还未实例化,实例化并将实例绑定到`cls._inst`,以后每次实例化的时候都返回第一次实例化创建的实例。注意从Singleton派生子类的时候,不要重载`__new__`。
    ## 共享属性
    有时候我们并不关心生成的实例是否具有同一id,而只关心其状态和行为方式。我们可以允许许多个实例被创建,但所有的实例都共享状态和行为方式:
    

    class Borg(object):
    _shared_state={}
    def new(cls,args,kwargs):
    obj=super(Borg,cls).new(cls,
    args,**kwargs)
    obj.dict=cls._shared_state
    return obj

    将所有实例的__dict__指向同一个字典,这样实例就共享相同的方法和属性。对任何实例的名字属性的设置,无论是在__init__中修改还是直接修改,所有的实例都会受到影响。不过实例的id是不同的。要保证类实例能共享属性,但不和子类共享,注意使用cls._shared_state,而不是Borg._shared_state。
    
    因为实例是不同的id,所以每个实例都可以做字典的key:
    

    if name=='main':
    class Example(Borg):
    pass
    a=Example()
    b=Example()
    c=Example()
    adict={}
    j=0
    for i in a,b,c:
    adict[i]=j
    j+=1
    for i in a,b,c:
    print adict[i]
    结果:
    0
    1
    2

    如果这种行为不是你想要的,可以为Borg类添加__eq__和__hash__方法,使其更接近于单例模式的行为:
    

    class Borg(object):
    _shared_state={}
    def new(cls,args,kwargs):
    obj=super(Borg,cls).new(cls,
    args,**kwargs)
    obj.dict=cls._shared_state
    return obj
    def hash(self):
    return 1
    def eq(self,other):
    try:
    return self.dict is other.dict
    except:
    return False
    if name=='main':
    class Example(Borg):
    pass
    a=Example()
    b=Example()
    c=Example()
    adict={}
    j=0
    for i in a,b,c:
    adict[i]=j
    j+=1
    for i in a,b,c:
    print adict[i]
    结果:
    2
    2
    2

    所有的实例都能当一个key使用了。
    ## 装饰器版本
    

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

    @singleton
    class MyClass:
    ...

    ## 基于元组
    当你编写一个类的时候,某种机制会使用类名字,基类元组,类字典来创建一个类对象。新型类中这种机制默认为type,而且这种机制是可编程的,称为元类__metaclass__ 。
    

    class Singleton(type):
    def init(self,name,bases,class_dict):
    super(Singleton,self).init(name,bases,class_dict)
    self._instance=None
    def call(self,args,kwargs):
    if self._instance is None:
    self._instance=super(Singleton,self).call(
    args,**kwargs)
    return self._instance
    if name=='main':
    class A(object):
    metaclass=Singleton
    a=A()
    b=A()
    print id(a),id(b)
    结果:

    34248016 34248016

    id是相同的。
    
    例子中我们构造了一个Singleton元类,并使用`__call__`方法使其能够模拟函数的行为。构造类A时,将其元类设为Singleton,那么创建类对象A时,行为发生如下:
    
    `A=Singleton(name,bases,class_dict)`,A其实为Singleton类的一个实例。
    
    创建A的实例时,`A()=Singleton(name,bases,class_dict)()=Singleton(name,bases,class_dict).__call__()`,这样就将A的所有实例都指向了A的属性`_instance`上,这种方法与方法1其实是相同的。
    ## 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()

    ## python中的作用域
    Python 中,一个变量的作用域总是由在代码中被赋值的地方所决定的。
    
    当 Python 遇到一个变量的话他会按照这样的顺序进行搜索:
    
    本地作用域(Local)→当前作用域被嵌入的本地作用域(Enclosing locals)→全局/模块作用域(Global)→内置作用域(Built-in)
    ## GIL线程全局锁
    
    线程全局锁(Global Interpreter Lock),即Python为了保证线程安全而采取的独立线程运行的限制,说白了就是一个核只能在同一时间运行一个线程.
    
    见Python 最难的问题http://www.oschina.net/translate/pythons-hardest-problem
    
    ==解决办法就是多进程和下面的协程(协程也只是单CPU,但是能减小切换代价提升性能).==
    ## 协程
    知乎被问到了,呵呵哒,跪了
    
    简单点说协程是进程和线程的升级版,进程和线程都面临着内核态和用户态的切换问题而耗费许多切换时间,而协程就是用户自己控制切换的时机,不再需要陷入系统的内核态.
    
    Python里最常见的yield就是协程的思想!可以查看第九个问题.
    ## 闭包
    闭包(closure)是函数式编程的重要的语法结构。闭包也是一种组织代码的结构,它同样提高了代码的可重复使用性。
    
    当一个内嵌函数引用其外部作作用域的变量,我们就会得到一个闭包. 总结一下,创建一个闭包必须满足以下几点:
    
    必须有一个内嵌函数
    内嵌函数必须引用外部函数中的变量
    外部函数的返回值必须是内嵌函数
    
    感觉闭包还是有难度的,几句话是说不明白的,还是查查相关资料.
    
    重点是函数运行后并不会被撤销,就像16题的instance字典一样,当函数运行完后,instance并不被销毁,而是继续留在内存空间里.这个功能类似类里的类变量,只不过迁移到了函数上.
    
    闭包就像个空心球一样,你知道外面和里面,但你不知道中间是什么样.
    ## lambda函数
    其实就是一个匿名函数,为什么叫lambda?因为和后面的函数式编程有关.
    
    推荐: 知乎(http://www.zhihu.com/question/20125256 )
    ## python函数式编程
    这个需要适当的了解一下吧,毕竟函数式编程在Python中也做了引用.
    
    推荐: 酷壳(http://coolshell.cn/articles/10822.html )
    
    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

    ## python里的拷贝
    引用和copy(),deepcopy()的区别:
    1. copy.copy 浅拷贝 只拷贝父对象,不会拷贝对象的内部的子对象。
    2. copy.deepcopy 深拷贝 拷贝对象及其子对象
    3. copy拷贝一个对象,但是对象的属性还是引用原来的,deepcopy拷贝一个对象,把对象里面的属性也做了拷贝,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 垃圾回收机制
    Python GC主要使用引用计数(reference counting)来跟踪和回收垃圾。在引用计数的基础上,通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用问题,通过“分代回收”(generation collection)以空间换时间的方法提高垃圾回收效率。
    ### 引用计数
    PyObject是每个对象必有的内容,其中`ob_refcnt`就是做为引用计数。当一个对象有新的引用时,它的`ob_refcnt`就会增加,当引用它的对象被删除,它的ob_refcnt就会减少.引用计数为0时,该对象生命就结束了。
    
    - 优点:
    
      - 简单
      - 实时性
    
    - 缺点:
    
      - 维护引用计数消耗资源
      - 循环引用
    
    ## 标记\-清楚机制
    基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标记的对象释放。
    ## 分代技术
    分代回收的整体思想是:将系统中的所有内存块根据其存活时间划分为不同的集合,每个集合就成为一个“代”,垃圾收集频率随着“代”的存活时间的增大而减小,存活时间通常利用经过几次垃圾回收来度量。
    
    Python默认定义了三代对象集合,索引数越大,对象存活时间越长。
    
    举例:
      当某些内存块M经过了3次垃圾收集的清洗之后还存活时,我们就将内存块M划到一个集合A中去,而新分配的内存都划分到集合B中去。当垃圾收集开始工作时,大多数情况都只对集合B进行垃圾回收,而对集合A进行垃圾回收要隔相当长一段时间后才进行,这就使得垃圾收集机制需要处理的内存少了,效率自然就提高了。在这个过程中,集合B中的某些内存块由于存活时间长而会被转移到集合A中,当然,集合A中实际上也存在一些垃圾,这些垃圾的回收会因为这种分代的机制而被延迟。
    # python的list
    推荐: http://www.jianshu.com/p/J4U6rR (c语言的实现)
    - 基本列表操作:
        - 删除  
        `del list[2]`
        - 分片赋值  
        `name[2:] = list('ar')`
    - append
    

    list.append(2)

    - count
    

    x = [[1,2],1,1,[2,1,[1,2]]]
    x.count([1,2])
    1
    x.count(1)
    2

    - append
    用于在列表末尾追加新的对象
    

    lst = [1,2,3,4]
    lst.append[4]
    lst
    [1,2,3,4]

    - extend
    可以在列表末尾一次性追加另一个序列的多个值
    

    a = [1,2,3]
    b = [4,5,6]
    a.extend(b)
    a
    [1,2,3,4,5,6]

    看起来与`a+b`操作很像, 但是extend方法修改了被扩展序列,而`a+b`则是返回新的序列
    

    a = [1,2,3]
    b = [4,5,6]
    a+b
    [1,2,3,4,5,6]
    a
    [1,2,3]

    - index方法
    查找元素在列表中的位置
    

    L= [1,2,3,3]
    [1,2,3,3]
    L.index(3)
    2

    - insert方法
    

    L= [1,2,3]
    [1,2,3]
    L.insert(0,10)
    [10,1,2,3]

    - pop方法
    

    L= [1,2,3]
    [1,2,3]
    L.pop(0)
    1
    L
    [2,3]

    Perl的列表array里面pop只能弹出右侧的一个元素, 而这个可以弹出指定的index元素
    有返回值, 返回值是弹出的元素, 并且修改了原列表
    - remove方法
    移除列表中某个值的第一个匹配项
    

    L= [1,2,3,3,4]
    [1,2,3,3,4]
    L.remove(3)
    L
    [1,2,3,4]

    没有返回值,原位修改
    - sort方法
    sort方法用于在原位置对列表进行排序。
    

    L= [1,2,3,5,4]
    L.sort()
    L
    [1,2,3,4,5]

    - reverse方法
    

    L= [1,2,3,3,4]
    [1,2,3,3,4]
    L.reverse()
    L
    [4,3,3,2,1]

    - sort 与sorted()的关系
    - 相同:
        - 都是排序
        - 都支持key, reverse参数, 其中key的话可以实现高级排序
    - 不同
        -  sort只对list起作用, 而sorted是全局函数,对任何可迭代的序列均可以使用
        -  sort是原位修改,而sorted()会返回新的列表
    
    详情请看( https://github.com/qiwsir/algorithm/blob/master/python_sort.md )
    
    # python的is
    is是对比地址,==是对比值
    # read, readline和readlines
    - read 读取整个文件
    - readline 读取下一行,使用生成器方法
    - readlines 读取整个文件到一个迭代器以供我们遍历
    
    # python2和3的区别
    推荐:《Python 2.7.x 和 3.x 版本的重要区别》http://python.jobbole.com/80006/
    # 操作系统
    ## select,poll和epoll
    其实所有的I/O都是轮询的方法,只不过实现的层面不同罢了.
    
    这个问题可能有点深入了,但相信能回答出这个问题是对I/O多路复用有很好的了解了.其中tornado使用的就是epoll的.
    
    selec,poll和epoll区别总结(http://www.cnblogs.com/Anker/p/3265058.html )
    
    基本上select有3个缺点:
    
      - 连接数受限
      - 查找配对速度慢
      - 数据由内核拷贝到用户态
    
    poll改善了第一个缺点
    
    epoll改了三个缺点.
    
    关于epoll的: http://www.cnblogs.com/my_life/articles/3968782.html
    ## 调度算法
    1. 先来先服务(FCFS, First Come First Serve)
    2. 短作业优先(SJF, Shortest Job First)
    3. 最高优先权调度(Priority Scheduling)
    4. 时间片轮转(RR, Round Robin)
    5. 多级反馈队列调度(multilevel feedback queue
    6. scheduling)
    
    - 实时调度算法:
    
    1. 最早截至时间优先 EDF
    2. 最低松弛度优先 LLF
    
    ## 死锁
    - 原因:
    
    1. 竞争资源
    2. 程序推进顺序不当
    
    - 必要条件:
    
    1. 互斥条件
    2. 请求和保持条件
    3. 不剥夺条件
    4. 环路等待条件
    
    - 处理死锁基本方法:
    
    1. 预防死锁(摒弃除1以外的条件)
    2. 避免死锁(银行家算法)
    3. 检测死锁(资源分配图)
    4. 解除死锁
        1. 剥夺资源
        2. 撤销进程
    
    ## 程序编译与链接
    推荐: http://www.ruanyifeng.com/blog/2014/11/compiler.html
    
    Bulid过程可以分解为4个步骤:预处理(Prepressing), 编译(Compilation)、汇编(Assembly)、链接(Linking)
    
    以c语言为例:
    
    - 预处理
    
    预编译过程主要处理那些源文件中的以“#”开始的预编译指令,主要处理规则有:
    
    将所有的“#define”删除,并展开所用的宏定义
    处理所有条件预编译指令,比如“#if”、“#ifdef”、 “#elif”、“#endif”
    处理“#include”预编译指令,将被包含的文件插入到该编译指令的位置,注:此过程是递归进行的
    删除所有注释
    添加行号和文件名标识,以便于编译时编译器产生调试用的行号信息以及用于编译时产生编译错误或警告时可显示行号
    保留所有的#pragma编译器指令。
    
    - 编译
    
    编译过程就是把预处理完的文件进行一系列的词法分析、语法分析、语义分析及优化后生成相应的汇编代码文件。这个过程是整个程序构建的核心部分。
    
    - 汇编
    
    汇编器是将汇编代码转化成机器可以执行的指令,每一条汇编语句几乎都是一条机器指令。经过编译、链接、汇编输出的文件成为目标文件(Object File)
    
    - 链接
    
    链接的主要内容就是把各个模块之间相互引用的部分处理好,使各个模块可以正确的拼接。
    链接的主要过程包块 地址和空间的分配(Address and Storage Allocation)、符号决议(Symbol Resolution)和重定位(Relocation)等步骤。
    
    - 静态链接和动态链接
    
    静态链接方法:静态链接的时候,载入代码就会把程序会用到的动态代码或动态代码的地址确定下来
    静态库的链接可以使用静态链接,动态链接库也可以使用这种方法链接导入库
    
    动态链接方法:使用这种方式的程序并不在一开始就完成动态链接,而是直到真正调用动态库代码时,载入程序才计算(被调用的那部分)动态代码的逻辑地址,然后等到某个时候,程序又需要调用另外某块动态代码时,载入程序又去计算这部分代码的逻辑地址,所以,这种方式使程序初始化时间较短,但运行期间的性能比不上静态链接的程序
    
    - 虚拟内存技术
    
    虚拟存储器是值具有请求调入功能和置换功能,能从逻辑上对内存容量加以扩充的一种存储系统.
    
    - 分页和分段
    
    分页: 用户程序的地址空间被划分成若干固定大小的区域,称为“页”,相应地,内存空间分成若干个物理块,页和块的大小相等。可将用户程序的任一页放在内存的任一块中,实现了离散分配。
    
    分段: 将用户程序地址空间分成若干个大小不等的段,每段可以定义一组相对完整的逻辑信息。存储分配时,以段为单位,段与段在内存中可以不相邻接,也实现了离散分配。
    
    分页与分段的主要区别
    
    页是信息的物理单位,分页是为了实现非连续分配,以便解决内存碎片问题,或者说分页是由于系统管理的需要.段是信息的逻辑单位,它含有一组意义相对完整的信息,分段的目的是为了更好地实现共享,满足用户的需要.
    页的大小固定,由系统确定,将逻辑地址划分为页号和页内地址是由机器硬件实现的.而段的长度却不固定,决定于用户所编写的程序,通常由编译程序在对源程序进行编译时根据信息的性质来划分.
    分页的作业地址空间是一维的.分段的地址空间是二维的.
    
    - 页面置换算法
    
    最佳置换算法OPT:不可能实现
    先进先出FIFO
    最近最久未使用算法LRU:最近一段时间里最久没有使用过的页面予以置换.
    clock算法
    
    - 边沿触发和水平触发
    
    边缘触发是指每当状态变化时发生一个 io 事件,条件触发是只要满足条件就发生一个 io 事件
    
    # 数据库
    ## 事物
    数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。
    ## 数据库索引
    推荐: http://tech.meituan.com/mysql-index.html
    
    MySQL索引背后的数据结构及算法原理(http://blog.jobbole.com/24006/)
    
    聚集索引,非聚集索引,B-Tree,B+Tree,最左前缀原理
    ## Redis原理
    ## 乐观锁和悲观锁
    悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作
    
    乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。
    ## MVCC
    ## MyISAM和InnoDB
    MyISAM 适合于一些需要大量查询的应用,但其对于有大量写操作并不是很好。甚至你只是需要update一个字段,整个表都会被锁起来,而别的进程,就算是读进程都无法操作直到读操作完成。另外,MyISAM 对于 SELECT COUNT(*) 这类的计算是超快无比的。
    
    InnoDB 的趋势会是一个非常复杂的存储引擎,对于一些小的应用,它会比 MyISAM 还慢。他是它支持“行锁” ,于是在写操作比较多的时候,会更优秀。并且,他还支持更多的高级应用,比如:事务。

    相关文章

      网友评论

      本文标题:python笔试题

      本文链接:https://www.haomeiwen.com/subject/vojpdttx.html