[toc]

算法面试 Python 相关知识点

什么是解释性语言,什么是编译性语言?

计算机不能直接理解高级语言,只能直接理解机器语言,所以必须要把高级语言翻译成机器语言,计算机才能执行高级语言编写的程序。

  • 解释性语言在运行程序的时候才会进行翻译。

  • 编译型语言写的程序在执行之前,需要一个专门的编译过程,把程序编译成机器语言(可执行文件)。

Python 程序运行过程?

Python 程序在解释器上执行分两个过程:

  • 编译:
    首先把程序的字节码保存为一个以.pyc 为扩展名的文件。作为一种启动速度的优化。下一次运行程序时,如果上没有修改过源码的话,Python 将会加载.pyc 文件并跳过编译这个步骤。

  • 执行:
    当程序编译成字节码后,发送到 Python 虚拟机上来执行。虚拟机是 Python 的运行引擎。是 Python 解释器的最后一步。

注:解释器即让其他程序运行起来的程序,是代码与机器的计算机硬件之间的软件逻辑层。Python 也是一个名为解释器的软件包。

Python 的作用域?

Python 中的作用域分 4 种情况:

  • L:local,局部作用域,即函数中定义的变量;
  • E:enclosing,嵌套的父级函数的局部作用域,即包含此函数的上级函数的局部作用域;
  • G:global,全局变量,就是模块级别定义的变量;
  • B:built-in,系统固定模块里面的变量,比如 int, bytearray 等。 搜索变量的优先级顺序依次是:作用域局部>外层作用域>当前模块中的全局>python 内置作用域,也就是 LEGB。
1
2
3
4
5
6
7
8
9
10
11
x = int(2.9)  # int buiLt-in
g_count = 0 # GLobaL
def outer():
o_count = 1 # EncLosing
def inner():
i_count = 2 # LocaL
print(o_count)
# print(i_count) 找不到
inner()
outer()
# print(o_count) # 找不到

Python 的数据结构?

Python 中的绝大部分数据结构可以被最终分解为三种类型:集合(Set),序列(Sequence),映射(Mapping)。

    1. 集合是独立于标量,序列和映射之外的特殊数据结构,它支持数学理论的各种集合的运算。它的存在使得用程序代码实现数学理论变得方便。
    1. 序列是 Python 中最为基础的内建类型。它分为七种类型:列表、字符串、元组、Unicode 字符串、字节数组、缓冲区和 xrange 对象。常用的是:列表(List)、字符串(String)、元组(Tuple)。
    1. 映射在 Python 的实现是数据结构字典(Dictionary)。作为第三种基本单位,映射的灵活使得它在多种场合中都有广泛的应用和良好的可拓展性。

Python 可变类型是列表、集合、字典,不可变有字符串、元组、数字。

进程和线程

  • 进程:一个运行的程序(代码)就是一个进程,没有运行的代码叫程序,进程是系统资源分配的最小单位,进程拥有自己独立的内存空间,所有进程间数据不共享,开销大。
  • 线程:CPU 调度执行的最小单位,也叫执行路径,不能独立存在,依赖进程存在,一个进程至少有一个线程,叫主线程,而多个线程共享内存(数据共享,共享全局变量),从而极大地提高了程序的运行效率。
  • 多线程
    • Python 在任意时刻,只有一个线程在解释器中运行。对 Python 虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。
    • 多线程共享主进程的资源,所以可能还会改变其中的变量,这个需加上线程锁,每次执行完一个线程再执行下一个线程。
    • 一个 CPU 在同一个时刻只能执行一个线程,但是当遇到 IO 操作或者运行一定的代码量的时候就会释放全局解释器锁,执行另外一个线程。
  • 互斥锁和死锁
    • 互斥锁:即确保某段关键代码的数据只能又一个线程从头到尾完整执行,保证了这段代码数据的安全性,但是这样就会导致死锁。
    • 死锁:多个子线程在等待对方解除占用状态,但是都不先解锁,互相等待,这就是死锁。

Lambda

