【编程之路】Python编程进阶

2023-11-03

Python编程进阶

不使用中间变量,交换两个变量a和b的值。

a, b = b, a

需要注意,a, b = b, a 这种做法其实并不是元组解包,虽然很多人都这样认为。Python 字节码指令中有 ROT_TWO 指令来支持这个操作,类似的还有 ROT_THREE,对于 3 个以上的元素,如 a, b, c, d = b, c, d, a,才会用到创建元组和元组解包。想知道你的代码对应的字节码指令,可以使用 Python 标准库中 dis 模块的 dis 函数来反汇编你的Python代码。

Lambda 函数是什么,举例说明的它的应用场景。

Lambda 函数通常用在高阶函数中,主要的作用是通过 向函数传入函数让函数返回函数 最终实现代码的解耦合。

Lambda 函数也叫 匿名函数,它是功能简单用一行代码就能实现的小型函数。Python 中的 Lambda 函数只能写一个表达式,这个表达式的执行结果就是函数的返回值,不用写 return 关键字。Lambda 函数因为没有名字,所以也不会跟其他函数发生命名冲突的问题。

Lambda 函数其实最为主要的用途是把一个函数传入另一个高阶函数(如 Python 内置的 filtermap 等)中来为函数做解耦合,增强函数的灵活性和通用性。

下面的例子通过使用 filtermap 函数,实现了 从列表中筛选出奇数并求平方构成新列表 的操作,因为用到了高阶函数,过滤和映射数据的规则都是函数的调用者通过另外一个函数传入的,因此这 filtermap 函数没有跟特定的过滤和映射数据的规则耦合在一起。

items = [12, 5, 7, 10, 8, 19]
items = list(map(lambda x: x ** 2, filter(lambda x: x % 2, items)))
print(items)  # [25, 49, 361]

用列表的生成式来实现上面的代码会更加简单明了,代码如下所示。

items = [12, 5, 7, 10, 8, 19]
items = [x ** 2 for x in items if x % 2]
print(items)    # [25, 49, 361]

说说 Python 中的浅拷贝和深拷贝。

回答这个题目的要点不仅仅是能够说出浅拷贝和深拷贝的区别,深拷贝的时候可能遇到的两大问题,还要说出 Python 标准库对浅拷贝和深拷贝的支持,然后可以说说列表、字典如何实现拷贝操作以及如何通过序列化和反序列的方式实现深拷贝,最后还可以提到设计模式中的原型模式以及它在项目中的应用。

在这里插入图片描述

浅拷贝通常只复制对象本身,而深拷贝不仅会复制对象,还会递归的复制对象所关联的对象。

浅拷贝 只是单纯地进行指针的复制,原变量与新变量指向同一片存储空间,从而导致:修改原变量 / 新变量的值的同时,新变量 / 原变量的值也是随之同时改变。

深拷贝 是另起一片存储空间,将原存储空间的内容复制到新的存储空间中,故:修改原变量 / 新变量的值时,新变量 / 原变量的值不会发生改变。

深拷贝可能会遇到两个问题:

  • 一是一个对象如果直接或间接的引用了自身,会导致无休止的递归拷贝;
  • 二是深拷贝可能对原本设计为多个对象共享的数据也进行拷贝。

Python 通过 copy 模块中的 copydeepcopy 函数来实现浅拷贝和深拷贝操作,其中 deepcopy 可以通过 memo 字典来保存已经拷贝过的对象,从而避免刚才所说的自引用递归问题;此外,可以通过 copyreg 模块的 pickle 函数来定制指定类型对象的拷贝行为。

deepcopy 函数的本质其实就是对象的一次序列化和一次返回序列化,面试题中还考过用自定义函数实现对象的深拷贝操作,显然我们可以使用 pickle 模块的 dumpsloads 来做到,代码如下所示。

import pickle
my_deep_copy = lambda obj: pickle.loads(pickle.dumps(obj))

列表的切片操作 [:] 相当于实现了列表对象的浅拷贝,而字典的 copy 方法可以实现字典对象的浅拷贝。对象拷贝其实是更为快捷的创建对象的方式。

在 Python 中,通过构造器创建对象属于两阶段构造,首先是 分配内存空间,然后是 初始化。在创建对象时,我们也可以基于 “原型” 对象来创建新对象,通过对原型对象的拷贝(复制内存)就完成了对象的创建和初始化,这种做法更加高效,这也就是设计模式中的 原型模式。在Python中,我们可以通过元类的方式来实现原型模式,代码如下所示。

import copy

