阿虎说
Python常见面试题

Python语言特性

类变量和实例变量

类变量

是可在类的所有实例之间共享的值(也就是说,它们不是单独分配给每个实例的)。

例如下例中,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('wkt')  
    print Test.num_of_instance   # 1
    t2 = Test('hy')  
    print t1.name , t1.num_of_instance  # wkt 2
    print t2.name , t2.num_of_instance  # hy 2
class Student(object):
    name = "hy"

s1 = Student()
s2 = Student()
s1.name = "wkt"
print s1.name  # wkt
print s2.name  # hy
print Student.name  # hy


class Teacher:
    students = []

t1 = Teacher()
t2 = Teacher()
t1.students.append(1)
print t1.students  # [1]
print t2.students  # [1]
print Teacher.students  # [1]

metaclass

  • object是所有类的超类,而且type也是继承自object;所有对象创建自type,而且object也是type的实例。
  • object和type是python中的两个源对象,事实上,它们是互相依赖对方来定义,所以它们不能分开而论。
  • 通过这两个源对象可以繁育出一堆对象:list、tuple、dict等。元类就是这些类的类,这些类是元类的实例。
  • metaclass是type的子类,通过替换type的call运算符重载机制,“超越变形”正常的类。

Python自省

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

newinit 区别

  1. new是一个静态方法,而init是一个实例方法。
  2. new方法会返回一个创建的实例,而init什么都不会返回。
  3. 只有在new返回一个cls的实例后,init才会被调用。
  4. 当创建一个新实例时调用new,初始化一个实例时调用init

metaclass 的作用

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

单例模式

  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 Test(Singleton):
    
    pass
    
    
  2. 装饰器

    def singleton(cls):
    instances = {}
    def get_instance(cls, *args, **kw)
        if cls not in instances:
            instances[cls] = cls(*arg, **kw)
        return instances[cls]
    return get_instance
    
    @singleton
    class Test:
    pass
    
  3. import

    # singleton.py
    class My_Singleton(object):
    pass
    
    my_singleton = My_Singleton()
    
    # use
    from singleton import my_singleton
    

GIL线程全局锁

线程全局锁(Global Interpreter Lock),Python为了保证线程安全而采取的独立线程运行的限制,一个核只能在同一时间运行一个线程。Python中的多线程是假的多线程,Python解释器虽然可以开启多个线程,但同一时间只有一个线程能在解释器中执行,而做到这一点正是由于GIL锁的存在,它的存在使得CPU资源同一时间只会给一个线程使用。

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

线程锁

线程锁是由于在进程进行数据操作时要保证数据操作的安全性(同一进程中线程之间可以共用信息,如果同时对数据进行操作,则会出现公共数据错误)。

进程和线程

进程是资源(CPU、内存)分配的基本单位,它是执行程序是的一个实例。程序运行时系统会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。

线程是程序执行时的最小单位,它是进程的执行流,是CPU调度和分派的基本单位,一个进程可以由多个线程组成,线程间共享进程的所有资源,每个进程都有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。

区别

  1. 进程是资源分配的最小单位,线程是程序执行的最小单位。
  2. 进程有自己的独立空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
  3. 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。
  4. 多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。

使用场景

IO密集型的程序,可以使用多线程提高程序整体效率(socket server 网络并发)。程序是CPU密集型的,使用Python的多线程是无法提升效率的,使用多进程来实现。

CPU密集型(CPU-bound)

CPU密集型也叫计算密集型,指的是系统的硬盘、内存性能相对CPU要好很多,此时,系统运作大部分的状况是CPU Loading 100%,CPU要读/写I/O(硬盘/内存)

I/O在很短的时间就可以完成,而CPU还有许多运算要处理,CPU Loading很高。

IO密集型(I/O bound)

IO密集型指的是系统的CPU性能相对硬盘、内存要好很多,此时,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高

I/O bound的程序一般在达到性能极限时,CPU占用率仍然较低。这可能是因为任务本身需要大量I/O操作,而pipeline做得不是很好,没有充分利用处理器能力

协程

协程是一种用户态的轻量级线程,即线程是由用户程序自己控制调度的。

优点

  1. 协程的开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级。
  2. 单线程内就能实现并发的效率,最大限度的利用CPU。

缺点

  1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程开启多个线程,每个线程开启协程。
  2. 协程是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程。

特点

  1. 必须在只有一个单线程里实现并发。
  2. 修改共享数据不需加锁。
  3. 用户程序里自己保存多个控制流的上下文栈。
  4. 一个协程遇到IO操作自动切换到其它协程。

应用场景

yield

  1. 提高并发。

并行和并发

并行(parallel)指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从微观还是从宏观来看,二者都是一起执行的。

并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。

Python垃圾回收机制

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

  • 引用计数 PyObject是每个对象必须有的内容,其中ob_refcnt就是作为引用计数。当一个对象有新的引用是,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会被减少。引用计数为0时,该对象生命就结束了。

优点:

  1. 简单
  2. 实时性

缺点:

  1. 维护引用计数消耗资源
  2. 循环引用
  • 标记-清除机制 基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没有标记的对象释放。

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

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

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

Python的魔法方法