lambda 函数是一个可以接收任意多个参数(包括可选参数)并且返回单个表达式值的函数。

  • 1.lambda 函数比较轻便,即用即仍,很适合需要完成一项功能,但是此功能只在此一处使用,连名字都很随意的情况下。
  • 2.匿名函数,一般用来给 filter,map 这样的函数式编程服务。
  • 3.作为回调函数,传递给某些应用,比如消息处理。

浅拷贝和深拷贝

  • 浅拷贝(copy):创建新对象,其内容是原对象的引用。拷贝父对象,不会拷贝对象的内部的子对象。
  • 深拷贝(deepcopy): copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象。深拷贝出来的对象是一个全新的对象,不再与原来的对象有任何关联。

解析:

  • 变量:是一个系统表的元素,拥有指向对象的连接空间
  • 对象:被分配的一块内存,存储其所代表的值
  • 引用:是自动形成的从变量到对象的指针
  • 类型:属于对象,而非变量
  • 不可变对象:一旦创建就不可修改的对象,包括字符串、元组、数值类型,不可变类型,不管是深拷贝还是浅拷贝,地址值和拷贝后的值都是一样的。(该对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。)
  • 可变对象:可以修改的对象,包括列表、字典、集合(该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的地址,通俗点说就是原地改变。)

当写 a = ‘python’,Python 解释器干的事情:

1
2
3
1.创建变量a
2.创建一个对象(分配一块内存),来存储值 ‘python’
3.将变量与对象,通过指针连接起来,从变量到对象的连接称之为引用(变量引用对象)
    1. 赋值:只是复制了新对象的引用,不会开辟新的内存空间。并不会产生一个独立的对象单独存在,只是将原有的数据块打上一个新标签,所以当其中一个标签被改变的时候,数据块就会发生变化,另一个标签也会随之改变。
    1. 浅拷贝:创建新对象,其内容是原对象的引用。

    浅拷贝有三种形式: 切片操作,工厂函数,copy 模块中的 copy 函数。

    1
    2
    3
    4
    如: lst = [1,2,[3,4]]
    切片操作:lst1 = lst[:] 或者 lst1 = [each for each in lst]
    工厂函数:lst1 = list(lst)
    copy函数:lst1 = copy.copy(lst)

    浅拷贝之所以称为浅拷贝,是它仅仅只拷贝了一层,拷贝了最外围的对象本身,内部的元素都只是拷贝了一个引用而已,在 lst 中有一个嵌套的 list[3,4],如果修改了它,情况就不一样了。

    • 浅拷贝要分两种情况进行讨论:

      1. 当浅拷贝的值是不可变对象(字符串、元组、数值类型)时和“赋值”的情况一样,对象的 id 值(id()函数用于获取对象的内存地址)与浅拷贝原来的值相同。
         - 2. 当浅拷贝的值是可变对象(列表、字典、集合)时会产生一个“不是那么独立的对象”存在。有两种情况:第一种情况:复制的对象中无复杂子对象[1,2,3,4,5,6],原来值的改变并不会影响浅复制的值,同时浅复制的值改变也并不会影响原来的值。原来值的 id 值与浅复制原来的值不同。第二种情况:复制的对象中有复杂子对象(例如列表中的一个子元素是一个列表[1,2,3,[4,5],6]),如果不改变其中复杂子对象,浅复制的值改变并不会影响原来的值。 但是改变原来的值中的复杂子对象的值会影响浅复制的值。
    1. 深拷贝:和浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。深拷贝出来的对象是一个全新的对象,不再与原来的对象有任何关联。所以改变原有被复制对象不会对已经复制出来的新对象产生影响。

Python 多线程是否能用多个 CPU,为什么?

Python 的多线程不能利用多核 CPU。
因为 Python 解释器使用了 GIL(Global Interpreter Lock),在任意时刻中只允许单个 Python 线程运行。无论系统有多少个 CPU 核心,Python 程序都只能在一个 CPU 上运行 。
注:GIL 的功能是:在 CPython 解释器中执行的每一个 Python 线程,都会先锁住自己,以阻止别的线程执行。