class PrototypeMeta(type):
    """实现原型模式的元类"""

    def __init__(cls, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # 为对象绑定clone方法来实现对象拷贝
        cls.clone = lambda self, is_deep=True: \
            copy.deepcopy(self) if is_deep else copy.copy(self)

class Person(metaclass=PrototypeMeta):
    pass

p1 = Person()
p2 = p1.clone()                 # 深拷贝
p3 = p1.clone(is_deep=False)    # 浅拷贝

Python 是如何实现内存管理的?

Python 提供了 自动化的内存管理,也就是说内存空间的分配与释放都是由 Python 解释器在运行时自动进行的,自动管理内存功能极大的减轻程序员的工作负担,也能够帮助程序员在一定程度上解决内存泄露的问题。以 CPython 解释器为例,它的内存管理有三个关键点:引用计数、标记清理、分代收集。

  • 引用计数:对于 CPython 解释器来说,Python 中的每一个对象其实就是 PyObject 结构体,它的内部有一个名为 ob_refcnt 的引用计数器成员变量。程序在运行的过程中 ob_refcnt 的值会被更新并借此来反映引用有多少个变量引用到该对象。当对象的引用计数值为 0 时,它的内存就会被释放掉。
  • 标记清理:CPython 使用了 “标记-清理”(Mark and Sweep)算法解决容器类型可能产生的循环引用问题。该算法在垃圾回收时分为两个阶段:标记阶段,遍历所有的对象,如果对象是可达的(被其他对象引用),那么就标记该对象为可达;清除阶段,再次遍历对象,如果发现某个对象没有标记为可达,则就将其回收。
  • 分代回收:在循环引用对象的回收中,整个应用程序会被暂停,为了减少应用程序暂停的时间,Python 通过分代回收(空间换时间)的方法提高垃圾回收效率。分代回收的基本思想是:对象存在的时间越长,是垃圾的可能性就越小,应该尽量不对这样的对象进行垃圾回收

说一下你对 Python 中迭代器和生成器的理解。

迭代器是实现了迭代器协议的对象。跟其他编程语言不同,Python 中没有用于定义协议或表示约定的关键字,像 interfaceprotocol 这些单词并不在 Python 语言的关键字列表中。Python 语言通过魔法方法来表示约定,也就是我们所说的协议,而 __next____iter__ 这两个魔法方法就代表了 迭代器协议。可以通过 for-in 循环从迭代器对象中取出值,也可以使用 next 函数取出迭代器对象中的下一个值。

生成器是迭代器的语法升级版本,可以用更为简单的代码来实现一个迭代器。生成器本质是一个函数,它记住了上一次返回时在函数体中的位置,对生成器函数的第二次调用,跳转到函数上一次挂起的位置,而且记录了程序执行的上下文,生成器不仅仅记住了它的数据状态,也记住了它执行的位置。

迭代器是一种支持 next() 操作的对象,它包含了一组元素,执行 next() 操作时,返回其中一个元素,当所有元素都返回时,再执行会报异常。

迭代器是一个更抽象的概念,任何对象,如果它的类有 next 方法和 iter 方法返回自己本身。对于 stringlistdicttuple 等这类容器对象,使用 for 循环遍历是很方便的。在后台 for 语句对容器对象调用 iter() 函数,iter() 是 python 的内置函数。iter() 会返回一个定义了 next() 方法的迭代器对象,它在容器中逐个访问容器内元素,next() 也是 python 的内置函数。在没有后续元素时,next() 会抛出一个 StopIteration 异常

生成器(Generator)是创建迭代器的简单而强大的工具。它们写起来就像是正规的函数,只是在需要返回数据的时候使用 yield 语句。每次 next() 被调用时,生成器会返回它脱离的位置(它记忆语句最后一次执行的位置和所有的数据值)。

区别:生成器能做到迭代器能做的所有事,而且因为自动创建了 __iter__()next() 方法,生成器显得特别简洁,而且生成器也是高效的,使用生成器表达式取代列表解析可以同时节省内存。除了创建和保存程序状态的自动方法,当发生器终结时,还会自动抛出 StopIteration 异常。

面试中经常让写生成斐波那契数列的迭代器,大家可以参考下面的代码。

class Fib(object):

    def __init__(self, num):
        self.num = num
        self.a, self.b = 0, 1
        self.idx = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.idx < self.num:
            self.a, self.b = self.b, self.a + self.b
            self.idx += 1
            return self.a
        raise StopIteration()

如果用生成器的语法来改写上面的代码,代码会简单优雅很多。

def fib(num):
    a, b = 0, 1
    for _ in range(num):
        a, b = b, a + b
        yield a

正则表达式的 match 方法和 search 方法有什么区别?

正则表达式是字符串处理的重要工具,所以也是面试中经常考察的知识点。

在 Python 中,使用正则表达式有两种方式,一种是直接调用 re 模块中的函数,传入正则表达式和需要处理的字符串;一种是先通过 re 模块的 compile 函数创建正则表达式对象,然后再通过对象调用方法并传入需要处理的字符串。

如果一个正则表达式被频繁的使用,我们推荐用 re.compile 函数创建正则表达式对象,这样会减少频繁编译同一个正则表达式所造成的开销。

match 方法是从字符串的起始位置进行正则表达式匹配,返回 Match对象None。如果匹配的字符不是在开头处,那么它将会报错。
search 方法会扫描整个字符串来找寻匹配的模式,同样也是返回 Match对象None 。会匹配一整个字符串后得出结果

import re

result1 = re.match('li', 'liadadafbba').group()
result2 = re.match('li', 'addadlidadaf')
print(result1, result2) # li None

result1 = re.search('li', 'liadadafbba').group()
result2 = re.search('li', 'addadlidadaf').group()
print(result1, result2) # li li

Python 中为什么没有函数重载?

C++、Java、C# 等诸多编程语言都支持函数重载,所谓函数重载指的是 在同一个作用域中有多个同名函数,它们拥有不同的参数列表(参数个数不同或参数类型不同或二者皆不同),可以相互区分

重载也是一种多态性,因为通常是在编译时通过参数的个数和类型来确定到底调用哪个重载函数,所以也被称为 编译时多态性 或者叫 前绑定。这个问题的潜台词其实是问面试者是否有其他编程语言的经验,是否理解 Python 是 动态类型语言,是否知道 Python 中函数的可变参数、关键字参数这些概念。

首先 Python 是解释型语言,函数重载现象通常出现在编译型语言中。其次 Python 是动态类型语言, 函数的参数没有类型约束,也就 无法根据参数类型来区分重载。再者 Python 中函数的参数可以有默认值,可以使用可变参数和关键字参数,因此 即便没有函数重载,也要可以让一个函数根据调用者传入的参数产生不同的行为

对于 Python 中位置参数、关键字参数、默认参数、可变参数的理解。

  • 位置函数:调用函数时根据函数定义的参数位置来传递参数。
  • 关键字参数:用于函数调用,通过 “键-值” 形式加以指定。可以让函数更加清晰、容易使用,同时也清除了参数的顺序需求。
  • 默认参数:用于定义函数,为参数提供默认值,调用函数时可传可不传该默认参数的值(注意:所有位置参数必须出现在默认参数前,包括函数定义和调用)
  • 可变参数:定义函数时,有时候我们不确定调用的时候会传递多少个参数(不传参也可以)。此时,可用包裹(packing)位置参数,或者包裹关键字参数,来进行参数传递,会显得非常方便。

函数参数 *arg 和 **kwargs 分别代表什么?

Python 中,函数的参数分为 位置参数、关键字参数、可变参数、命名关键字参数

*args 代表可变参数,可以接收 0 个或任意多个参数,当不确定调用者会传入多少个位置参数时,就可以使用可变参数,它会将传入的参数打包成一个元组。
**kwargs 代表关键字参数,可以接收用参数名=参数值的方式传入的参数,传入的参数的会打包成一个字典。

定义函数时如果同时使用 *args**kwargs,那么函数可以接收任意参数。

*args:可变长度的 位置参数

在最后一个形参名前加 *,溢出的位置参数都会被接收,以元组的形式保存下来赋值给该形参。

def foo(x, y, z=1, *args):  # 在最后一个形参名 args 前加 * 号
    print(x)
    print(y)
    print(z)
    print(args)
    
foo(1,2,3,4,5,6,7)

在这里插入图片描述

**kwargs:可变长度的 关键字参数

在最后一个形参名前面加 **,调用函数时,溢出的关键字参数都会被接收,以字典的形式保存下来赋值给该形参。

def foo(x, **kwargs):  # 在最后一个参数 kwargs 前加 **
   print(x)
   print(kwargs)

# 溢出的关键字实参 y=2,z=3 都被 ** 接收,以字典的形式保存下来,赋值给 kwargs
foo(x=1, y=2, z=3)  

在这里插入图片描述

说说你用过Python标准库中的哪些模块。

模块 解释
sys 跟 Python 解释器相关的变量和函数,例如:sys.version、sys.exit()
os 和操作系统相关的功能,例如:os.listdir()、os.remove()
re 和正则表达式相关的功能,例如:re.compile()、re.search()
math 和数学运算相关的功能,例如:math.pi、math.e、math.cos
logging 和日志系统相关的类和函数,例如:logging.Logger、logging.Handler
json / pickle 实现对象序列化和反序列的模块,例如:json.loads、json.dumps
hashlib 封装了多种哈希摘要算法的模块,例如:hashlib.md5、hashlib.sha1
urllib 包含了和 URL 相关的子模块,例如:urllib.request、urllib.parse
itertools 提供各种迭代器的模块,例如:itertools.cycle、itertools.product
functools 函数相关工具模块,例如:functools.partial、functools.lru_cache
collections / heapq 封装了常用数据结构和算法的模块,例如:collections.deque
threading / multiprocessing 多线程/多进程相关类和函数的模块,例如:threading.Thread
concurrent.futures / asyncio 并发编程/异步编程相关的类和函数的模块,例如:ThreadPoolExecutor
base64 提供 BASE-64 编码相关函数的模块,例如:bas64.encode
csv 和读写 CSV 文件相关的模块,例如:csv.reader、csv.writer
profile / cProfile / pstats 和代码性能剖析相关的模块,例如:cProfile.run、pstats.Stats
unittest 和单元测试相关的模块,例如:unittest.TestCase

__init__ 和 __new__方法有什么区别?

Python 中 调用构造器创建对象 属于两阶段构造过程:

  • 首先执行 __new__ 方法获得保存对象所需的内存空间
  • 再通过 __init__ 执行对内存空间数据的填充(对象属性的初始化)。

__new__ 方法的返回值是创建好的 Python 对象(的引用),而 __init__ 方法的第一个参数就是这个对象(的引用),所以在 __init__ 中可以完成对对象的初始化操作。

__new__类方法,它的第一个参数是 __init__对象方法,它的第一个参数是 对象

说一下你知道的 Python 中的魔术方法。

方法 解释
__new__、__init__、__del__ 创建和销毁对象相关
__add__、__sub__、__mul__、__div__、__floordiv__、__mod__ 算术运算符相关
__eq__、__ne__、__lt__、__gt__、__le__、__ge__ 关系运算符相关
__pos__、__neg__、__invert__ 一元运算符相关
__lshift__、__rshift__、__and__、__or__、__xor__ 位运算相关
__enter__、__exit__ 上下文管理器协议
__iter__、__next__、__reversed__ 迭代器协议
__int__、__long__、__float__、__oct__、__hex__ 类型/进制转换相关
__str__、__repr__、__hash__、__dir__ 对象表述相关
__len__、__getitem__、__setitem__、__contains__、__missing__ 序列相关
__copy__、__deepcopy__ 对象拷贝相关
__call__、__setattr__、__getattr__、__delattr__ 其他魔术方法

什么是鸭子类型(duck typing)?

在 Python 语言中,有很多 bytes-like 对象(如:bytesbytearrayarray.arraymemoryview)、file-like 对象(如:StringIOBytesIOGzipFilesocket)、path-like 对象(如:strbytes)。

其中 file-like 对象都能支持 readwrite 操作,可以像文件一样读写,这就是所谓的 对象有鸭子的行为就可以判定为鸭子的判定方法。再比如 Python 中列表的 extend 方法,它需要的参数并不一定要是列表,只要是可迭代对象就没有问题。

说明:动态语言的鸭子类型使得设计模式的应用被大大简化。

说一下 Python 中变量的作用域。

Python 中有四种作用域,分别是

  • 局部作用域(Local):没有内部函数时,函数体为本地作用域。
  • 嵌套作用域(Embedded):包含内部函数时,函数体为函数嵌套作用域。
  • 全局作用域(Global):一般模块文件顶层声明的变量具有全局作用域,从外部来看,模块的全局变量就是一个模块对象的属性,仅限于单个模块文件中。
  • 内置作用域(Built-in):Python 运行时的环境为内置作用域,它包含了 Python 的各种预定义变量和函数。

搜索一个标识符时,会按照 LEGB 的顺序进行搜索,如果所有的作用域中都没有找到这个标识符,就会引发 NameError 异常。

说一下你对闭包的理解。

闭包是支持一等函数的编程语言(Python、JavaScript 等)中实现 词法绑定 的一种技术。

当捕捉闭包的时候,它的自由变量(在函数外部定义但在函数内部使用的变量)会在捕捉时被确定,这样即便脱离了捕捉时的上下文,它也能照常运行。

简单的说,可以将闭包理解为 能够读取其他函数内部变量 的函数。正在情况下,函数的局部变量在函数调用结束之后就结束了生命周期,但是闭包使得局部变量的生命周期得到了延展。使用闭包的时候需要注意,闭包会使得函数中创建的对象不会被垃圾回收,可能会导致很大的内存开销,所以闭包一定不能滥用。

闭包就是能够读取 外部函数内的变量 的函数。
作用1:闭包是将外层函数内的局部变量和外层函数的外部连接起来的一座桥梁。
作用2:将外层函数的变量持久地保存在内存中。

说一下 Python 中的多线程和多进程的应用场景和优缺点。

线程是操作系统分配 CPU 的基本单位,进程是操作系统分配内存的基本单位。通常我们运行的程序会包含一个或多个进程,而每个进程中又包含一个或多个线程。多线程的优点在于多个线程可以共享进程的内存空间,所以进程间的通信非常容易实现

但是如果使用官方的 CPython 解释器,多线程受制于 GIL(Global Interpreter Lock,全局解释器锁),并不能利用 CPU 的多核特性,这是一个很大的问题。使用多进程可以充分利用 CPU 的多核特性,但是进程间通信相对比较麻烦,需要使用 IPC 机制(管道、套接字等)。

多线程 适合那些会 花费大量时间在 I/O 操作上,但 没有太多并行计算需求,且 不需占用太多内存 的 I/O 密集型应用。

多进程 适合执行计算密集型任务(如:视频编码解码、数据处理、科学计算等)、可以分解为多个并行子任务,并能合并子任务执行结果的任务,以及在内存使用方面没有任何限制且不强依赖于 I/O 操作的任务。

Python 中实现 并发编程 通常有 多线程、多进程、异步编程 三种选择。异步编程实现了协作式并发,通过多个相互协作的子程序的用户态切换,实现对 CPU 的高效利用,这种方式也是非常适合 I/O 密集型应用的。

Python 如何实现多线程?

Python 主要是通过 threadthreading 这两个模块来实现多线程支持。

Python 的 thread 模块是比较底层的模块,Python 的 threading 模块是对 thread 做了一些封装,可以更加方便的被使用。但是 Python(CPython)由于 GIL(Global Interpreter Lock,全局解释器锁) 的存在无法使用 threading 充分利用 CPU 资源,如果想充分发挥多核 CPU 的计算能力需要使用 multiprocessing 模块(Windows下使用会有诸多问题)。

Python3.x 中已经摒弃了 Python2.x 中采用函数式 thread 模块中的 start_new_thread() 函数来产生新线程方式。

Python3.x 中通过 threading 模块创建新的线程有两种方法:

  • 一种是通过 threading.Thread(Target=executable Method),即传递给 Thread 对象一个可执行方法(或对象)。
  • 第二种是继承 threading.Thread 定义子类并重写 run() 方法。第二种方法中,唯一必须重写的方法是 run()

谈谈你对 “猴子补丁”(monkey patching)的理解。

“猴子补丁” 是动态类型语言的一个特性,代码运行时 在不修改源代码的前提下改变代码中的方法、属性、函数等 以达到热补丁(hot patch)的效果。

很多系统的安全补丁也是通过猴子补丁的方式来实现的,但实际开发中应该避免对猴子补丁的使用,以免造成代码行为不一致的问题。

在使用 gevent 库的时候,我们会在代码开头的地方执行 gevent.monkey.patch_all(),这行代码的作用是把标准库中的 socket 模块给替换掉,这样我们在使用 socket 的时候,不用修改任何代码就可以实现对代码的协程化,达到提升性能的目的,这就是对猴子补丁的应用。

另外,如果希望用 ujson 三方库替换掉标准库中的 json,也可以使用猴子补丁的方式,代码如下所示。

import json, ujson

json.__name__ = 'ujson'
json.dumps = ujson.dumps
json.loads = ujson.loads

单元测试中的 Mock 技术也是对猴子补丁的应用,Python 中的 unittest.mock 模块就是解决单元测试中用 Mock 对象替代被测对象所依赖的对象的模块。

解释一下线程池的工作原理。

池化技术 就是一种典型空间换时间的策略,我们使用的数据库连接池、线程池等都是池化技术的应用,Python 标准库 currrent.futures 模块的 ThreadPoolExecutor 就是线程池的实现,如果要弄清楚它的工作原理,可以参考下面的内容。

线程池是一种用于 减少线程本身创建和销毁造成的开销 的技术,属于典型的空间换时间操作。如果应用程序需要频繁的将任务派发到线程中执行,线程池就是必选项,因为 创建和释放线程涉及到大量的系统底层操作,开销较大,如果能够在应用程序工作期间,将创建和释放线程的操作变成 预创建和借还操作,将大大减少底层开销。

线程池在应用程序启动后,立即创建一定数量的线程,放入空闲队列中。这些线程最开始都处于阻塞状态,不会消耗 CPU 资源,但会占用少量的内存空间。当任务到来后,从队列中取出一个空闲线程,把任务派发到这个线程中运行,并将该线程标记为已占用。当线程池中所有的线程都被占用后,可以选择自动创建一定数量的新线程,用于处理更多的任务,也可以选择让任务排队等待直到有空闲的线程可用。在任务执行完毕后,线程并不退出结束,而是继续保持在池中等待下一次的任务。 当系统比较空闲时,大部分线程长时间处于闲置状态时,线程池可以自动销毁一部分线程,回收系统资源。

基于这种预创建技术,线程池将线程创建和销毁本身所带来的开销 分摊到了各个具体的任务上,执行次数越多,每个任务所分担到的线程本身开销则越小。

一般线程池都必须具备下面几个组成部分:

  • 线程池管理器:用于创建并管理线程池。
  • 工作线程和线程队列:线程池中实际执行的线程以及保存这些线程的容器。
  • 任务接口:将线程执行的任务抽象出来,形成任务接口,确保线程池与具体的任务无关。
  • 任务队列:线程池中保存等待被执行的任务的容器。

举例说明什么情况下会出现 KeyError、TypeError、ValueError。

举一个简单的例子,变量 a 是一个字典,执行 int(a[‘x’]) 这个操作就有可能引发上述三种类型的异常。

  • 如果字典中没有键 x,会引发 KeyError
  • 如果键 x 对应的值不是 strfloatintbool 以及 bytes-like 类型,在调用 int 函数构造 int 类型的对象时,会引发 TypeError
  • 如果 a[x] 是一个字符串或者字节串,而对应的内容又无法处理成 int 时,将引发 ValueError

为什么要使用 with open 打开文件?

f = open("...","wb")
try:
	f.write("hello world")
except:
	pass
finally:
	f.close()

打开文件在进行读写的时候可能会出现一些异常状况,如果按照常规的 f.open 写法,我们需要 tryexceptfinally,做异常判断,并且文件最终不管遇到什么情况,都要执行 finally 中的 f.close(),关闭文件,with 方法帮我们实现了 finallyf.close

with open() as f:

主要打开一个缓存区,将打开的数据储存在缓存区内,进行修改,增减等处理。

  • with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的清理操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等;
  • with 语句即上下文管理器,在程序中用来表示代码执行过程中所处的前后环境。上下文管理器:含有 __enter____exit__ 方法的对象就是上下文管理器。
  • enter():在执行 with 语句之前,首先执行该方法,通常返回一个实例对象,如果 with 语句有 as 目标,则将对象赋值给 as 目标。
  • exit():在执行 with 语句结束后,自动调用 __exit__() 方法,用户释放资源,若此方法返回布尔值 True,程序会忽略异常。
  • 使用环境:文件读写、线程锁的自动释放等。

这个上下文管理器,新手可能不是太好理解。其实你看它做了什么,with open() as f,然后你下面的代码里就都可以用 f 来做一些操作。也就是说在这个小代码段内,f 就指代了前面的文件,你用 f,就是在用这个文件。这里带来的另外一个好处当然就是自动打开和关闭文件, 这个大家就都熟悉了。

如何读取大文件,例如内存只有 4G,如何读取一个大小为 8G 的文件?

很显然 4G 内存要一次性的加载大小为 8G 的文件是不现实的,遇到这种情况必须要考虑 多次读取和分批次处理。在 Python 中读取文件可以先通过 open 函数获取文件对象,在读取文件时,可以通过 read 方法的 size 参数指定读取的大小,也可以通过 seek 方法的 offset 参数指定读取的位置,这样就可以 控制单次读取数据的字节数和总字节数。除此之外,可以使用内置函数 iter 将文件对象处理成 迭代器对象,每次只读取少量的数据进行处理,代码大致写法如下所示。

with open('...', 'rb') as file:
    for data in iter(lambda: file.read(2097152), b''):
        pass

在 Linux 系统上,可以通过 split 命令将大文件切割为小片,然后通过读取切割后的小文件对数据进行处理。例如下面的命令将名为 filename 的大文件切割为大小为 512M 的多个文件。

split -b 512m filename

如果愿意, 也可以将名为 filename 的文件切割为 10 个文件,命令如下所示。

split -n 10 filename

扩展:外部排序 跟上述的情况非常类似,由于处理的数据不能一次装入内存,只能放在读写较慢的外存储器(通常是硬盘)上。排序-归并算法 就是一种常用的外部排序策略。在排序阶段,先读入能放在内存中的数据量,将其排序输出到一个 临时文件,依此进行,将待排序数据组织为多个有序的临时文件,然后在归并阶段将这些临时文件组合为一个大的有序文件,这个大的有序文件就是排序的结果。

说一下你对 Python 中模块和包的理解。

每个 Python 文件就是一个模块,而保存这些文件的文件夹就是一个包,但是这个作为 Python 包的文件夹必须要有一个名为 __init__.py 的文件,否则无法导入这个包。

通常一个文件夹下还可以有子文件夹,这也就意味着一个包下还可以有子包,子包中的 __init__.py 并不是必须的。

模块和包解决了 Python 中 命名冲突 的问题,不同的包下可以有同名的模块,不同的模块下可以有同名的变量、函数或类。

在 Python 中可以使用 importfrom … import … 来导入包和模块,在导入的时候还可以使用 as 关键字对包、模块、类、函数、变量等进行别名,从而彻底解决编程中尤其是多人协作团队开发时的命名冲突问题。

说一下你知道的 Python 编码规范。

点评:企业的 Python 编码规范基本上是参照 PEP-8 或谷歌开源项目风格指南来制定的,后者还提到了可以使用 Lint 工具来检查代码的规范程度,面试的时候遇到这类问题,可以先说下这两个参照标准,然后挑重点说一下 Python 编码的注意事项。

  • 空格的使用
    • 使用空格来表示缩进而不要用制表符(Tab)。
    • 和语法相关的每一层缩进都用4个空格来表示。
    • 每行的字符数不要超过 79 个字符,如果表达式因太长而占据了多行,除了首行之外的其余各行都应该在正常的缩进宽度上再加上 4 个空格。
    • 函数和类的定义,代码前后都要用两个空行进行分隔。
    • 在同一个类中,各个方法之间应该用一个空行进行分隔。
    • 二元运算符的左右两侧应该保留一个空格,而且只要一个空格就好。
  • 标识符命名
    • 变量、函数和属性应该使用小写字母来拼写,如果有多个单词就使用下划线进行连接。
    • 类中受保护的实例属性,应该以一个下划线开头。
    • 类中私有的实例属性,应该以两个下划线开头。
    • 类和异常的命名,应该每个单词首字母大写。
    • 模块级别的常量,应该采用全大写字母,如果有多个单词就用下划线进行连接。
    • 类的实例方法,应该把第一个参数命名为 self 以表示 对象自身
    • 类的类方法,应该把第一个参数命名为 cls 以表示 该类自身
  • 表达式和语句
    • 采用内联形式的否定词,而不要把否定词放在整个表达式的前面。例如:if a is not b 就比 if not a is b 更容易让人理解。
    • 不要用检查长度的方式来判断字符串、列表等是否为 None 或者没有元素,应该用 if not x 这样的写法来检查它。
    • 就算 if 分支、for 循环、except 异常捕获等中只有一行代码,也不要将代码和 ifforexcept 等写在一起,分开写才会让代码更清晰。
    • import 语句总是放在文件开头的地方。
    • 引入模块的时候,from math import sqrtimport math 更好。
    • 如果有多个 import 语句,应该将其分为三部分,从上到下分别是 Python标准模块第三方模块自定义模块,每个部分内部应该按照模块名称的字母表顺序来排列。

参考资料

【1】【建议收藏】50 道硬核的 Python 面试题!
【2】Python 闭包(Closure)详解
【3】用最简单的语言解释 Python 的闭包是什么?

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【编程之路】Python编程进阶 的相关文章

  • 如何在 AWS CDK 创建的 Python Lambda 函数中安装外部模块?

    我在 Cloud9 中使用 Python AWS CDK 并且我部署简单的 Lambda 函数那应该是发送 API 请求到 Atlassian 的 API当对象上传到 S3 存储桶时 也是由 CDK 创建的 这是我的 CDK 堆栈代码 fr
  • python 中的代表

    我实现了这个简短的示例来尝试演示一个简单的委托模式 我的问题是 这看起来我已经理解了委托吗 class Handler def init self parent None self parent parent def Handle self
  • if 语句未命中中的 continue 断点

    在下面的代码中 两者a and b是生成器函数的输出 并且可以评估为None或者有一个值 def testBehaviour self a None b 5 while True if not a or not b continue pri
  • Argparse nargs="+" 正在吃位置参数

    这是我的解析器配置的一小部分 parser add argument infile help The file to be imported type argparse FileType r default sys stdin parser
  • Numpy 过滤器平滑零区域

    我有一个 0 及更大整数的 2D numpy 数组 其中值代表区域标签 例如 array 9 9 9 0 0 0 0 1 1 1 9 9 9 9 0 7 1 1 1 1 9 9 9 9 0 2 2 1 1 1 9 9 9 8 0 2 2 1
  • 奇怪的 MySQL Python mod_wsgi 无法连接到 'localhost' (49) 上的 MySQL 服务器问题

    StackOverflow上也有类似的问题 但我还没有发现完全相同的情况 这是在使用 MySQL 的 OS X Leopard 机器上 一些起始信息 MySQL Server version 5 1 30 Apache 2 2 13 Uni
  • 将 matplotlib 颜色图集中在特定值上

    我正在使用 matplotlib 颜色图 seismic 绘制绘图 并且希望白色以 0 为中心 当我在不进行任何更改的情况下运行脚本时 白色从 0 下降到 10 我尝试设置 vmin 50 vmax 50 但在这种情况下我完全失去了白色 关
  • 将 JSON 对象传递给带有请求的 url

    所以 我想利用 Kenneth 的优秀请求模块 https github com kennethreitz requests 在尝试使用时偶然发现了这个问题自由库API http wiki freebase com wiki API 基本上
  • 在 pytube3 中获取 youtube 视频的标题?

    我正在尝试构建一个应用程序来使用 python 下载 YouTube 视频pytube3 但我无法检索视频的标题 这是我的代码 from pytube import YouTube yt YouTube link print yt titl
  • 如何使用列表作为pandas数据框中的值?

    我有一个数据框 需要列的子集包含具有多个值的条目 下面是一个带有 运行时 列的数据框 其中包含程序在各种条件下的运行时 df condition a runtimes 1 1 5 2 condition b runtimes 0 5 0 7
  • Python 将日志滚动到变量

    我有一个使用多线程并在服务器后台运行的应用程序 为了无需登录服务器即可监控应用程序 我决定包括Bottle http bottlepy org为了响应一些HTTP端点并报告状态 执行远程关闭等 我还想添加一种查阅日志文件的方法 我可以使用以
  • python Soap zeep模块获取结果

    我从 SOAP API 得到如下结果 client zeep Client wsdl self wsdl transport transport auth header lb E authenticate self login res cl
  • 使用yield 进行字典理解

    作为一个人为的例子 myset set a b c d mydict item yield join item s for item in myset and list mydict gives as cs bs ds a None b N
  • 如何在 OSX 上安装 numpy 和 scipy?

    我是 Mac 新手 请耐心等待 我现在使用的是雪豹 10 6 4 我想安装numpy和scipy 所以我从他们的官方网站下载了python2 6 numpy和scipy dmg文件 但是 我在导入 numpy 时遇到问题 Library F
  • 您可以将操作直接应用于map/reduce/filter 中的参数吗?

    map and filter通常可以与列表理解互换 但是reduce并不那么容易被交换map and filter 此外 在某些情况下我仍然更喜欢函数语法 但是 当您需要对参数本身进行操作时 我发现自己正在经历语法体操 最终必须编写整个函数
  • 具有自定义值的 Django 管理外键下拉列表

    我有 3 个 Django 模型 class Test models Model pass class Page models Model test models ForeignKey Test class Question model M
  • 检查字典键是否有空值

    我有以下字典 dict1 city name yass region zipcode phone address tehsil planet mars 我正在尝试创建一个基于 dict1 的新字典 但是 它不会包含带有空字符串的键 它不会包
  • 从 Twitter API 2.0 获取 user.fields 时出现问题

    我想从 Twitter API 2 0 端点加载推文 并尝试获取标准字段 作者 文本 和一些扩展字段 尤其是 用户 字段 端点和参数的定义工作没有错误 在生成的 json 中 我只找到标准字段 但没有找到所需的 user fields 用户
  • 迭代 pandas 数据框的最快方法?

    如何运行数据框并仅返回满足特定条件的行 必须在之前的行和列上测试此条件 例如 1 2 3 4 1 1 1999 4 2 4 5 1 2 1999 5 2 3 3 1 3 1999 5 2 3 8 1 4 1999 6 4 2 6 1 5 1
  • Scrapy Spider不存储状态(持久状态)

    您好 有一个基本的蜘蛛 可以运行以获取给定域上的所有链接 我想确保它保持其状态 以便它可以从离开的位置恢复 我已按照给定的网址进行操作http doc scrapy org en latest topics jobs html http d

随机推荐

  • 【STL】模板概念+STL介绍

    模板 Template 模板就是C 实现代码重用机制的一种工具 它可以实现类型参数化 即把类型定义为参数 从而实现了真正的代码可重用性 可以使用模板来定义函数和类 当你定义一个如下函数 想要计算一个数的平方 int square int x
  • sql中用什么替代in

    IN和EXISTS 有时候会将一列和一系列值相比较 最简单的办法就是在where子句中使用子查询 在where子句中可以使用两种格式的子查询 第一种格式是使用IN操作符 where column in select from where 第
  • CVE-2019-0708 远程桌面代码执行漏洞复现

    0X00 靶机 漏洞环境 我当时就是虚拟机装了Windows7 SP1的系统 一下就ojbk 你们没有 就下载装吧 使用VM安装Windows7 SP1模拟受害机 Windows7 SP1下载链接 这里的靶机是使用清水表哥提供的win7sp
  • SOA:原理•方法•实践,第 1 部分: SOA 的基本概念

    SOA 原理方法实践 的第 1 章从概念上对 SOA 给出一个全面而精炼的总体描述 首先说明 SOA 的特点 以及使用 SOA 对系统进行架构决策和设计的必要性 然后介绍了 SOA 的参考体系结构 设计原则及相关技术的简介 查看本系列更多内
  • Android性能优化 _ 大图做帧动画卡?优化帧动画之 SurfaceView滑动窗口式帧复用

    ps 粗斜体表示引导方案逐步进化的关键点 SurfaceView逐帧解析 帧复用 简单回顾下上一篇的内容 原生帧动画在播放前解析所有帧 对内存压力大 SurfaceView可以精细地控制帧动画每一帧的绘制 在每一帧绘制前才解析当前帧 且解析
  • java常用部署脚本

    记一个工作中常用的java部署脚本 首先进入linux 1 使用下面代码段 vim run sh 2 然后按 i 进入编辑模式 3 拷贝下面脚本代码 bin sh chkconfig 2345 99 10 description Start
  • 西门子S7-1200 PLC进行项目选型要了解这些

    西门子S7 1200 PLC是西门子S7系列PLC产品中一员 S7系列产品包含有 S7 200 Smart 200 S7 1200 S7 300 S7 1500 S7 400等系列PLC 其中S7 200 Smart 200 S7 1200
  • Java多线程编程详解(第零章)

    Java多线程编程详解 0 参考书籍 Java并发编程实战 Java并发编程实战 本文是关于以上两本书的读书笔记以及一些个人思考 0 关于并发与多线程的简介 编写正确的程序很难 而编写正确的并发程序则难上加难 与串行程序相比 在并发程序中存
  • 如何针对单个表单项去掉验证和添加验证?

    this refs conpanyForm clearValidate district 清除校验 this refs conpanyForm validate district 添加校验
  • Ubuntu安装notepad++

    习惯了Windows的代码编写输入方式 转回Ubuntu vim编程总有点不习惯和不方便 即使vim功能很强大 我还是喜欢Windows的代码编写输入方式 简简单单 安装个notepadqq就行了 Ubuntu版notepad 安装方式 s
  • vue vue-json-viewer 展示 JSON 格式数据

    1 下载 vue json viewer npm 下载 vue json viewer Vue2 npm install vue json viewer 2 save Vue3 npm install vue json viewer 3 s
  • MS Active Accessibility 接口技术编程尝试

    MS Active Accessibility 接口技术编程尝试 编译 崔传凯 下载源代码 Microsoft Active Accessibility 2 0 is a COM based technology that improves
  • 【Ogre编程入门与进阶】第十三章 公告板与粒子系统

    Ogre编程入门与进阶 第十三章 公告板与粒子系统 标签 ogre公告板粒子系统ogre粒子系统 2015 07 05 14 41 1365人阅读 评论 1 收藏 举报 分类 Orge模块 16 版权声明 本文为博主原创文章 未经博主允许不
  • 计算机电源接口图解,菜鸟老鸟都要知道 电源接口图文全教程

    IT168 应用 电源的功率一直是玩家们关注的焦点 可对于刚涉足DIY领域的用户来说 自己组装DIY一台电脑拿才是最令人兴奋的事情 组装电脑少不了要接各种各样的线材 那么如何辨别各种类型的接口 每个接口之间的的功能有何区别呢 电源接口种类繁
  • 微信小程序之分享页面内容为空

    文章目录 错误记录 分享的关键方法 onShareAppMessage 错误记录 分享出去的页面 别人打开没有内容 解决方法参考文章 说是因为分享出去的页面的某些数据是上级页面传递过来的 结果直接分享出去的页面 别人打开是获取不到传递过来的
  • leetcode 二叉树题目总结

    leetcode 二叉树题目总结 一 基本问题 遍历 前序遍历 后序遍历 中序遍历 莫里斯遍历 空间复杂度O 1 层次遍历 由序列构造二叉树 递归解决二叉树问题 将二叉树转换为其他结构 二叉树结构 struct TreeNode int v
  • nrm安装(NPM源管理器)

    1 什么是nrm nrm 是一个 npm 源管理器 允许你快速地在 npm源间切换 npm默认情况下是使用npm官方源 使用命令可以查看 一般我们都会用淘宝npm源 设置淘宝源 npm set registry https registry
  • 智能小车之PWM脉冲控制小车调速

    目录 一 PWM脉冲控制小车调速 二 代码实现 一 PWM脉冲控制小车调速 原理 全速前进是LeftCon1A 0 LeftCon1B 1 完全停止是LeftCon1A 0 LeftCon1B 0 那么单位时间内比如 20ms 有15ms是
  • java keytool 工具

    Keytool 是一个JAVA环境下的安全钥匙与证书的管理工具 Keytool将密钥 key 和证书 certificates 存在一个称为keystore 的文件 受密码保护 中 在keystore里 包含两种数据 密钥实体 Key en
  • 【编程之路】Python编程进阶

    Python编程进阶 不使用中间变量 交换两个变量a和b的值 a b b a 需要注意 a b b a 这种做法其实并不是元组解包 虽然很多人都这样认为 Python 字节码指令中有 ROT TWO 指令来支持这个操作 类似的还有 ROT