魔法方法就是可以给你的类增加魔力的特殊方法,如果你的对象实现(重载)了这些方法中的某一个,那么这个方法就会在特殊的情况下被Python所调用,它们通常是两个下划线包围起来命名的(比如initlen

非常详细的介绍 python魔法方法

浅拷贝是拷贝一层,深层次的对象级别的就拷贝引用;深拷贝是拷贝多层,每一级别的数据都会拷贝出来;

可变类型和不可变类型

可变类型

是指变量所指向的内存地址处的值是可以被改变的。

类型有:Set(集合)、List(列表)、Dict(字典)

不可变类型

是指变量所指向的内存地址处的值是不可被改变的。

类型有:Number(数字)、String(字符串)、Tuple(元组)

闭包

闭包的特点就是内部函数引用了外部函数中的变量。

判断是否是闭包函数

函数名.closure在函数式闭包函数时,返回一个cell元素;不是闭包时,返回None。

装饰器

装饰器(Decorator),能在代码运行期间动态增加功能。

示例


import time

def cost(func):
    def wrapper(*args, **kwargs):
        t1 = time.time()
        result = func(*args, **kwargs)
        t2 = time.time()
        print('cost: %s ms', func.__name__, (t2 - t1) * 1000)
    return wrapper

@cost
def test():
    pass

# test() 相当于执行 cost(test)

带参数的装饰器


import time


def cost(timeout=1):
    def decorator(func):
        def wrapper(*args, **kwargs):
            t1 = time.time()
            result = func(*args, **kwargs)
            t2 = time.time()
            print('cost: %s ms', func.__name__, (t2 - t1) * 1000)
        return wrapper
    return decorator

@cost(timeout=2)
def test():
    pass

# test() 相当于执行 cost(timeout=2)(test)

偏函数

functools.partial的作用就是,把设置某个函数的默认值,返回一个新的函数,调用这个新函数会更简单。

迭代

Python的for循环不仅可以用在list或tuple上,还可以作用在其他可迭代对象上。

list这种数据类型虽然有下标,但很多其他数据类型是没有下标的,但是,只要是可迭代对象,无论有无下标,都可以迭代,比如dict就可以迭代。

dict的存储不是按照list的方式顺序排列,所以,迭代出的结果顺序很可能不一样。

默认情况下,dict迭代的是key。

判断可迭代 通过collections模块的Iterable类型判断。

from collections import Iterable

isinstance('abc', Iterable)

生成器

在Python中,这种一边循环一边计算的机制,称为生成器:generator。

数据库

mysql

事务

事务主要用于处理操作量大、复杂度高的数据。

事务是一个最小的不可再分的工作单元;通常一个事务对应一个完整的业务。

ACID

事务四大特征(ACID)

  • 原子性(Atomicity):指一个事务是一个不可分割的工作单位,其中的操作要么都做,要么都不做;如果事务中一个sql语句执行失败,则已执行的语句也必须回滚,数据库退回到事务前的状态。
  • 一致性(Consistency):事务执行结束后,数据库的完整性约束没有被破坏,事务执行的前后都是合法的数据状态。
  • 隔离性(Isolation):事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰。严格的隔离性,对应了事务隔离级别中的Serializable (可串行化)。
  • 持久性(Durability):事务一旦提交,它对数据库的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

事务的隔离性


title: ‘Python常见面试题目’ date: 2021-06-23T20:00:00+08:00

draft: false

锁机制

隔离性要求同一时刻只能有一个事务对数据进行写操作,InnoDB通过锁机制来保证这一点。

锁可以分为表锁、行锁以及其他位于二者之间的锁。

表锁在操作数据时会锁定整张表,并发性能较差;行锁则只锁定需要操作的数据,并发性能好。但是由于加锁本身需要消耗资源(获得锁、检查锁、释放锁等都需要消耗资源),因此在锁定数据较多情况下使用表锁可以节省大量资源。MySQL中不同的存储引擎支持的锁是不一样的,例如MyIsam只支持表锁,而InnoDB同时支持表锁和行锁,且出于性能考虑,绝大多数情况下使用的都是行锁。

隔离级别
  • read uncommitted,读未提交。 脏读:可能。 不可重复读:可能。 幻读:可能。

  • read committed,读已提交。 脏读:不可能。 不可重复读:可能。 幻读:可能。

  • repeatable read,可重重复读。 脏读:不可能。 不可重复读:不可能。 幻读:可能。

  • serializable,可串行化。 脏读:不可能。 不可重复读:不可能。 幻读:不可能。

脏读:当前事务(A)中可以读到其他事务(B)未提交的数据(脏数据)。

不可重复读:在事务A中先后两次读取同一个数据,两次读取的结果不一样,这种现象称为不可重复读。

幻读:在事务A中按照某个条件先后两次查询数据库,两次查询结果的条数不同,这种现象称为幻读。

脏读与不可重复读的区别在于:前者读到的是其他事务未提交的数据,后者读到的是其他事务已提交的数据

不可重复读与幻读的区别可以通俗的理解为:前者是数据变了,后者是数据的行数变了

oracle默认的隔离级别是读已提交。 mysql默认的隔离级别是可重复读。