Python 垃圾回收机制

Python 的垃圾回收机制是以:引用计数器为主,标记清除和分代回收为辅。

    1. 引用计数:每个对象内部都维护了一个值,该值记录这此对象被引用的次数,如果次数为 0,则 Python 垃圾回收机制会自动清除此对象。
    1. 标记-清除(Mark—Sweep):被分配对象的计数值与被释放对象的计数值之间的差异累计超过某个阈值,则 Python 的收集机制就启动
    • 标记阶段,遍历所有的对象,如果是可达的(reachable),也就是还有对象引用它,那么就标记该对象为可达。
    • 清除阶段,再次遍历对象,如果发现某个对象没有标记为可达,则就将其回收。
    1. “分代回收”(Generational Collection)。当代码中主动执行 gc.collect() 命令时,Python 解释器就会进行垃圾回收。

总体来说,在 Python 中,主要通过引用计数进行垃圾回收;通过 “标记-清除” 解决容器对象可能产生的循环引用问题;通过 “分代回收” 以空间换时间的方法提高垃圾回收效率。

Python 生成器

生成器是一种可以简单有效的创建迭代器的工具。像常规函数一样撰写,但是在需要返回数据时使用 yield 语句。每当对它调用 next()函数,生成器从它上次停止的地方重新开始(它会记住所有的数据值和上次执行的语句)。

Python 迭代器和生成器的区别

  • 迭代器有两个方法 next 方法和 iter 方法,iter 方法获取对象的迭代器,next 方法返回下一个迭代器。
  • 生成器:使用了 yield 的函数被称为生成器(generator),在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。调用一个生成器函数,返回的是一个迭代器对象。

区别:

  • 1.语法上:生成器是通过函数的形式中调用 yield 或()的形式创建的;迭代器可以通过 iter() 内置函数创建
  • 2.用法上:生成器在调用 next()函数或 for 循环中,所有过程被执行,且返回值;迭代器在调用 next()函数或 for 循环中,所有值被返回,没有其他过程或说动作。

总结:

  • 迭代器:在循环遍历自定义容器对象时,会使用 python 内置函数 iter()调用遍历对象的_iter_(self)获得一个迭代器,之后再循环对这个迭代器使用 next()调用迭代器对象的_next_(self) 。
  • 生成器:只能遍历一次,是一类特殊的迭代器。生成器能做到迭代器能做的所有事,而且因为自动创建了 iter()和 next()方法,生成器显得特别简洁,而且生成器也是高效的,使用生成器表达式取代列表解析可以同时节省内存。除了创建和保存程序状态的自动方法,当发生器终结时,还会自动抛出 StopIteration 异常。

什么是闭包

在一个内部函数中,对外部作用域的变量进行引用,(并且一般外部函数的返回值为内部函数),那么内部函数就被认为是闭包。

1
2
3
4
5
6
7
8
def outer(x):
def inner(y):
return x + y
return inner

print(outer(6)(5))
-----------------------------
>>>11

如代码所示,在 outer 函数内,又定义了一个 inner 函数,并且 inner 函数又引用了外部函数 outer 的变量 x,这就是一个闭包了。在输出时,outer(6)(5),第一个括号传进去的值返回 inner 函数,其实就是返回 6 + y,所以再传第二个参数进去,就可以得到返回值,6 + 5。

Python 装饰器

装饰器本质上是一个 Python 函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能。
装饰器的返回值也是一个函数对象,它经常用于有切面需求的场景,比如:插入日志 性能测试 事务处理 缓存 权限校验等场景 装饰器是解决这类问题的绝佳设计。
有了装饰器,就可以抽离出大量与函数功能本身无关的雷同代码并继续重用,概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

装饰器

函数 use_logging 就是装饰器,它把执行真正业务方法的 func 包裹在函数里面,看起来像 bar 被 use_logging 装饰了。@符号是装饰器的语法糖,在定义函数的时候使用,避免再一次赋值操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def use_logging(func):

def wrapper(*args, **kwargs):
logging.warn("%s is running" % func.__name__)
return func(*args, **kwargs)
return wrapper

@use_logging
def bar():
print('i am bar')

@use_logging
def foo():
print('i am foo')

bar()
------------------------
>>>bar is running
>>>i am bar

带参数的装饰器

装饰器的语法允许我们在调用时,提供其它参数,比如@decorator(a)。这样,就为装饰器的编写和使用提供了更大的灵活性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def use_logging(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == "warn":
logging.warn("%s is running" % func.__name__)
return func(*args)
return wrapper
return decorator

@use_logging(level="warn")
def foo(name='foo'):
print("i am %s" % name)

foo()
------------------------
>>>foo is running
>>>i am foo

类装饰器

相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器还可以依靠类内部的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Foo(object):
def __init__(self, func):
self._func = func

def __call__(self):
print ('class decorator runing')
self._func()
print ('class decorator ending')

@Foo
def bar():
print ('bar')

bar()
------------------------
>>>class decorator runing
>>>bar
>>>class decorator ending

Python 中 yield 和 return 的区别

  • 共同点:returnyield都用来返回值;在一次性地返回所有值场景中returnyield的作用是一样的。
  • 不同点:如果要返回的数据是通过for等循环生成的迭代器类型数据(如列表、元组),return只能在循环外部一次性地返回,yeild则可以在循环内部逐个元素返回(yield 函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行)

Python 中 set 的底层实现

散列表/哈希表。set 只是默认键和值是相同的。
注:散列表是根据关键字而直接进行访问值的数据结构。也就是说散列表建立了关键字和存储地址之间的一种直接映射关系。

Python 中字典与 set 区别?

  • 1.字典是一系列无序的键值对的组合;集合 set()里的元素默认键值是一样的,是单一的一个元素。
  • 2.从 python3.6 后,字典有序;集合无序。
  • 3.字典键不能重复;集合 set()元素不能重复。

Python 中__init__和__new__和__call__的区别?

  • __init__是初始化方法
  • __new__实例化对象
  • __call__允许一个类的实例像函数一样被调用。实质上说,这意味着 x() 与 x.call() 是相同。

构造方法 = 创建对象 + 初始化对象 = new + init
__new__方法是在实例创建之前被调用,是一个静态方法,主要的功能就是创建一个类的实例并返回
__init__方法是在实例创建之后被调用,主要的功能是设置实例的一些属性初始值

实际测试:__new____init__之前被调用,__new__的返回值(实例)将传递给__init__方法的第一个参数(self),然后__init__给这个实例(self)设置一些参数。

Python 中的内存管理

什么是内存管理器(what)

Python 作为一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言,与大多数编程语言不同,Python 中的变量无需事先申明,变量无需指定类型,程序员无需关心内存管理,Python 解释器给你自动回收。开发人员不用过多的关心内存管理机制,这一切全部由 Python 内存管理器承担了复杂的内存管理工作。

内存不外乎创建和销毁两部分,本文将围绕 python 的内存池和垃圾回收两部分进行分析。

Python 内存池

为什么要引入内存池(why)

当创建大量消耗小内存的对象时,频繁调用 new/malloc 会导致大量的内存碎片,致使效率降低。内存池的作用就是预先在内存中申请一定数量的,大小相等的内存块留作备用,当有新的内存需求时,就先从内存池中分配内存给这个需求,不够之后再申请新的内存。这样做最显著的优势就是能够减少内存碎片,提升效率。

Python 中的内存管理机制为Pymalloc

内存池是如何工作的(how)
首先,我们看一张 CPython(python 解释器)的内存架构图:

2022-05-16-21-36-24.png

  • Python 的对象管理主要位于 Level+1~Level+3 层。
  • Level+3 层:对于 Python 内置的对象(比如 int,dict 等)都有独立的私有内存池,对象之间的内存池不共享,即 int 释放的内存,不会被分配给 float 使用。
  • Level+2 层:当申请的内存大小 < 256KB 时,内存分配主要由 Python 对象分配器(Python’s object allocator)实施。
  • Level+1 层:当申请的内存大小 > 256KB 时,由 Python 原生的内存分配器进行分配,本质上是调用 C 标准库中的 malloc/realloc 等函数

内存释放

关于释放内存方面,当一个对象的引用计数变为 0 时,Python 就会调用它的析构函数。调用析构函数并不意味着最终一定会调用 free 来释放内存空间,如果真是这样的话,那频繁地申请、释放内存空间会使 Python 的执行效率大打折扣。因此在析构时也采用了内存池机制,从内存池申请到的内存会被归还到内存池中,以避免频繁地申请和释放动作。

垃圾回收机制

Python 的垃圾回收机制采用引用计数机制为主,标记-清除分代回收机制为辅的策略。其中,标记-清除机制用来解决计数引用带来的循环引用而无法释放内存的问题,分代回收机制是为提升垃圾回收的效率。

引用计数

Python 通过引用计数来保存内存中的变量追踪,即记录该对象被其他使用的对象引用的次数。

Python 中有个内部跟踪变量叫做引用计数器,每个变量有多少个引用,简称引用计数。当某个对象的引用计数为 0 时,就列入了垃圾回收队列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> a = [1, 2]
>>> import sys
>>> sys.getrefcount(a) # 获取对象a的引用次数
2
>>> b = a
>>> sys.getrefcount(a)
3
>>> del b # 删除b的引用
>>> sys.getrefcount(a)
2
>>> c = list()
>>> c.append(a) # 加入到容器中
>>> sys.getrefcount(a)
3
>>> del c # 删除容器,引用-1
>>> sys.getrefcount(a)
2
>>> b = a
>>> sys.getrefcount(a)
3
>>> a = [3, 4] # 重新赋值
>>> sys.getrefcount(a)
2
1
注意:当把a作为参数传递给getrefcount时,会产生一个临时的引用,因此得出来的结果比真实情况 + 1

引用计数增加的情况

  • 一个对象被分配给一个新的名字(例如:a = [1, 2]
  • 将其放入一个容器中(如列表、元组或字典)(例如:c.append(a)

引用计数减少的情况

  • 使用del语句对对象别名显式的销毁(例如:del b)
  • 对象所在的容器被销毁或从容器中删除对象(例如:del c
  • 引用超出作用域或被重新赋值(例如:a = [3,4]

引用计数能够解决大多数垃圾回收的问题,但是遇到两个对象相互引用的情况,del 语句可以减少引用次数,但是引用计数不会归 0,对象也就不会被销毁,从而造成了内存泄漏问题。针对该情况,Python 引入了标记-清除机制。

标记-清除(Mark-and-Sweep)

标记-清除用来解决引用计数机制产生的循环引用,进而导致内存泄漏的问题。循环引用只有在容器对象才会产生,比如字典,元组,列表等。

顾名思义,该机制在进行垃圾回收时分成了两步,分别是:

  • 标记阶段,遍历所有的对象,如果是可达的(reachable),也就是还有对象引用它,那么就标记该对象为可达。
  • 清除阶段,再次遍历对象,如果发现某个对象没有标记为可达(即为 Unreachable),则就将其回收。

当一个对象被创建时,它的标记位被设置为 0(False)
在标记阶段,从根结点出发,也就是可以直接访问的局部变量,采用图的遍历算法,比如 DFS,将所有可达对象的标记位设置为 1(True)
在清除阶段,线性扫描堆内存,将不可达对象直接释放,将可达对象的标记位重新设置为 0,为下一次标记清除做准备。
标记清除的优点是可以处理循环引用,缺点是算法会暂停程序的执行。为了优化垃圾回收的性能,可以使用分代回收算法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> a = [1, 2]
>>> b = [3, 4]
>>> sys.getrefcount(a)
2
>>> sys.getrefcount(b)
2
>>> a.append(b)
* * *
>>> sys.getrefcount(b)
3
>>> b.append(a)
>>> sys.getrefcount(a)
3
>>> del a
>>> del b
  • a引用bb引用a, 此时两个对象各自被引用了 2 次(去除 getrefcout()的临时引用)
  • 执行del之后,对象a, b的引用次数都减 1,此时各自的引用计数器都为 1,陷入循环引用。
  • 标记:找到其中的一端a,因为它有一个对b的引用,则将b的引用计数减 1。
  • 标记:再沿着引用到bb有一个a的引用,将a的引用计数减 1,此时对象ab的引用次数全部为 0,被标记为不可达(Unreachable)。
  • 清除: 被标记为不可达的对象就是真正需要被释放的对象

上面描述的垃圾回收的阶段,会暂停整个应用程序,等待标记清除结束后才会恢复应用程序的运行。为了减少应用程序暂停的时间,Python 通过**“分代回收”(Generational Collection)**以空间换时间的方法提高垃圾回收效率。

分代回收

分代回收是基于这样的一个统计事实,对于程序,存在一定比例的内存块的生存周期比较短;而剩下的内存块,生存周期会比较长,甚至会从程序开始一直持续到程序结束。生存期较短对象的比例通常在 80%~ 90%之间。 因此,简单地认为:对象存在时间越长,越可能不是垃圾,应该越少去收集。这样在执行标记-清除算法时可以有效减小遍历的对象数,从而提高垃圾回收的速度,是一种以空间换时间的方法策略

Python 将所有的对象分为年轻代(第 0 代)、中年代(第 1 代)、老年代(第 2 代)三代。所有的新建对象默认是 第 0 代对象。当在第 0 代的 gc 扫描中存活下来的对象将被移至第 1 代,在第 1 代的 gc 扫描中存活下来的对象将被移至第 2 代。

1
gc扫描次数(第0代>第1代>第2代)

当某一代中被分配的对象与被释放的对象之差达到某一阈值时,就会触发当前一代的gc 扫描。当某一代被扫描时,比它年轻的一代也会被扫描,因此,第 2 代的 gc 扫描发生时,第 0,1 代的 gc 扫描也会发生,即为全代扫描

1
2
3
4
5
6
>>> import gc
>>> gc.get_threshold() # 分代回收机制的参数阈值设置
(700, 10, 10)
# 700=新分配的对象数量-释放的对象数量,第0代gc扫描被触发
# 第一个10:第0代gc扫描发生10次,则第1代的gc扫描被触发
# 第二个10:第1代的gc扫描发生10次,则第2代的gc扫描被触发

总体而言,Python 通过内存池来减少内存碎片化,提高执行效率。主要通过引用计数来完成垃圾回收,通过标记-清除解决容器对象循环引用造成的问题,通过分代回收提高垃圾回收的效率。

类方法和静态方法的区别

Python 类方法和实例方法相似,它最少也要包含一个参数,只不过类方法中通常将其命名为 cls,Python 会自动将类本身绑定给 cls 参数(注意,绑定的不是类对象)。也就是说,在调用类方法时,无需显式为 cls 参数传参。

静态方法没有类似 self、cls 这样的特殊参数,因此 Python 解释器不会对它包含的参数做任何类或对象的绑定。也正因为如此,类的静态方法中无法调用任何类属性和类方法。

实例方法只能被实例对象调用(Python3 中,如果类调用实例方法,需要显示的传 self, 也就是实例对象自己),静态方法(由@staticmethod 装饰的方法)、类方法(由@classmethod 装饰的方法),可以被类或类的实例对象调用。

  • 实例方法,第一个参数必须要默认传实例对象,一般习惯用 self。
  • 静态方法,参数没有要求。
  • 类方法,第一个参数必须要默认传类,一般习惯用 cls。

Python 的错误和异常处理

Python 中会发生两种类型的错误。

    1. 语法错误:如果未遵循正确的语言语法,则会引发语法错误。
    1. 逻辑错误(异常):在运行时中,通过语法测试后发生错误的情况称为异常或逻辑类型。

异常处理
通过使用 tryexcept 来处理异常状况。一般来说会把通常的语句放在 try 代码块中,将错误处理器代码放置在 except 代码块中。

  • try…else 语句:else 语句是在 try 语句中的代码没有任何异常的情况下,再执行 else 语句下的代码。
  • try…finally 语句:finally 语句就是不管上面有没有异常,都要执行 finally 语句下的代码,通常是做一些必须要释放的资源的代码,最典型的就是文件操作和数据库操作。
  • 抛出异常 raise:raise 语句是抛出一个指定的异常。

Python 中的 is 和==的区别

  • is比较的是两个对象的 id 值是否相等,也就是比较两个对象是否为同一个实例对象,是否指向同一个内存地址。
  • ==比较的是两个对象的内容是否相等,默认会调用对象的eq()方法。

GBK 和 UTF-8 的区别

  • GBK 是在国家标准 GB2312 基础上扩容后兼容 GB2312 的标准。GBK 编码专门用来解决中文编码的,是双字节的。不论中英文都是双字节的。
  • UTF-8 编码是用以解决国际上字符的一种多字节编码,它对英文使用 8 位(即一个字节),中文使用 24 位(三个字节)来编码。对于英文字符较多的论坛则用 UTF-8 节省空间。另外,如果是外国人访问 GBK 网页,需要下载中文语言包支持。访问 UTF-8 编码的网页则不出现这问题。可以直接访问。
  • GBK 包含全部中文字符;UTF-8 则包含全世界所有国家需要用到的字符。

Python 遍历字典的方法

1
2
3
4
5
6
7
8
9
dic1 = {'date':'2018.11.2','name':'carlber','work':"遍历",'number':3}
for i in dic1:
print(i)
for key in dic1.keys(): #遍历字典中的键
print(key)
for value in dic1.values(): #遍历字典中的值
print(value)
for item in dic1.items(): #遍历字典中的元素
print(item)

__init__.py文件的作用以及意义

  • 这个文件定义了包的属性和方法,它可以什么也不定义;可以只是一个空文件,但是必须存在。
  • 如果 __init__.py 不存在,这个目录就仅仅是一个目录,而不是一个包,它就不能被导入或者包含其它的模块和嵌套包。
  • 或者可以这样理解。这样,当导入这个包的时候,__init__.py文件自动运行。帮导入了这么多个模块,就不需要将所有的import语句写在一个文件里了,也可以减少代码量。

函数调用参数的传递方式是值传递还是引用传递?

Python 的参数传递有:位置参数, 默认参数, 可变参数, 关键字参数.

函数的传值到底是值传递还是引用传递, 要分情况:

  • 不可变参数用值传递:像整数和字符串这样的不可变对象,是通过拷贝进行传递的,因为你无论如何都不可能在原处改变不可变对象.
  • 可变参数是引用传递:比如像列表, 字典这样的对象是通过引用传递, 和 C 语言里面的用指针传递数组很相似, 可变对象能在函数内部改变.

Python 的缺省参数

缺省参数指在调用函数的时候没有传入参数的情况下, 调用默认的参数, 在调用函数的同时赋值时, 所传入的参数会替代默认参数.

  • *args是不定长参数,它可以表示输入参数是不确定的,可以是任意多个。
  • **kwargs是关键字参数,赋值的时候是以键值对的方式,参数可以是任意多对在定义函数的时候
  • 不确定会有多少参数会传入时,就可以使用两个参数。

mapreduce函数

  • map()是将传入的函数依次作用到序列的每个元素,每个元素都是独自被函数“作用”一次 。
  • reduce()是将传入的函数作用在序列的第一个元素得到结果后,把这个结果继续与下一个元素作用(累

积计算)。

1
2
3
4
map(lambda x: x * x, [1, 2, 3, 4])   # 使用 lambda
# [1, 4, 9, 16]
reduce(lambda x, y: x * y, [1, 2, 3, 4]) # 相当于 ((1 * 2) * 3) * 4
# 24

hasattr(), getattr(), setattr()函数使用详解?

  1. hasattr(object, name)函数:

判断一个对象里面是否有 name 属性或者 name 方法,返回 bool 值,有 name 属性(方法)返回 True,否则返回 False。

1
2
3
4
5
6
7
8
class function_demo(object):
name = 'demo'
def run(self):
return "hello function"
functiondemo = function_demo()
res = hasattr(functiondemo, "name") # 判断对象是否有name属性,True
res = hasattr(functiondemo, "run") # 判断对象是否有run方法,True
res = hasattr(functiondemo, "age") # 判断对象是否有age属性,False
  1. getattr(object, name[,default])函数:

获取对象 object 的属性或者方法,如果存在则打印出来,如果不存在,打印默认值,默认值可选。
注意:如果返回的是对象的方法,则打印结果是:方法的内存地址,如果需要运行这个方法,可以在后面添加括号()

1
2
3
4
5
6
7
8
9
class function_demo(object):
name = 'demo'
def run(self):
return "hello function"
functiondemo = function_demo()
getattr(functiondemo, "name")# 获取name属性,存在就打印出来 --- demo
getattr(functiondemo, "run") # 获取run 方法,存在打印出方法的内存地址
getattr(functiondemo, "age") # 获取不存在的属性,报错
getattr(functiondemo, "age", 18) # 获取不存在的属性,返回一个默认值
  1. setattr(object, name, values)函数:

给对象的属性赋值,若属性不存在,先创建再赋值

1
2
3
4
5
6
7
8
9
10
class function_demo(object):
name = "demo"
def run(self):
return "hello function"
functiondemo = function_demo()
res = hasattr(functiondemo, "age") # 判断age属性是否存在,False
print(res)
setattr(functiondemo, "age", 18) # 对age属性进行赋值,无返回值
res = hasattr(functiondemo, "age") # 再次判断属性是否存在,True
print(res)
  1. 综合使用
1
2
3
4
5
6
7
8
9
10
11
12
13
class function_demo(object):
name = "demo"
def run(self):
return "hello function"
functiondemo = function_demo()
res = hasattr(functiondemo, "addr") # 先判断是否存在
if res:
addr = getattr(functiondemo, "addr")
print(addr)
else:
addr = getattr(functiondemo, "addr", setattr(functiondemo, "addr", "北京首
都"))
print(addr)

什么是断言(assert)?

assert 断言——声明其布尔值必须为真判定,发生异常则为假。

Python 是如何进行类型转换的?

内建函数(build-in)封装了各种转换函数,可以使用目标类型关键字强制类型转换
进制之间的转换可以用int(str, base='n')将特定进制的字符串转换为十进制,再用相应的进制转换函数将十进制转换为目标进制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# hex to decimal
print(int('0xf', 16)) # 15
# binary to decimal
print(int('10100111110', 2)) # 1342
# octonary to decimal
print(int('17', 8)) # 15

# decimal to hex
print(hex(1033)) # 0x409
# binary to hex
print(hex(int('101010', 2))) # 0x2a
# oct to hex
print(hex(int('17', 8))) # 0xf

# decimal to binary
print(bin(10)) # 0b1010
# hex to bin
print(bin(int('0xff', 16))) # 0b11111111
# oct to bin
print(bin(int('17',8))) # 0b1111

提高 Python 运行效率的方法?

  1. 使用生成器,因为可以节约大量内存

  2. 循环代码优化,避免过多重复代码的执行

  3. 核心模块用 Cython PyPy 等,提高效率

  4. 多进程, 多线程, 协程

  5. 多个 if elif 条件判断,可以把最有可能先发生的条件放到前面写,这样可以减少程序判断的次数,提高效率

Python 中any()all()方法

  • any():只要可迭代对象中有一个元素为真就为真
1
2
3
4
5
def any(iterable):
for element in iterable:
if element:
return True
return False
  • all():可迭代对象中所有的判断项返回都是真,结果才为真
1
2
3
4
5
def all(iterable):
for element in iterable:
if not element:
return False
return True

Python 为假的变量

0,空字符串,空列表,空字典,空元组,None,False