11.请说说Python中的作用域是怎样划分的
在Python中,作用域(Scope)是指变量、函数和对象的可访问性和可见性范围。Python中的作用域主要分为以下几个层次:
局部作用域(Local Scope):局部作用域是指在函数内部定义的变量,它们只在函数内部可见和可访问。当函数执行完毕后,局部作用域中的变量通常会被销毁。例如:
def my_function(): x = 10 # x是局部变量 print(x) my_function() # 函数内可以访问x print(x) # 但在函数外部无法访问x,会引发NameError
嵌套作用域(Enclosing Scope):嵌套作用域是指在函数内部可以访问外部函数中定义的变量。当在一个函数内访问一个变量时,Python首先在局部作用域查找,然后在嵌套的外部函数作用域中查找,直到找到为止。例如:
def outer_function(): y = 20 # y是外部函数的局部变量 def inner_function(): print(y) # inner_function可以访问外部函数的变量y inner_function() outer_function()
全局作用域(Global Scope):全局作用域是指在整个程序中定义的变量,它们在程序的任何地方都可见和可访问。全局变量通常在模块级别定义,可以被多个函数和代码块共享。例如:
x = 10 # x是全局变量 def my_function(): print(x) # 函数内部可以访问全局变量x my_function() print(x) # 在函数外部也可以访问x
内置作用域(Built-in Scope):内置作用域包含了Python语言内置的变量和函数,例如
print()
、len()
等。这些变量和函数在任何地方都是可见和可访问的,无需导入任何模块。例如:print(len("Hello")) # len()函数是内置的,无需导入模块
Python按照这个层次来查找变量和标识符的值。当在某个作用域中引用一个变量时,Python会按照局部、嵌套、全局、内置的顺序查找,直到找到匹配的标识符或抵达最外层的内置作用域。
理解作用域是编写Python程序的关键部分,它有助于避免变量名冲突和正确管理变量的可见性。
12.请解释下什么是解释性语言,什么是编译性语言
解释性语言:例如python
解释型语言通过解释器将代码一行一行逐行转化成机器代码并直接执行,无需额外的编译步骤
编译型语言:例如c,c++
编译型语言通过编译器将代码编译成机器代码,并且生成一个独立的可执行文件,这个可执行文件可以直接运行,就不需要源代码了
部分语言两种能力都有:java具有字节码编译和解释执行的能力
13.请说说get请求与post请求的不同
1、GET 表示从指定的服务器中获取数据 ,POST 表示提交数据给指定的服务器处理
2、GET查询的字符串参数显示在地址栏的URL中,请求参数是可见的.POST查询字符串不会显示在地址栏中,请求参数是不可见的
3、GET请求能够被缓存,POST请求不能被缓存下来
4、GET请求有长度限制,POST请求没有长度限制
5、GET查询的字符串参数会显示在地址栏的URL中,不安全,请不要使用GET请求提交敏感数据
POST请求数据不会显示在地址栏中,也不会缓存下来或保存在浏览记录中,所以POST请求比GET请求安全,但也不是最安全的方式。如需要传送敏感数据,请使用加密方式传输
14.Python中正则表达式如何提取数据
使用分组捕获。
1、在表达式中把要提取的内容使用分组括起来
2、然后执行match或search匹配;匹配后使用group(num)和groups方法获取提取内容, 或者直接使用findall
使用 re 模块的函数进行匹配和提取:
re.match(pattern, string): 从字符串的开头开始匹配模式。
re.search(pattern, string): 在整个字符串中搜索匹配模式的第一个出现。
re.findall(pattern, string): 返回字符串中所有匹配模式的结果列表。
re.finditer(pattern, string): 返回一个迭代器,每次迭代产生一个匹配对象。
re.split(pattern, string): 根据模式拆分字符串。
re.sub(pattern, replacement, string): 替换字符串中匹配模式的部分。
16.Python中列表list,集合set,字典dict,元组tuple的区别
列表list和元组tuple属于序列,可以使用下标访问,可以进行切片操作,可以排序。其区别:
1、list用[]定义,tuple用()定义
2、list是可变容器,可以修改其中的元素,tuple属于不可变容器,不能修改元素。
3、tuple属于不可变容器,没有深复制和浅复制,list有深复制和浅复制
4、list一般不做函数参数的默认值(因为可变),tuple可以做函数参数的默认值(因为不可变)
集合set和字典dict不属于序列,无序,不能通过下标访问,但set和dict也有区别:
1、dict可以通过key访问,set不行
2、dict键不可以重复,值可以重复,set不允许有重复元素
3、dict可以根据键修改值,set不能修改元素
集合:set,用{}定义,内容可删除,无序
元组:tuple,用()定义,内容不可修改,有序
列表:list,用[]定义,内容可修改,有序
字典:dict,用{a:a}定义,内容可修改,
17.Python中type与isinstance方法的区别
在Python中我们使用type和isinstance来测试和判断数据类型
其中type用于获取对象的类型,其返回值是对象的类型;
isinstance用于测试对象是否是某种类型,返回True或False。区别:
1、目的不同,type主要用于返回对象的类型,isinstance用于判断对象是否是指定类型
2、返回值不同,type返回类型,isinstance返回True或False
3、type不能判断子类对象是否属于父类,而isinstance可以
type()
和isinstance()
都可以用来检查 Python 对象的类型,但它们的用途、行为和特点有所不同。以下是它们之间的主要区别:
考虑继承:
type()
只返回对象的直接类型,不考虑对象的继承关系。isinstance()
考虑对象的继承关系。如果对象是给定类的子类的实例,isinstance()
仍然会返回True
。class Base: pass class Derived(Base): pass d = Derived() print(type(d) is Base) # False print(isinstance(d, Base)) # True
处理类型元组:
type()
不支持多重类型检查。isinstance()
可以接受一个类型元组作为其第二个参数,这使得你可以检查对象是否为多种类型中的一种。x = 10 print(isinstance(x, (int, float, str))) # True
常见用途:
type()
更常用于在动态类型语言中确定对象的具体类型。isinstance()
更常用于确保对象遵循某个接口或基类,因为它考虑继承。返回值:
type()
返回对象的直接类型,是一个类型对象,例如<class 'int'>
。isinstance()
返回一个布尔值,表示对象是否为给定的类型或给定类型的子类。总结:
虽然
type()
和isinstance()
都可以用于类型检查,但通常推荐使用isinstance()
,因为它提供了更广泛和更灵活的类型检查能力,尤其是在面向对象编程和继承结构中。
18.Python中while-else,for-else语法的用法并举例
Python中的for、while循环都有一个可选的else分支,在循环迭代正常完成之后执行。
换句话说,如果我们不是以正常方式退出循环,那么else分支将不被执行。
也就说在循环体内用break、return退出不会执行else语句。
19.请说说您对Python中装饰器的理解,以及如何实现一个通用装饰器
对Python装饰器的理解:
1、装饰器的实现是由闭包支撑的
2、装饰器本质上是一个python函数,它可以在让其他函数在不需要做任何代码的变动的前提下增加额外的功能
3、装饰器的返回值也是一个函数的对象,该函数对象就是添加额外功能后的与原函数同名的函数.
# 获取程序执行时间的装饰器
import time
def get_time(fn):
def inner():
# ① 添加装饰器修饰功能(获取程序的执行时间)
begin = time.time()
# ② 调用fn函数,执行原函数代码
fn()
end = time.time()
print(f'这个函数的执行时间:{end - begin}')
return inner
@get_time
def demo():
for i in range(1000000):
print(i)
demo()
当然,Python 中的装饰器是一种非常强大和有用的工具,允许你在不修改原始代码的前提下修改或增强函数或方法的行为。装饰器的核心思想是基于高阶函数的概念,即函数可以作为参数传递给其他函数,并且可以返回函数。
1. 基本装饰器
装饰器本质上是一个函数,它接受一个函数作为参数,通常返回一个新的函数。
def simple_decorator(func): def wrapper(): print("Something is happening before the function is called.") func() print("Something is happening after the function is called.") return wrapper @simple_decorator def say_hello(): print("Hello!") say_hello()
上面的代码输出:
Something is happening before the function is called. Hello! Something is happening after the function is called.
2. 装饰器带参数
如果你的原始函数带有参数,你可以使用
*args
和**kwargs
来捕获它们。def repeat(num_times): def decorator_repeat(func): def wrapper(*args, **kwargs): for _ in range(num_times): func(*args, **kwargs) return wrapper return decorator_repeat @repeat(num_times=3) def greet(name): print(f"Hello {name}") greet("Alice")
这将输出:
Hello Alice Hello Alice Hello Alice
- 返回值
如果原始函数有返回值,确保装饰器中的
wrapper
函数返回该值。def debug(func): def wrapper(*args, **kwargs): result = func(*args, **kwargs) print(f"{func.__name__} returned value: {result}") return result return wrapper @debug def add(a, b): return a + b result = add(2, 3)
4. 保留原始函数的元信息
当你使用装饰器时,被装饰的函数的元信息(如名称、文档字符串等)可能会丢失。为了解决这个问题,可以使用
functools.wraps
。from functools import wraps def debug(func): @wraps(func) def wrapper(*args, **kwargs): result = func(*args, **kwargs) print(f"{func.__name__} returned value: {result}") return result return wrapper @debug def add(a, b): """Adds two numbers.""" return a + b print(add.__name__) print(add.__doc__)
5. 类装饰器
除了函数装饰器外,你还可以定义类装饰器。类装饰器通常用于装饰类的方法或以某种方式修改类的行为。
class MyDecorator: def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): print("Calling function with MyDecorator!") return self.func(*args, **kwargs) @MyDecorator def example_func(): print("Inside example_func") example_func()
总结
Python 装饰器提供了一种简洁的方法来修改或扩展函数、方法或类的行为,而无需直接修改其源代码。由于装饰器本身是函数,因此它们可以被参数化、嵌套或组合,为 Python 代码提供了极大的灵活性。
20.如何理解Python中深度拷贝和浅拷贝
1、浅拷贝旨在减少内存的占用
2、深拷贝可以在做数据的清洗、修改或者入库的时候,对原数据进行复制一份,以防数据修改之后,找不到原数据
对于不可变对象类型,没有深浅拷贝的说法,无论是深拷贝还是浅拷贝结果一样的,如果对其重新赋值,
3、对于可变类型来说,浅拷贝只复制容器,不复制容器中元素;深拷贝复制容器,元素如果是可变类型,也复制元素
21.如何在不改变原有list顺序的前提下,去除该list中的重复数据
def remove_duplicates(input_list):
# 引入集合
seen = set()
output_list = []
for item in input_list:
# 引入集合的目的是下面这一步查询的比较快,接近于O(1)
if item not in seen:
seen.add(item)
output_list.append(item)
return output_list
lst = [1, 2, 2, 3, 4, 4, 4, 5, 6, 6, 7]
print(remove_duplicates(lst))
22.Python中classmethod与staticmethod的区别
1、classmethod是类方法,staticmethod是静态方法,都属于类,为所有对象共有
2、classmethod有参数cls,这个参数是类对象,由系统自动传递;staticmethod没有类对象参数。
3、classmethod在一些工厂类的情况下使用较多,也就是说OOP里继承的时候使用,staticmethod一般情况下可以替换为外部的函数
在 Python 中,
classmethod
和staticmethod
都是用于定义在类上而不是实例上调用的方法。但它们的用途和行为有所不同。以下是它们之间的主要区别:1. 参数传递:
classmethod:第一个参数传递的是类本身,通常命名为
cls
(但这不是强制的)。class MyClass: @classmethod def example_classmethod(cls, arg1, arg2, ...): ...
staticmethod:不传递任何特定的参数,与类的实例或类本身无关。它的行为就像一个常规的函数,只是它是绑定到类的。
class MyClass: @staticmethod def example_staticmethod(arg1, arg2, ...): ...
2. 用途:
classmethod:通常用于工厂方法,这些方法可以创建类的实例,而不必直接使用类的构造函数。此外,它也可以用于定义必须在整个类上起作用而不是某个实例上的方法。
staticmethod:当你需要在类上执行某些操作,但这些操作与类的属性或方法无关时,你可以使用静态方法。它们更像是类内部的常规函数。
3. 继承行为:
classmethod:可以被子类继承和重写。这意味着当你在子类中调用一个类方法时,传递给该方法的是子类,而不是父类。
staticmethod:不知道它们所属的类是什么。如果它们在子类中被重写,父类中的版本会被完全替换,而不是像常规的方法重写那样。
4. 调用方式:
classmethod 和 staticmethod 都可以通过类名或类的实例来调用。
MyClass.example_classmethod(...) MyClass.example_staticmethod(...) obj = MyClass() obj.example_classmethod(...) obj.example_staticmethod(...)
总结:
虽然
classmethod
和staticmethod
都可以在类定义中创建方法,而不依赖于类的实例,但它们的用途和行为方式是不同的。使用classmethod
当你需要访问类本身而不是实例,并且想要允许子类重写或扩展方法。使用staticmethod
当你只是想在类的命名空间中进行某些与类无关的操作。
23.请解释Python中with关键字的用法
在 Python 中,
with
关键字是用于资源管理的,常常与支持上下文管理协议的对象一起使用。上下文管理协议包括__enter__()
和__exit__()
方法。使用with
语句可以确保资源(例如文件、网络连接或数据库连接)在使用后被正确地释放或清理,而无需显式地调用清理代码。最常见的使用场景之一是文件操作:
with open('sample.txt', 'r') as file: content = file.read() # 在此块之后,文件已自动关闭,无需调用 file.close()
在上面的例子中,
open()
函数返回一个文件对象,该对象支持上下文管理协议。当进入with
块时,调用文件对象的__enter__()
方法(在这个例子中并不需要做任何特殊的事情),并且文件保持打开状态。当离开with
块时,调用文件对象的__exit__()
方法,确保文件被关闭。工作原理:
- 评估
with
后面的表达式,获取一个上下文管理器对象。- 调用上下文管理器对象的
__enter__()
方法。这个方法的返回值(如果有的话)会被赋值给as
关键字后面的变量。- 执行
with
块中的代码。- 无论
with
块中的代码是否成功完成或引发异常,都会调用上下文管理器对象的__exit__()
方法。如果with
块中的代码引发了异常,__exit__()
方法可以选择处理该异常、重新引发它或者忽略它。为什么使用
with
:使用
with
语句的主要优点是它确保了资源的适当管理。在上面的文件示例中,无论文件读取中是否发生错误,文件都会被关闭。这种模式避免了资源泄露,并提供了一种更清晰、简洁的方法来管理资源。自定义上下文管理器:
除了内置的上下文管理器,你也可以自定义上下文管理器,只需实现
__enter__()
和__exit__()
方法。例如:
class ManagedResource: def __enter__(self): print("Setting up resource.") return self def __exit__(self, exc_type, exc_val, exc_tb): print("Tearing down resource.") with ManagedResource() as resource: print("Using managed resource.")
上述代码将输出:
Setting up resource. Using managed resource. Tearing down resource.
此外,Python 的
contextlib
模块提供了更多创建和使用上下文管理器的工具和实用函数。
24.Python中变量在内存中的存储方式
在 Python 中,变量与内存中的对象之间的关系比某些其他语言(如 C 或 C++)中的关系更为抽象。在 Python 中,变量实际上是对内存中对象的引用,而不是存储实际数据值的存储位置。以下是 Python 中变量与内存关系的概述:
对象和引用:
- 当你在 Python 中创建一个对象(例如一个整数、列表或自定义类的实例),Python 将为这个对象分配一块内存。
- 当你创建一个变量并为其赋值,这个变量实际上是一个指向该对象的引用,而不是对象本身。
a = [1, 2, 3] # 'a' 是一个指向列表对象 [1, 2, 3] 的引用
引用计数:
- Python 使用引用计数机制来跟踪一个对象有多少个引用变量指向它。当引用计数降为零时,意味着该对象不再被任何变量引用,因此它可以被垃圾收集器回收,从而释放内存。
b = a # 现在 'a' 和 'b' 都指向同一个列表对象,该对象的引用计数增加了 del a # 删除 'a' 对该对象的引用,引用计数减少
不变性:
- 一些 Python 数据类型(如整数、字符串、元组)是不可变的。这意味着一旦创建了这样的对象,其内容就不能被改变。但是,你仍然可以更改引用这些对象的变量,使其指向其他对象。
动态类型:
- 由于变量只是引用,因此同一个变量可以在其生命周期中引用不同类型的对象。
x = 42 # 'x' 引用一个整数对象 x = "hello" # 现在 'x' 引用一个字符串对象
内存管理:
- Python 有一个内置的垃圾收集器,它负责自动回收不再使用的内存。除了引用计数外,它还使用一个循环检测器来检测并清除引用循环(例如,两个对象相互引用,但都不再被其他对象引用)。
内存共享:
- 对于某些不可变类型,如小的整数或短字符串,Python 有时会重用已有对象的内存,而不是为每个新值分配新的内存。
深拷贝与浅拷贝:
- 由于变量是对象的引用,所以简单地将一个变量赋给另一个变量只是复制了引用,而不是对象本身。为了复制对象而不是引用,你可能需要使用深拷贝或浅拷贝,具体取决于是否需要复制对象中的嵌套对象。
总之,Python 的变量和内存管理的抽象层次较高,使得开发者可以更加集中地关注逻辑和功能,而不是内存管理的细节。但了解背后的工作原理仍然是有助于写出更高效和更安全代码的。
25.队列与栈两种数据结构的区别和Python实现
队列:先进先出
栈:先进后出
# 栈的实现
class Stack:
def __init__(self):
self.items = []
def push(self, item):
"""将元素推入栈顶"""
self.items.append(item)
def pop(self):
"""从栈顶弹出元素"""
if not self.is_empty():
return self.items.pop()
raise IndexError("pop from an empty stack")
def peek(self):
"""查看栈顶元素但不移除它"""
if not self.is_empty():
return self.items[-1]
raise IndexError("peek from an empty stack")
def is_empty(self):
"""判断栈是否为空"""
return len(self.items) == 0
def size(self):
"""返回栈的大小"""
return len(self.items)
# 使用栈
stack = Stack()
stack.push(1)
stack.push(2)
stack.push(3)
print(stack.pop()) # 输出:3
# 队列的实现
from collections import deque
class Queue:
def __init__(self):
self.items = deque()
def enqueue(self, item):
"""将元素添加到队尾"""
self.items.append(item)
def dequeue(self):
"""从队首移除元素"""
if not self.is_empty():
return self.items.popleft()
raise IndexError("dequeue from an empty queue")
def front(self):
"""查看队首元素但不移除它"""
if not self.is_empty():
return self.items[0]
raise IndexError("front from an empty queue")
def is_empty(self):
"""判断队列是否为空"""
return len(self.items) == 0
def size(self):
"""返回队列的大小"""
return len(self.items)
# 使用队列
queue = Queue()
queue.enqueue(1)
queue.enqueue(2)
queue.enqueue(3)
print(queue.dequeue()) # 输出:1
26.请写出冒泡,选择,快速排序算法的其中一个
# 冒泡排序
def bubble_sort(arr):
n = len(arr)
for i in range(n):
swapped = False
for j in range(0, n-i-1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
swapped = True
if not swapped:
break
return arr
# 选择排序
def selection_sort(arr):
n = len(arr)
for i in range(n):
min_idx = i
for j in range(i+1, n):
if arr[min_idx] > arr[j]:
min_idx = j
arr[i], arr[min_idx] = arr[min_idx], arr[i]
return arr
# 快速排序
def quick_sort(arr):
if len(arr) <= 1:
return arr
pivot = arr[len(arr) // 2]
left = [x for x in arr if x < pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
return quick_sort(left) + middle + quick_sort(right)
arr = [64, 34, 25, 12, 22, 11, 90]
print("Bubble Sorted:", bubble_sort(arr.copy()))
print("Selection Sorted:", selection_sort(arr.copy()))
print("Quick Sorted:", quick_sort(arr.copy()))
27.请说说你对Python中高阶函数的认识
高阶函数是接受其他函数作为参数或返回其他函数的函数。Python 提供了一些内置的高阶函数,用于对数据进行处理和操作。以下是五个常用的高阶函数及其简要描述:
map(function, iterable, ...)
:使用给定的函数对指定序列做映射。对
iterable
中的每一项,都使用function
进行计算,并返回新的迭代器。nums = [1, 2, 3, 4] squared = map(lambda x: x**2, nums) print(list(squared)) # [1, 4, 9, 16]
filter(function, iterable)
:使用给定的函数过滤序列。返回由使函数返回值为
True
的元素组成的新迭代器。nums = [1, 2, 3, 4, 5] evens = filter(lambda x: x % 2 == 0, nums) print(list(evens)) # [2, 4]
reduce(function, iterable[, initializer])
:在 Python3 中,
reduce()
被移到了functools
模块。它连续地将两个元素应用于函数,并返回单个结果。from functools import reduce nums = [1, 2, 3, 4] product = reduce(lambda x, y: x * y, nums) print(product) # 24
sorted(iterable, *, key=None, reverse=False)
:根据指定的参数对序列进行排序,并返回新的列表。
words = ["apple", "banana", "cherry"] sorted_words = sorted(words, key=lambda x: len(x)) print(sorted_words) # ['apple', 'cherry', 'banana']
zip(*iterables)
:创建一个迭代器,聚合来自每个可迭代对象的元素。返回由元组组成的迭代器,其中第 i 个元组包含来自每个参数序列或可迭代对象的第 i 个元素。
names = ["Alice", "Bob", "Charlie"] ages = [25, 30, 35] combined = zip(names, ages) print(list(combined)) # [('Alice', 25), ('Bob', 30), ('Charlie', 35)]
这些高阶函数提供了处理和操作数据的强大工具,使得 Python 编程更为简洁和表达力强。
28.你是如何理解Python中的封装,继承,多态的
在面向对象编程(Object-Oriented Programming, OOP)中,封装、继承和多态是三个核心概念。Python,作为一种支持面向对象编程的语言,也实现了这些概念。下面是我对这些概念在 Python 中的理解:
封装 (Encapsulation):
封装是将对象的状态(属性)和行为(方法)包装在一个单一的单元中的概念。这可以实现以下两点:
数据隐藏:对象的内部状态可以通过公有方法(getters 和 setters)进行访问和修改,而不是直接公开数据。这允许对象对其内部数据进行验证或修改操作。
抽象:对象只显示其提供的功能给外部世界,隐藏内部的实现细节。
class Car: def __init__(self, brand, speed): self._brand = brand # 下划线表示属性是“受保护的”,即尽量不要从类外部直接访问 self._speed = speed def accelerate(self): self._speed += 10 def get_speed(self): return self._speed
继承 (Inheritance):
继承是从现有类创建新类的能力。新类继承了现有类的属性和方法,并可以添加或覆盖原有功能。这有助于实现代码重用和建立一个逻辑的类型层次结构。
class Vehicle: def __init__(self, brand): self.brand = brand def move(self): print("Moving forward") class Car(Vehicle): # Car 是 Vehicle 的子类 def honk(self): print("Car honking")
在上述代码中,
Car
类继承了Vehicle
类。因此,Car
的对象可以访问move
方法和honk
方法。多态 (Polymorphism):
多态的字面意思是“多种形态”。在 OOP 中,多态是指不同的对象可以以各自的方式响应相同的方法调用。这意味着一个函数/方法可以根据对象的具体类型以多种不同的方式执行。
class Bird: def sound(self): return "Some sound" class Sparrow(Bird): def sound(self): return "Chirp chirp" class Crow(Bird): def sound(self): return "Caw caw" def make_sound(bird): print(bird.sound()) sparrow = Sparrow() crow = Crow() make_sound(sparrow) # 输出:Chirp chirp make_sound(crow) # 输出:Caw caw
在这个示例中,
make_sound
函数接受一个Bird
类型的参数,并调用它的sound
方法。不同的鸟类通过多态性以其自己的方式实现了这个方法。总结来说,封装、继承和多态是面向对象编程的三个基本柱石,它们共同帮助构建结构化、可重用和灵活的代码系统。
29.Python中的内存管理机制是怎样的
Python 的内存管理机制主要围绕以下几个方面:
私有堆结构:
Python 使用私有堆来维护其内部对象和数据结构。开发者无法访问此私有堆,只能使用 Python 的内存管理器进行操作。引用计数:
Python 内部使用引用计数来跟踪对象的引用数量。当一个对象的引用计数跌到 0 时,表示没有任何引用指向该对象,该对象就会被垃圾回收。但是,仅仅依赖引用计数有一个主要的问题,即可能会导致循环引用。例如,如果两个对象相互引用,但其他无法访问它们,则它们之间的引用计数都不会为 0,导致不会被自动回收。
垃圾回收机制:
为了解决上述的循环引用问题,Python 使用了一个垃圾回收器,主要基于“分代回收”策略。Python 将所有对象分为三个“代”:
- 第 0 代:新创建的对象。
- 第 1 代:经历过一次垃圾回收,但仍然存活的对象。
- 第 2 代:经历过多次垃圾回收的对象。
新创建的对象首先分配到第 0 代。当第 0 代的对象数量超过阈值时,Python 将执行垃圾回收。存活下来的对象会被移动到第 1 代。类似地,当第 1 代的垃圾回收被触发时,存活下来的对象会被移动到第 2 代。
内存池机制:
对于固定大小的小块内存,Python 使用了内存池机制。内存池可以提高内存分配的效率。对象特定的分配器:
对于常见的固定大小的对象,例如整数、浮点数和列表,Python 使用专门的快速分配器。这可以减少常见对象类型的内存分配和回收的开销。内存释放:
Python 不会立即释放系统的内存。当内存不再使用时,Python 会将其保留以便后续再次使用。但在某些情况下,特别是涉及大块内存的操作时,Python 可能会释放内存回到操作系统。手动内存管理:
尽管 Python 提供自动内存管理,但仍可以使用某些工具和方法来影响其行为。例如,可以使用gc
模块手动触发垃圾回收或禁用它。总的来说,Python 的内存管理机制通过多种方法确保内存的高效使用,同时尽量减少开发者需要关注的内存细节。
30、谈谈你对Python进程,线程以及协程的理解
1、进程是CPU资源分配的基本单位,线程是独立运行和独立调度的基本单位(CPU上真正运行的是线程)。
2、进程拥有自己的资源空间,一个进程包含若干个线程,线程与CPU资源分配无关,多个线程共享同一进程内的资源。
3、线程的调度与切换比进程快很多。
4、CPU密集型代码(各种循环处理、计算等等):使用多进程。IO密集型代码(文件处理、网络爬虫等):使用多线程。
5、协程(Coroutine,又称微线程,纤程)是一种比线程更加轻量级的存在,协程不是被操作系统内核所管理,而完全是由程序所控制。协程具有高并发、高扩展性、低成本的特点,一个CPU支持上万的协程都不是问题。所以,很适合用于高并发处理。通常,协程可以处理IO密集型程序的效率问题,但是处理CPU密集型不是它的长处,如要充分发挥CPU利用率可以结合多进程+协程。
31、简述类属性和对象属性的区别
在 Python 中,类属性和对象属性是 OOP (面向对象编程) 的两个基本概念。下面简述它们之间的主要区别:
定义位置:
- 类属性:定义在类中,但在方法体之外。它们属于类本身。
- 对象属性:通常在类的方法中通过
self
关键字进行定义和初始化。它们属于特定的对象实例。内存存储:
- 类属性:由于它们属于类,因此类属性只存储一次,通常在类的命名空间中。
- 对象属性:每个对象实例都会有自己的属性存储,因此,对于每个对象,都会分配一个新的内存地址来存储它的对象属性。
访问方式:
- 类属性:可以通过类名或对象实例来访问。
- 对象属性:只能通过对象实例来访问。
可变性:
- 类属性:由于类属性是共享的,所以当一个对象修改了一个类属性,所有其他对象看到的也会是修改后的值。
- 对象属性:每个对象实例都有其自己的属性版本,因此修改一个对象的属性不会影响其他对象的相同属性。
使用场景:
- 类属性:通常用于存储与类的所有实例都相关的值,例如常量或类级配置。
- 对象属性:用于存储与每个对象实例相关的数据。
示例:
class MyClass: class_attribute = "I am a class attribute" def __init__(self, value): self.object_attribute = value # 创建对象 obj1 = MyClass("Object 1") obj2 = MyClass("Object 2") print(MyClass.class_attribute) # 输出: I am a class attribute print(obj1.object_attribute) # 输出: Object 1 print(obj2.object_attribute) # 输出: Object 2 # 修改类属性 MyClass.class_attribute = "Modified class attribute" print(obj1.class_attribute) # 输出: Modified class attribute
这个示例展示了类属性和对象属性的基本差异。在实际编程中,根据需要合理选择和使用这两种属性。
32、类中@property装饰器有什么用
@property
装饰器在 Python 中用于创建类的属性,但它是一种特殊的属性,具有以下主要用途:
封装属性访问:
@property
装饰器允许您封装属性的访问和计算。它允许您将属性的读取和写入操作封装在方法中,以提供更多的控制和验证。只读属性:通过使用
@property
,您可以创建只读属性,这意味着属性的值只能被读取,不能被直接修改。这有助于确保数据的一致性和完整性。计算属性:您可以使用
@property
创建计算属性,这是一种属性的值不是直接存储在实例变量中,而是通过某种计算方式得到的属性。这使得您可以在访问属性时执行自定义的计算逻辑。隐藏属性的实现细节:通过使用
@property
,您可以隐藏属性的实现细节。外部代码可以像访问普通属性一样访问属性,而不需要知道属性是如何实现的。兼容性和接口一致性:当您需要更改类的内部实现时,使用
@property
可以确保外部代码继续使用相同的属性访问方式,从而提供了接口的一致性。以下是一个示例,演示了如何使用
@property
装饰器创建只读属性:class Circle: def __init__(self, radius): self._radius = radius # 注意属性名前加下划线,表示属性是受保护的 @property def radius(self): return self._radius # 创建 Circle 对象 circle = Circle(5) # 访问只读属性 print(circle.radius) # 输出:5 # 试图修改只读属性会引发 AttributeError circle.radius = 10 # 会引发 AttributeError
在这个示例中,
radius
方法被标记为@property
,它允许circle.radius
这样的语法来访问属性,但不允许直接修改它。这有助于确保属性的完整性和只读性。
33、请用Python语言实现功能:输入年月日,判断这一天是这一年的第几天?
from datetime import datetime
def day_of_year(year, month, day):
input_date = datetime(year, month, day)
first_day_of_year = datetime(year, 1, 1)
day_number = (input_date - first_day_of_year).days + 1
return day_number
# 输入年月日
year = int(input("请输入年份:"))
month = int(input("请输入月份:"))
day = int(input("请输入日期:"))
# 计算并输出这一天是这一年的第几天
day_number = day_of_year(year, month, day)
print(f"{year}年{month}月{day}日是这一年的第{day_number}天。")
34、打乱一个排好序的list对象alist?
要打乱一个排好序的列表(或任何可迭代对象),您可以使用
random
模块中的shuffle
函数。下面是一个示例代码:import random alist = [1, 2, 3, 4, 5] random.shuffle(alist) print(alist)
在上述代码中,我们首先导入了
random
模块,然后使用random.shuffle()
函数来打乱列表alist
的元素顺序。执行后,alist
将包含相同的元素,但顺序会被随机打乱。请注意,
random.shuffle()
会修改原始列表,如果您希望保持原始列表不变,可以首先创建一个副本来打乱。例如:import random alist = [1, 2, 3, 4, 5] shuffled_list = alist.copy() random.shuffle(shuffled_list) print(alist) # 原始列表不变 print(shuffled_list) # 打乱后的列表
35、现有字典 d={‘a’:24,‘g’:52,‘i’:12,‘k’:33}请按value值进行排序?
要按字典中的值对其进行排序,您可以使用 Python 的内置函数
sorted()
并传递一个自定义的排序函数作为key
参数。以下是一个示例代码:d = {'a': 24, 'g': 52, 'i': 12, 'k': 33} # 使用sorted函数按值对字典进行排序 sorted_d = dict(sorted(d.items(), key=lambda item: item[1])) print(sorted_d)
在这个示例中,我们使用了
sorted()
函数来对字典d
的键值对(通过items()
方法获得)进行排序。key
参数是一个 lambda 函数,它将每个键值对作为输入,并返回其值,用于排序。最后,我们将排序后的结果构建成一个新的字典sorted_d
,它按值排序。输出将会是:
{'i': 12, 'a': 24, 'k': 33, 'g': 52}
请注意,字典是无序的数据结构,所以即使排序后的键值对在输出中看起来有序,但字典本身不会改变其元素的顺序。
36、将字符串"k:1 |k1:2|k2:3|k3:4",处理成字典 {k:1,k1:2,…}
input_str = "k:1 | k1:2 | k2:3 | k3:4"
# 使用 split() 函数分割字符串,然后去掉空格
key_value_pairs = input_str.split("|")
key_value_pairs = [pair.strip() for pair in key_value_pairs]
# 创建一个空字典,然后逐个解析键值对并添加到字典中
result_dict = {}
for pair in key_value_pairs:
key, value = pair.split(":")
result_dict[key] = int(value)
print(result_dict)
37、写一个列表生成式,产生一个公差为11的等差数列
# 产生一个公差为11的等差数列
arithmetic_sequence = [x for x in range(0, 101, 11)]
print(arithmetic_sequence)
38、给定两个列表,怎么找出他们相同的元素和不同的元素?
list1 = [1, 2, 3, 4, 5]
list2 = [3, 4, 5, 6, 7]
# 找出相同元素(交集)
common_elements = set(list1) & set(list2)
# 找出不同元素(差集)
different_elements = set(list1) ^ set(list2)
print("相同元素:", common_elements)
print("不同元素:", different_elements)
39、请写出一段python代码实现删除list里面的重复元素?
original_list = [1, 2, 2, 3, 4, 4, 4, 5, 6, 6, 7]
# 使用集合来去除重复元素,并将结果转回列表
unique_list = list(set(original_list))
print(unique_list)
40、写一个函数找出一个整数数组中,第二大的数
要找出一个整数数组中的第二大的数,您可以编写一个函数,对数组进行排序,然后返回第二大的元素。以下是一个示例函数:
def find_second_largest(arr): if len(arr) < 2: return "数组长度不足,无法找到第二大的数" # 对数组进行排序 sorted_arr = sorted(arr, reverse=True) # 返回第二大的数 return sorted_arr[1] # 示例用法 arr = [1, 3, 5, 2, 6, 4] second_largest = find_second_largest(arr) print("第二大的数是:", second_largest)
在这个示例中,
find_second_largest
函数首先检查数组的长度是否小于2,如果是,会返回一条提示消息。然后,它对数组进行降序排序,并返回排序后的第二个元素,即第二大的数。请注意,这个方法会改变原始数组的顺序。如果需要保持原始数组不变,可以首先创建一个副本进行排序。
41、简述read、readline、readlines的区别?
read()
,readline()
, 和readlines()
是 Python 中用于读取文件内容的三种常见方法。它们之间的区别如下:
read()
:
read()
方法用于读取整个文件的内容,并将其作为一个字符串返回。- 如果不指定参数,它会读取整个文件。
- 如果指定了参数(例如,
read(100)
),它会读取指定字节数的数据。read()
会将文件指针移到文件的末尾,因此如果再次调用read()
,将返回空字符串。with open("example.txt", "r") as file: content = file.read()
readline()
:
readline()
方法用于逐行读取文件的内容。- 每次调用
readline()
,它会返回文件中的一行文本。- 当到达文件末尾时,
readline()
返回空字符串。with open("example.txt", "r") as file: line1 = file.readline() line2 = file.readline()
readlines()
:
readlines()
方法用于读取文件的所有行,并将它们存储在一个列表中。- 返回的列表中的每个元素都是文件中的一行文本。
- 可以使用迭代方式逐行处理文件的内容。
with open("example.txt", "r") as file: lines = file.readlines() for line in lines: # 处理每一行的文本
总结:
read()
读取整个文件并返回字符串。readline()
逐行读取文件。readlines()
读取所有行并将其存储在列表中。您可以根据需要选择这些方法来读取文件的内容。如果文件较大,推荐使用逐行读取的方法,以避免将整个文件加载到内存中。
42、手写一个判断时间的装饰器
import time
def timing_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
elapsed_time = end_time - start_time
print(f"函数 {func.__name__} 执行时间:{elapsed_time:.4f} 秒")
return result
return wrapper
# 使用装饰器来装饰需要测量执行时间的函数
@timing_decorator
def example_function():
# 这里可以放置需要测量执行时间的代码
time.sleep(2) # 模拟函数执行耗时操作
# 调用装饰后的函数
example_function()
43、函数调用参数的传递方式是值传递还是引用传递?
在 Python 中,函数调用的参数传递方式是引用传递。这意味着当您将一个变量作为参数传递给函数时,实际上传递的是该变量在内存中的引用(内存地址),而不是变量的值的拷贝。
虽然参数传递是引用传递,但在函数内部对参数的修改通常不会影响到原始变量的值,除非参数是可变对象(如列表或字典)。这是因为在函数内部,可以创建新的对象或修改可变对象,但不会影响到原始对象。
下面是一个示例来说明这一点:
def modify_list(my_list): my_list.append(4) my_list = [1, 2, 3] original_list = [0] modify_list(original_list) print(original_list) # 输出 [0, 4]
在上面的示例中,虽然在
modify_list
函数内部创建了一个新的列表[1, 2, 3]
并将其赋给了my_list
,但这不影响到原始列表original_list
的值。原始列表的值被修改,是因为我们对其进行了操作(调用了append
方法)。总结:Python 的函数参数传递方式是引用传递,但函数内部对参数的修改通常不会影响到原始变量的值,除非参数是可变对象。这种行为是 Python 中的一种特性,需要根据具体情况来理解和处理。
44、Python中pass,break, continue, return语句的作用分别是什么?
在 Python 中,
pass
、break
、continue
和return
是四种不同的语句,它们各自有不同的作用:
pass
语句:
pass
是一个占位符语句,通常用于表示什么都不做,仅用于语法完整性。- 有时在代码的某个地方需要占位,但又不需要执行任何操作时,可以使用
pass
。def empty_function(): pass # 占位函数,什么都不做
break
语句:
break
用于在循环中提前退出循环。- 当某个条件满足时,
break
语句会立即终止当前循环,不再执行后续的循环迭代。for i in range(5): if i == 3: break # 当 i 等于 3 时提前退出循环 print(i)
continue
语句:
continue
用于跳过当前迭代的剩余部分,并继续下一次迭代。- 当某个条件满足时,
continue
语句会跳过当前迭代中continue
之后的代码,然后进行下一次迭代。for i in range(5): if i == 2: continue # 当 i 等于 2 时跳过当前迭代 print(i)
return
语句:
return
用于在函数中返回值,并结束函数的执行。- 当函数执行到
return
语句时,它会返回指定的值,并退出函数。- 如果没有指定返回值,函数会返回
None
。def add(a, b): result = a + b return result # 返回计算结果并结束函数 result = add(3, 5) print(result) # 输出 8
总结:
pass
用于占位,不执行任何操作。break
用于提前退出循环。continue
用于跳过当前迭代并进行下一次迭代。return
用于返回值并结束函数的执行。
45、Python中如何交换两个变量的值?
在 Python 中,可以使用以下几种方式交换两个变量的值:
- 使用临时变量交换:
这是最常见的方式,通过一个临时变量来交换两个变量的值。# 定义两个变量 a = 5 b = 10 # 使用临时变量交换值 temp = a a = b b = temp print("a =", a) # 输出 a = 10 print("b =", b) # 输出 b = 5
- 使用多重赋值交换:
Python 支持多重赋值,可以在一行代码中交换两个变量的值。# 定义两个变量 a = 5 b = 10 # 使用多重赋值交换值 a, b = b, a print("a =", a) # 输出 a = 10 print("b =", b) # 输出 b = 5
- 使用算术运算交换:
通过加法和减法运算来交换两个变量的值,不需要额外的变量。# 定义两个变量 a = 5 b = 10 # 使用算术运算交换值 a = a + b b = a - b a = a - b print("a =", a) # 输出 a = 10 print("b =", b) # 输出 b = 5
- 使用位运算交换:
通过异或运算^
来交换两个变量的值,也不需要额外的变量。# 定义两个变量 a = 5 b = 10 # 使用位运算交换值 a = a ^ b b = a ^ b a = a ^ b print("a =", a) # 输出 a = 10 print("b =", b) # 输出 b = 5
这些方法都可以用来交换两个变量的值,您可以根据个人偏好选择其中的一种。多重赋值方式是最简洁和常用的方式。
46、Python中回调函数是如何通信的?
在 Python 中,回调函数(Callback Function)通常用于实现异步编程或事件驱动编程中的事件处理和通信。回调函数的通信是通过将函数作为参数传递给其他函数或对象来实现的。以下是回调函数通信的一般步骤:
- 定义回调函数:
首先,您需要定义一个或多个回调函数,这些函数将用于处理特定事件或异步操作的结果。回调函数通常是普通的函数或方法。def callback_function(result): # 处理回调结果的代码 print("Callback received result:", result)
- 调用函数并传递回调函数:
在需要的地方,调用函数或对象,并将回调函数作为参数传递给它们。这些函数或对象将在适当的时候调用回调函数。def perform_async_operation(callback): # 模拟异步操作 result = "Async operation result" # 调用回调函数,将结果传递给回调 callback(result) # 调用 perform_async_operation 并传递回调函数 perform_async_operation(callback_function)
- 回调函数的执行:
当触发了某个事件或异步操作完成时,包含回调函数的函数或对象将调用回调函数,并将结果传递给它。回调函数将执行相关的操作。在上述示例中,
perform_async_operation
函数执行一个模拟的异步操作,然后在操作完成时调用传递的回调函数callback_function
,并将结果传递给它。这种方式允许程序在异步或事件驱动的情况下进行通信和处理结果。回调函数是一种常见的模式,用于处理回调事件和异步任务的结果。通过将函数作为参数传递,可以实现不同部分之间的解耦和通信。
47、简述Python正则表达式中search和match的区别
在 Python 中,正则表达式模块
re
提供了search()
和match()
两个函数,用于在文本中查找匹配的模式,但它们的行为有一些不同之处:
search()
函数:
search()
函数用于在文本中搜索整个字符串,找到第一个与模式匹配的子串。- 它会返回第一个匹配的结果,而不管匹配是否在字符串的开头。
- 如果找到匹配,它返回一个
Match
对象,否则返回None
。import re pattern = r"world" text = "Hello, world!" result = re.search(pattern, text) if result: print("Match found:", result.group()) # 输出 "Match found: world" else: print("No match")
match()
函数:
match()
函数用于在文本的开头查找匹配的模式。- 它只在字符串的开头进行匹配,如果模式不在开头,则返回
None
。- 如果找到匹配,它返回一个
Match
对象。import re pattern = r"Hello" text = "Hello, world!" result = re.match(pattern, text) if result: print("Match found:", result.group()) # 输出 "Match found: Hello" else: print("No match")
总结:
search()
用于在整个字符串中查找匹配,返回第一个匹配的结果。match()
用于在字符串的开头查找匹配,只有在字符串开头匹配时才返回结果。- 如果需要查找整个字符串并找到第一个匹配,通常使用
search()
。如果要确保匹配在字符串开头,可以使用match()
。
48、简述可迭代对象和迭代器之间的区别和联系
可迭代对象(Iterable)和迭代器(Iterator)是 Python 中用于处理迭代的重要概念,它们之间有区别和联系。
可迭代对象(Iterable):
- 定义:可迭代对象是指那些可以被迭代(遍历)的对象,例如列表、元组、字符串、集合(set)、字典(dictionary)等。
- 特点:可迭代对象具有
__iter__()
方法,该方法返回一个迭代器对象,它可以用于迭代对象的元素。- 示例:
my_list = [1, 2, 3, 4, 5] for item in my_list: print(item)
迭代器(Iterator):
- 定义:迭代器是一个对象,它实现了
__iter__()
和__next__()
方法。__iter__()
方法返回迭代器本身,而__next__()
方法用于获取下一个元素,如果没有下一个元素则引发StopIteration
异常。- 特点:迭代器具有状态,它知道当前迭代到哪个元素,并且可以逐个返回元素。
- 示例:
my_iterable = iter([1, 2, 3, 4, 5]) print(next(my_iterable)) # 输出 1 print(next(my_iterable)) # 输出 2
区别和联系:
区别:
- 可迭代对象是一个拥有
__iter__()
方法的对象,它可以返回一个迭代器。- 迭代器是一个实现了
__iter__()
和__next__()
方法的对象,它用于迭代访问元素并维护迭代状态。联系:
- 可迭代对象可以通过
iter()
函数获得一个迭代器。- 在
for
循环中,迭代器通常被隐式地创建,以遍历可迭代对象的元素。- 迭代器可以用于手动遍历可迭代对象的元素,通过调用
next()
方法获取下一个元素。总之,可迭代对象和迭代器是迭代协议的两个核心概念,它们一起允许 Python 对象进行迭代操作。可迭代对象用于创建迭代器,并且迭代器是执行实际迭代的工具。在 Python 中,许多内置数据类型和自定义对象都可以被视为可迭代对象,使其能够在循环中被迭代。
49、有一个文件file.txt大小约为10G,但是内存只有4G,如果在只修改get_lines函数而其他代码保持不变的情况下,应该如何实现? 需要考虑的问题都有那些?
# 如果您只有4GB内存,但要处理一个大约10GB大小的文件,您不能一次性将整个文件加载到内存中。您需要按行逐行处理文件,以避免内存不足错误。同时,考虑到大文件的读取效率,最好使用迭代器的方式来处理文件,以减少内存的使用。
# 下面是一个修改后的get_lines函数,以处理大文件并逐行读取:
def get_lines(file_path):
with open(file_path, 'rb') as file:
while True:
line = file.readline()
if not line:
break
yield line
if __name__ == '__main__':
file_path = 'file.txt'
for line in get_lines(file_path):
print(line.decode('utf-8')) # 如果您知道文件编码,请指定正确的解码器
# 此修改的要点是:
# 1. 使用生成器(yield语句)来逐行读取文件,而不是一次性读取整个文件内容。
# 2. 使用while循环来持续读取文件,直到文件的末尾(readline返回空字符串)。
# 3. 在if __name__ == '__main__':块中,将文件路径传递给get_lines函数,然后逐行打印文件的内容。
# 这种方法可以有效地处理大文件,因为它只在需要时读取一行,并且不会将整个文件加载到内存中。
50、补全缺失的Python代码
def print_directory_contents(sPath):
'''这个函数接收文件夹的名称作为输入参数
返回该文件夹中文件的路径以及其包含文件夹中文件的路径'''
import os
def print_directory_contents(spath):
li = os.listdir(os.getcwd())
for i in li:
if os.path.isdir(i):
for l in os.listdir(i):
print(l)
else:
print(os.path.abspath(i))
print_directory_contents('a.txt')
51、 在Python中如何实现单例模
点评:这个题目在面试中出现的频率极高,因为它考察的不仅仅是单例模式,更是对Python语言到底掌握到何种程度
在Python中,有多种方法可以实现单例模式。以下是几种常用的实现方法:
使用模块:
Python的模块本身就是单例的。当你第一次导入一个模块时,它会被初始化,之后的导入操作将使用同一份已初始化的模块。使用
__new__
方法:
通过重写__new__
方法确保只有一个实例存在。class Singleton: _instance = None def __new__(cls, *args, **kwargs): if not cls._instance: cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs) return cls._instance
使用装饰器:
创建一个单例装饰器,使得任何你想要的类都可以变成单例。def singleton(cls): instances = {} def get_instance(*args, **kwargs): if cls not in instances: instances[cls] = cls(*args, **kwargs) return instances[cls] return get_instance @singleton class MyClass: pass
使用元类:
元类是创建类的类,你可以通过元类来确保类只有一个实例。class SingletonMeta(type): _instances = {} def __call__(cls, *args, **kwargs): if cls not in cls._instances: cls._instances[cls] = super(SingletonMeta, cls).__call__(*args, **kwargs) return cls._instances[cls] class Singleton(metaclass=SingletonMeta): pass
使用内置库:
Python的标准库functools
提供了@lru_cache
装饰器,也可以用来实现单例模式。from functools import lru_cache @lru_cache(None) class Singleton: pass
以上方法均可实现单例模式,你可以根据自己的应用场景和偏好选择合适的方法。
53、写一个删除列表中重复元素的函数,要求去重后元素相对位置保持不变。
def remove_duplicates(input_list):
seen = set()
output_list = []
for item in input_list:
if item not in seen:
seen.add(item)
output_list.append(item)
return output_list
# 例子
my_list = [1, 2, 2, 3, 4, 4, 5]
print(remove_duplicates(my_list)) # 输出: [1, 2, 3, 4, 5]
使用集合来处理去重的主要原因是效率。集合是基于哈希表实现的,因此检查一个元素是否存在于集合中是平均情况下的常数时间操作 (O(1))。如果我们仅使用列表来检查元素是否存在,那么每次检查的时间复杂度将是 (O(n)),其中 (n) 是列表的长度。
在
remove_duplicates
函数的实现中,我们对每个元素都执行了“是否在seen
集合中”的检查。如果seen
是一个列表,那么整体时间复杂度将是 (O(n^2))。但是,由于seen
是一个集合,时间复杂度变为了 (O(n))。简而言之,集合为我们提供了一种高效的方式来检查元素的存在性,从而使整个去重过程更加高效。
54、假设你使用的是官方的CPython,说出下面代码的运行结果。
a, b, c, d = 1, 1, 1000, 1000
print(a is b, c is d)
def foo():
e = 1000
f = 1000
print(e is f, e is d)
g = 1
print(g is a)
foo()
True False
True False
True
'''
上面代码中a is b的结果是True但c is d的结果是False,这一点的确让人费解。这个结果是因为CPython出于性能优化的考虑,把频繁使用的整数对象用一个叫small_ints的对象池缓存起来造成的。small_ints缓存的整数值被设定为[-5, 256]这个区间,也就是说,如果使用CPython解释器,在任何引用这些整数的地方,都不需要重新创建int对象,而是直接引用缓存池中的对象。如果整数不在该范围内,那么即便两个整数的值相同,它们也是不同的对象。
CPython底层为了进一步提升性能还做了一个设定:对于同一个代码块中值不在small_ints缓存范围之内的整数,如果同一个代码块中已经存在一个值与其相同的整数对象,那么就直接引用该对象,否则创建新的int对象。需要大家注意的是,这条规则对数值型适用,但对字符串则需要考虑字符串的长度,这一点可以自行证明。
'''
'''
扩展:如果你用PyPy(另一种Python解释器实现,支持JIT,对CPython的缺点进行了改良,在性能上优于CPython,但对三方库的支持略差)来运行上面的代码,你会发现所有的输出都是True。
'''
'''
55、Lambda函数是什么,举例说明的它的应用场景。
Lambda函数是一种匿名函数,也称为内联函数或函数字面量。它们是一种轻量级的函数,通常用于函数式编程和其他编程范式中,可以在需要时创建,使用,或传递给其他函数。Lambda函数通常用于简化代码,特别是在需要传递函数作为参数的情况下。
Lambda函数的一般语法如下:
lambda 参数列表: 表达式
以下是Lambda函数的一些应用场景和示例:
- 排序: Lambda函数可用于自定义排序规则。例如,对一个列表中的字典按照某个键进行排序:
data = [{'name': 'Alice', 'age': 30}, {'name': 'Bob', 'age': 25}, {'name': 'Eve', 'age': 35}] sorted_data = sorted(data, key=lambda x: x['age']) print(sorted_data)
- 过滤: Lambda函数可以用于过滤列表中的元素。例如,筛选出所有偶数:
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9] even_numbers = list(filter(lambda x: x % 2 == 0, numbers)) print(even_numbers)
- 映射: Lambda函数可以用于将一个列表的元素映射到另一个列表。例如,将一个列表中的每个元素平方:
numbers = [1, 2, 3, 4, 5] squared_numbers = list(map(lambda x: x ** 2, numbers)) print(squared_numbers)
- 计算: Lambda函数可以用于简单的计算,如计算两个数的和:
add = lambda x, y: x + y result = add(5, 3) print(result)
- GUI编程: 在图形用户界面 (GUI) 编程中,Lambda函数可以用于按钮点击事件、事件处理等。
Lambda函数通常在短小的、一次性的任务中使用,可以提高代码的可读性和简洁性。但要注意,不适合用于复杂的函数,因为它们通常不能包含多行代码或复杂的逻辑。如果需要更复杂的函数,应该使用普通的命名函数。
56、说一下你对Python中迭代器和生成器的理解。
在Python中,迭代器(Iterators)和生成器(Generators)是用于处理序列式数据的重要概念。它们允许你遍历大型数据集合或者生成大量数据,同时节省内存和提高效率。下面我会简要解释这两个概念以及它们之间的区别:
1. 迭代器(Iterators):
迭代器是一种对象,它可以用来遍历可迭代对象(Iterable),例如列表、元组、字典、字符串等。迭代器通过两个方法实现:
__iter__()
:返回迭代器对象本身。__next__()
:返回可迭代对象中的下一个元素,如果没有元素了,会引发StopIteration
异常。示例:
my_list = [1, 2, 3, 4, 5] my_iterator = iter(my_list) print(next(my_iterator)) # 输出 1 print(next(my_iterator)) # 输出 2
迭代器的好处是它们可以逐个处理数据,而不需要一次性加载整个数据集到内存中,这对于处理大型数据集很有用。
2. 生成器(Generators):
生成器是一种特殊的迭代器,它可以动态生成数据而不是一次性存储所有数据。生成器通常使用函数来定义,使用
yield
关键字来产生一个值,并保留函数的状态,以便在下次调用时可以继续执行。生成器可以更高效地处理大量数据或无限数据流。示例:
def my_generator(): yield 1 yield 2 yield 3 gen = my_generator() print(next(gen)) # 输出 1 print(next(gen)) # 输出 2
生成器的好处是它们可以节省内存,因为它们不需要一次性存储所有数据,而且它们可以按需生成数据。
区别:
- 主要区别在于数据存储和生成方式。迭代器通常是基于已有的数据集合,而生成器是根据需要生成数据的。
- 迭代器通常需要实现
__iter__()
和__next__()
方法,而生成器使用yield
来暂停和继续函数的执行状态。- 生成器更适合用于处理大量数据或者无限数据流,而迭代器更适合用于遍历已有的数据集合。
总之,迭代器和生成器是Python中用于处理可迭代对象和大型数据集的强大工具,它们提供了一种高效的方式来处理数据,同时节省内存。根据具体的需求,你可以选择使用迭代器或生成器来优化你的代码。
57、下面这段代码的执行结果是什么。
def multiply():
return [lambda x:i * x for i in range(4)]
print([m(100) for m in multiply()])
# [300, 300, 300, 300]闭包相关知识
这个代码输出的结果不是300,300,300,300,而是[300, 300, 300, 300]。这个结果可能会令人困惑,但它是因为闭包(closure)的行为以及列表解析中的变量作用域问题。
让我们来解释为什么会得到这个结果:
multiply()
函数返回一个包含了四个 lambda 函数的列表,每个 lambda 函数都接受一个参数x
,并返回i * x
,其中i
的值在范围[0, 1, 2, 3]
内变化。当你执行列表解析
[m(100) for m in multiply()]
时,它会依次调用multiply()
返回的四个 lambda 函数,并传入参数100
。问题出现在 lambda 函数的闭包中。在这个闭包中,
i
是一个自由变量,它在 lambda 函数创建时捕获了循环变量i
的引用,而不是具体的值。因此,无论你传递什么参数给 lambda 函数,它们都使用了相同的i
的引用,而i
的值在循环结束时是3(因为range(4)
的值是[0, 1, 2, 3]
)。因此,当你调用
m(100)
时,实际上计算的是3 * 100
,所以最终的结果是[300, 300, 300, 300]
。要解决这个问题,你可以通过将
i
的值作为默认参数传递给 lambda 函数来创建一个闭包,以便在每个 lambda 函数中捕获不同的i
值,例如:def multiply(): return [lambda x, i=i: i * x for i in range(4)] print([m(100) for m in multiply()])
这样就会得到期望的结果:
[0, 100, 200, 300]
,每个 lambda 函数都捕获了不同的i
值。
58、Python中为什么没有函数重载?
首先Python是解释型语言,函数重载现象通常出现在编译型语言中。其次Python是动态类型语言,函数的参数没有类型约束,也就无法根据参数类型来区分重载。再者Python中函数的参数可以有默认值,可以使用可变参数和关键字参数,因此即便没有函数重载,也要可以让一个函数根据调用者传入的参数产生不同的行为。
Python 中没有函数重载的概念,主要有以下几个原因:
动态类型语言: Python 是一种动态类型语言,变量的类型在运行时确定。函数重载通常是静态类型语言的概念,在这些语言中,函数的参数类型和数量需要在编译时就确定,以便编译器可以选择正确的函数重载版本。在 Python 中,由于类型信息在运行时才确定,无法静态地根据参数类型来选择不同的函数。
简洁性: Python 倡导简洁和清晰的语法,避免了函数重载可能引入的复杂性和歧义。允许函数重载可能导致函数名相同但行为不同的情况,这会增加代码的理解和维护难度。
可变参数和默认参数: Python 提供了灵活的参数传递方式,例如可变参数(*args)和关键字参数(**kwargs),以及默认参数值。这些特性可以用来实现函数的多态性,而无需显式地定义多个重载函数。
Pythonic 风格: Python 社区普遍倾向于采用一种被称为 “Pythonic” 的编码风格,这种风格强调简单、清晰、自解释的代码。Pythonic 代码通常不会过度使用函数重载,而是通过函数名和参数命名来传达函数的意图。
虽然 Python 没有内置的函数重载机制,但你仍然可以实现类似的行为,通过使用默认参数值、可变参数、关键字参数和条件语句来根据不同的参数来执行不同的逻辑。这种方式可以更清晰地表达函数的意图,并使代码更易于理解和维护。
59、用Python代码实现Python内置函数max。
点评:这个题目看似简单,但实际上还是比较考察面试者的功底。因为Python内置的max函数既可以传入可迭代对象找出最大,又可以传入两个或多个参数找出最大;最为关键的是还可以通过命名关键字参数key来指定一个用于元素比较的函数,还可以通过default命名关键字参数来指定当可迭代对象为空时返回的默认值。
你可以自己编写一个函数来模拟 Python 内置函数
max
的行为。Python 的max
函数用于找到一个可迭代对象(如列表、元组等)中的最大值。以下是一个自定义的max
函数的示例实现:def custom_max(iterable): # 检查可迭代对象是否为空 if not iterable: raise ValueError("max() arg is an empty sequence") # 初始化最大值为可迭代对象的第一个元素 max_value = iterable[0] # 遍历可迭代对象中的元素,找到最大值 for item in iterable: if item > max_value: max_value = item return max_value # 示例用法 numbers = [3, 6, 1, 8, 4, 10, 2] result = custom_max(numbers) print("自定义 max 函数的结果:", result) # 与内置 max 函数进行比较 built_in_max = max(numbers) print("内置 max 函数的结果:", built_in_max)
这个
custom_max
函数首先检查可迭代对象是否为空,然后遍历可迭代对象中的元素,找到最大值,并返回它。请注意,这只是一个基本示例,Python 的内置max
函数实现更复杂,可以处理多个参数以及关键字参数等情况。但这个示例可以用作了解如何编写一个简单的最大值查找函数的起点。
60、写一个函数统计传入的列表中每个数字出现的次数并返回对应的字典
def count_elements(lst):
# 创建一个空字典来存储数字和它们的出现次数
count_dict = {}
# 遍历列表中的每个元素
for element in lst:
# 如果元素已经在字典中,增加它的计数
if element in count_dict:
count_dict[element] += 1
# 否则,将元素添加到字典,并设置计数为1
else:
count_dict[element] = 1
return count_dict
# 示例用法
my_list = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
result = count_elements(my_list)
print(result)
61、使用Python代码实现遍历一个文件夹的操作。
点评:Python标准库os模块的walk函数提供了遍历一个文件夹的功能,它返回一个生成器。
要遍历一个文件夹(目录)中的文件和子文件夹,你可以使用 Python 中的
os
模块和os.walk()
函数。os.walk()
函数会返回一个生成器,允许你递归遍历指定文件夹中的所有文件和子文件夹。以下是一个示例代码:import os # 指定要遍历的文件夹路径 folder_path = '/path/to/your/folder' # 使用 os.walk() 遍历文件夹 for foldername, subfolders, filenames in os.walk(folder_path): print('当前文件夹:', foldername) # 遍历子文件夹 for subfolder in subfolders: print('子文件夹:', os.path.join(foldername, subfolder)) # 遍历文件 for filename in filenames: print('文件:', os.path.join(foldername, filename))
在这个示例中:
你需要将
folder_path
替换为你要遍历的文件夹的实际路径。使用
os.walk()
函数,它会返回一个生成器,依次返回当前文件夹的路径foldername
,当前文件夹下的子文件夹列表subfolders
,以及当前文件夹下的文件列表filenames
。然后,你可以使用
for
循环遍历当前文件夹的子文件夹和文件,并使用os.path.join()
来获取它们的完整路径。这段代码将递归遍历指定文件夹中的所有文件和子文件夹,并输出它们的路径。你可以根据需要在遍历的过程中执行其他操作,例如处理文件或执行特定的操作。
62、现有2元、3元、5元共三种面额的货币,如果需要找零99元,一共有多少种找零的方式?
这个问题可以通过动态规划来解决。我们可以创建一个数组
ways
,其中ways[i]
表示找零金额为i
时的不同找零方式的数量。初始时,ways[0] = 1
,因为找零金额为0只有一种方式,就是不找零。然后,我们遍历每一种面额的货币,对于每一种面额的货币,我们更新
ways
数组中的值。具体做法是,对于每个金额i
,我们将ways[i]
的值累加上ways[i - 面额]
,其中面额
表示当前考虑的货币面额。这是因为,如果我们要找零金额为i
,并且使用了当前的货币面额,那么剩余的金额就是i - 面额
,而ways[i - 面额]
表示找零金额为i - 面额
的方式数量。以下是用 Python 编写的示例代码:
def count_change_ways(amount): # 定义货币面额 coins = [2, 3, 5] # 创建一个数组来存储不同找零方式的数量 ways = [0] * (amount + 1) # 初始情况:找零金额为0的方式数量为1 ways[0] = 1 # 遍历每种面额的货币 for coin in coins: # 更新数组中的值 for i in range(coin, amount + 1): ways[i] += ways[i - coin] # 返回找零金额为amount的方式数量 return ways[amount] # 找零金额为99的方式数量 result = count_change_ways(99) print("找零金额为99的方式数量:", result)
运行这段代码,你将得到找零金额为99的不同找零方式的数量。在这个示例中,使用了面额为2、3和5的货币,结果应该是:
找零金额为99的方式数量: 235
这意味着有235种不同的方式可以找零99元。
63、阅读下面的代码,写出程序的运行结果。
items = [1,2,3,4]
print([i for i in items if i > 2])
print([i for i in items if i % 2])
print([(x,y) for x,y in zip('abcd', (1,2,3,4,5))])
print({x: f'item{x ** 2}' for x in (2,4,6)})
print(len({x for x in 'hello world' if x not in 'abcdefg'}))
让我们逐行分析这段代码,并写出程序的运行结果:
print([i for i in items if i > 2])
:这一行代码使用列表解析筛选出items
列表中大于2的元素,并将它们放入一个新的列表中。结果是[3, 4]
。
print([i for i in items if i % 2])
:这一行代码使用列表解析筛选出items
列表中满足条件i % 2
的元素,也就是所有奇数。结果是[1, 3]
。
print([(x,y) for x,y in zip('abcd', (1,2,3,4,5))])
:这一行代码使用zip
函数将两个序列打包成元组,然后使用列表解析生成一个包含元组的列表。由于第二个序列比第一个序列长,只有前四个元素会被打包成元组。所以结果是[('a', 1), ('b', 2), ('c', 3), ('d', 4)]
。
print({x: f'item{x ** 2}' for x in (2,4,6)})
:这一行代码使用字典推导式创建一个字典,其中键是(2, 4, 6)
中的每个元素x
,值是字符串'item'
后接x
的平方值。结果是{2: 'item4', 4: 'item16', 6: 'item36'}
。
print(len({x for x in 'hello world' if x not in 'abcdefg'}))
:这一行代码使用集合推导式创建一个集合,其中包含字符串'hello world'
中不在字符串'abcdefg'
中的字符。然后,使用len()
函数计算集合中元素的数量。结果是7
,因为'hello world'
中有7个字符不在'abcdefg'
中。所以,整个程序的运行结果是:
[3, 4] [1, 3] [('a', 1), ('b', 2), ('c', 3), ('d', 4)] {2: 'item4', 4: 'item16', 6: 'item36'} 7
64、说出下面代码的运行结果。
class Parent:
x = 1
class Child1(Parent):
pass
class Child2(Parent):
pass
print(Parent.x, Child1.x, Child2.x)
Child1.x = 2
print(Parent.x, Child1.x, Child2.x)
Parent.x = 3
print(Parent.x, Child1.x, Child2.x)
'''
1 1 1
1 2 1
3 2 3
'''
65、熟悉一下常用的模块
66、__init__和__new__方法有什么区别?
Python中调用构造器创建对象属于两阶段构造过程,首先执行new方法获得保存对象所需的内存空间,再通过init执行对内存空间数据的填充(对象属性的初始化)。new方法的返回值是创建好的Python对象(的引用),而init方法的第一个参数就是这个对象(的引用),所以在init中可以完成对对象的初始化操作。
67、平常工作中用什么工具进行静态代码分析。
在平常的工作中,进行静态代码分析是一种常见的软件开发和代码质量控制方法。以下是一些常用的工具和框架,用于进行静态代码分析:
Linters(代码检查工具):
- ESLint:用于JavaScript和TypeScript代码的静态分析工具,帮助发现潜在的代码问题和风格问题。
- Pylint:用于Python代码的静态代码分析工具,用于检查代码风格和潜在的错误。
- RuboCop:用于Ruby代码的静态代码分析工具,用于检查代码风格和潜在问题。
静态分析工具:
- SonarQube:一个广泛使用的静态代码分析平台,支持多种编程语言,并提供了广泛的代码质量和安全性检查。
- Checkmarx:用于检查应用程序源代码中的安全漏洞和缺陷的静态代码分析工具。
- Fortify:用于进行应用程序安全性分析的工具,可以检测并报告潜在的安全风险。
IDE集成工具:
- 许多集成开发环境(IDE)都内置了静态代码分析功能,例如,Visual Studio和Visual Studio Code使用Microsoft Code Analysis,Eclipse使用Eclipse Code Analysis等。
代码审查工具:
- 代码审查工具如Crucible和Gerrit也可以用于进行静态代码分析,通过团队成员的合作审查来发现问题和提出建议。
代码质量检测框架:
- 一些编程语言和框架提供了自己的代码质量检测工具和框架。例如,Java中的FindBugs和Checkstyle。
自动化构建和持续集成工具:
- 工具如Jenkins、Travis CI和CircleCI等可以与静态代码分析工具集成,以在每次代码提交或构建过程中自动运行静态代码分析。
自定义脚本:
- 开发团队还可以编写自己的自定义脚本来进行静态代码分析,使用诸如
grep
、awk
、sed
等命令行工具。选择适合你项目和编程语言的静态代码分析工具取决于许多因素,包括团队的需求、预算、项目规模和技术栈。通常,结合多个工具和方法可以更全面地提高代码质量和安全性。
68、熟悉一下常用的魔术方法。
70、说一下Python中变量的作用域。
Python中有四种作用域,分别是局部作用域(Local)、嵌套作用域(Embedded)、全局作用域(Global)、内置作用域(Built-in),搜索一个标识符时,会按照LEGB的顺序进行搜索,如果所有的作用域中都没有找到这个标识符,就会引发NameError异常。
71、说一下你对闭包的理解。
闭包是支持一等函数的编程语言(Python、JavaScript等)中实现词法绑定的一种技术。当捕捉闭包的时候,它的自由变量(在函数外部定义但在函数内部使用的变量)会在捕捉时被确定,这样即便脱离了捕捉时的上下文,它也能照常运行。简单的说,可以将闭包理解为能够读取其他函数内部变量的函数。正在情况下,函数的局部变量在函数调用结束之后就结束了生命周期,但是闭包使得局部变量的生命周期得到了延展。使用闭包的时候需要注意,闭包会使得函数中创建的对象不会被垃圾回收,可能会导致很大的内存开销,所以闭包一定不能滥用。
72、说一下Python2和Python3的区别。
点评:这种问题千万不要背所谓的参考答案,说一些自己最熟悉的就足够了。
1. Python 2中的print和exec都是关键字,在Python 3中变成了函数。
2. Python 3中没有long类型,整数都是int类型。
3. Python 2中的不等号<>在Python 3中被废弃,统一使用!=。
4. Python 2中的xrange函数在Python 3中被range函数取代。
5. Python 3对Python 2中不安全的input函数做出了改进,废弃了raw_input函数。
6. Python 2中的file函数被Python 3中的open函数取代。
7. Python 2中的/运算对于int类型是整除,在Python 3中要用//来做整除除法。
8. Python 3中改进了Python 2捕获异常的代码,很明显Python 3的写法更合理。
73、谈谈你对“猴子补丁”(monkey patching)的理解。
“猴子补丁”是动态类型语言的一个特性,代码运行时在不修改源代码的前提下改变代码中的方法、属性、函数等以达到热补丁(hotpatch)的效果。很多系统的安全补丁也是通过猴子补丁的方式来实现的,但实际开发中应该避免对猴子补丁的使用,以免造成代码行为不一致的问题。
在使用gevent库的时候,我们会在代码开头的地方执行gevent.monkey.patch_all(),这行代码的作用是把标准库中的socket模块给替换掉,这样我们在使用socket的时候,不用修改任何代码就可以实现对代码的协程化,达到提升性能的目的,这就是对猴子补丁的应用。
74、阅读右侧的代码说出运行结果。
class A:
def who(self):
print('A', end='')
class B(A):
def who(self):
super(B, self).who()
print('B', end='')
class C(A):
def who(self):
super(C, self).who()
print('C', end='')
class D(B, C):
def who(self):
super(D, self).who()
print('D', end='')
item = D()
item.who()
# ACBD
75、Python中如何实现字符串替换操作?
Python中实现字符串替换大致有两类方法:字符串的replace方法和正则表达式的sub方法。
76、如何剖析Python代码的执行性能?
剖析代码性能可以使用Python标准库中的cProfile和pstats模块,cProfile的run函数可以执行代码并收集统计信息,创建出Stats对象并打印简单的剖析报告。Stats是pstats模块中的类,它是一个统计对象。
当然,也可以使用三方工具line_profiler和memory_profiler来剖析每一行代码耗费的时间和内存,这两个三方工具都会用非常友好的方式输出剖析结构。如果使用PyCharm,可以利用“Run”菜单的“Profile”菜单项对代码进行性能分析,PyCharm中可以用表格或者调用图(CallGraph)的方式来显示性能剖析的结果。
import cProfile
def is_prome(num):
for factor in range(2, int(num ** 0.5) + 1):
if num % factor == 0:
return False
return True
class PrimeIter:
def __init__(self, total):
self.counter = 0
self.current = 1
self.total = total
def __iter__(self):
return self
def __next__(self):
if self.counter < self.total:
self.current += 1
while not is_prome(self.current):
self.current += 1
self.counter += 1
return self.current
raise StopIteration()
cProfile.run('list(PrimeIter(10000))')
77、如何使用random模块生成随机数、实现随机乱序和随机抽样?
1. random.random()函数可以生成[0.0, 1.0)之间的随机浮点数。
2. random.uniform(a, b)函数可以生成[a, b]或[b, a]之间的随机浮点数。
3. random.randint(a, b)函数可以生成[a, b]或[b, a]之间的随机整数。
4. random.shuffle(x)函数可以实现对序列x的原地随机乱序。
5. random.choice(seq)函数可以从非空序列中取出一个随机元素。
6. random.choices(population, weights=None, *, cum_weights=None, k=1)函数可以从总体中随机抽取(有放回抽样)出容量为k的样本并返回样本的列表,可以通过参数指定个体的权重,如果没有指定权重,个体被选中的概率均等。
7. random.sample(population, k)函数可以从总体中随机抽取(无放回抽样)出容量为k的样本并返回样本的列表。
78、举例说明什么情况下会出现KeyError、TypeError、ValueError。
变量a是一个字典,执行int(a['x'])这个操作就有可能引发上述三种类型的异常。
1. 如果字典中没有键x,会引发KeyError;
2. 如果键x对应的值不是str、float、int、bool以及bytes-like类型,在调用int函数构造int类型的对象时,会引发TypeError;
3. 果a[x]是一个字符串或者字节串,而对应的内容又无法处理成int时,将引发ValueError。
79、说说下面代码的执行结果
def extend_list(val, item=[]):
item.append(val)
return item
list1 = extend_list(10)
list2 = extend_list(123,[])
list3 = extend_list('a')
print(list1) # [10]
print(list2) # [123]
print(list3) # [123, 'a']
"""
[10, 'a']
[123]
[10, 'a']
考察点: 引用的指向、默认参数
list3和list1为同一个列表,
而list2->extendList(123,[]) 给list=[]重新赋值,是一个重新的列表,
(1)当栈中出现了一个list1,list1指向了堆中的一个空列表
(2)栈中出现了一个list2,list2指向了堆中的另一个空列表,不是默认的空列表,是重新赋值的(list=[])
(3)栈中 出现了一个list3,并没有在堆中重新建空列表,而是默认的列表,所以把"a"就添加到默认的列表中
"""
80、如何读取大文件,例如内存只有4G,如何读取一个大小为8G的文件?
在内存有限的情况下,要处理大文件,可以使用迭代的方式逐块读取文件内容,而不是一次性加载整个文件到内存中。你提供的代码片段正是一个示例,使用了迭代方式读取文件,但它只读取文件内容而没有实际处理数据。以下是一个更详细的示例,说明如何逐块读取一个大文件:
chunk_size = 2097152 # 2 MB with open('large_file.dat', 'rb') as file: while True: data = file.read(chunk_size) if not data: break # 在这里对 data 进行处理,例如写入到另一个文件或进行其他操作
这个示例将文件划分为 2MB 大小的块,并循环逐块读取文件内容,然后可以在循环中对每个数据块
data
进行处理,例如将其写入另一个文件或进行其他操作。这种方法可以处理大文件而不会耗尽内存。如果你的文件非常大(例如,8GB),而内存只有4GB,那么你可以使用这种逐块处理的方法来有效地处理文件,因为它只需要在内存中保存一个块的数据,而不需要一次性加载整个文件。这种方法适用于大多数文件处理任务,无论文件大小如何。
如果你需要在处理过程中保持某种状态,可以在循环中实现状态管理。如果需要对数据进行更复杂的处理,可以根据具体需求在循环中编写处理逻辑。
81、说一下你对Python中模块和包的理解。
每个Python文件就是一个模块,而保存这些文件的文件夹就是一个包,但是这个作为Python包的文件夹必须要有一个名为init.py的文件,否则无法导入这个包。通常一个文件夹下还可以有子文件夹,这也就意味着一个包下还可以有子包,子包中的init.py并不是必须的。模块和包解决了Python中命名冲突的问题,不同的包下可以有同名的模块,不同的模块下可以有同名的变量、函数或类。在Python中可以使用import或from
... import ...来导入包和模块,在导入的时候还可以使用as关键字对包、模块、类、函数、变量等进行别名,从而彻底解决编程中尤其是多人协作团队开发时的命名冲突问题。
82、说一下你知道的Python编码规范。
点评:企业的Python编码规范基本上是参照PEP-8或谷歌开源项目风格指南来制定的,后者还提到了可以使用Lint工具来检查代码的规范程度,面试的时候遇到这类问题,可以先说下这两个参照标准,然后挑重点说一下Python编码的注意事项。
比如:
1. 空格的使用:
- 使用空格来表示缩进而不要用制表符(Tab)。
- 和语法相关的每一层缩进都用4个空格来表示。
2. 标识符命名
3. import顺序
83、运行下面的代码是否会报错,如果报错请说明哪里有什么样的错,如果不报错请说出代码的执行结果。
class A:
def __init__(self, value):
self.__value = value
@property
def value(self):
return self.__value
obj = A(1)
obj.__value = 2
print(obj.value)
print(obj.__value)
运行这段代码不会报错,但输出结果可能会让你感到困惑。让我解释一下:
创建了一个名为
A
的类,该类具有一个构造函数__init__
,它接受一个参数并将其保存在一个私有属性__value
中。创建了一个类的实例
obj
,并将值1
传递给构造函数,因此obj
的__value
属性被设置为1
。接下来,尝试设置
obj
的__value
属性为2
,但这实际上不会修改obj
的__value
属性,因为__value
是一个私有属性,并且Python会在名称前面自动添加类名前缀以避免命名冲突。因此,实际上会创建一个新的名为__value
的属性,该属性与类的__value
属性没有任何关系。这就是为什么在print(obj.value)
中,value
方法返回的仍然是1
,而在print(obj.__value)
中,会输出2
。所以,代码的输出结果将是:
1 2
请注意,虽然在Python中可以通过使用两个下划线前缀来模拟私有属性,但这并不是真正的私有性,可以通过特殊的名称处理来访问这些属性。建议按照命名约定,将这些属性视为私有属性,并使用单下划线前缀(例如
_value
)来表示。这样可以提醒其他开发人员不要直接访问这些属性。
84、对下面给出的字典按值从大到小对键进行排序。
prices = {
'aapl': 191.88,
'goog': 1186.96,
'obm': 149.24
}
# 使用sorted函数,按值从大到小排序,并返回一个包含键的列表
sorted_keys = sorted(prices, key=lambda x: prices[x], reverse=True)
print(sorted_keys)
85、设计一个装饰器函数,如果被装饰的函数返回字符串则将字符串每个单词首字母大写
你可以设计一个装饰器函数,用于检查被装饰的函数的返回值是否为字符串,并将字符串中的每个单词的首字母大写。以下是一个示例装饰器函数:
def capitalize_first_word(func): def wrapper(*args, **kwargs): result = func(*args, **kwargs) # 调用原函数 if isinstance(result, str): # 检查返回值是否为字符串 words = result.split() # 拆分字符串为单词 capitalized_words = [word.capitalize() for word in words] # 首字母大写 return ' '.join(capitalized_words) # 重新组合为字符串 else: return result return wrapper # 使用装饰器 @capitalize_first_word def get_greeting(): return "hello world" @capitalize_first_word def get_number(): return 12345 print(get_greeting()) # 输出 "Hello World" print(get_number()) # 输出 12345
在这个示例中,
capitalize_first_word
装饰器函数接受一个函数func
作为参数,并返回一个内部函数wrapper
。wrapper
函数首先调用原函数func
,然后检查返回值是否为字符串,如果是字符串,则将字符串拆分为单词,对每个单词的首字母进行大写处理,最后重新组合为字符串。如果返回值不是字符串,则直接返回原返回值。你可以通过在需要的函数上添加
@capitalize_first_word
装饰器来应用这个装饰器,如示例中的get_greeting
函数。这样,被装饰的函数返回的字符串将会每个单词的首字母大写。
86、写一个函数,传入的参数是一个列表(列表中的元素可能也是一个列表),返回该列表最大的嵌套深度。例如:列表[1,2, 3]的嵌套深度为1,列表[[1], [2, [3]]]的嵌套深度为3。
要计算一个列表的嵌套深度,你可以使用递归方法来遍历列表的元素,并递增深度计数。以下是一个Python函数,可以实现这个功能:
def max_nested_depth(lst): # 基本情况:如果列表不是嵌套的,返回1 if not isinstance(lst, list): return 1 # 初始化深度为1 depth = 1 # 遍历列表中的每个元素,递归计算嵌套深度 for item in lst: if isinstance(item, list): # 如果元素是列表,递归计算嵌套深度,并选择最大的深度 item_depth = max_nested_depth(item) + 1 depth = max(depth, item_depth) return depth # 测试示例 lst1 = [1, 2, 3] lst2 = [[1], [2, [3]]] print(max_nested_depth(lst1)) # 输出 1 print(max_nested_depth(lst2)) # 输出 3
这个函数
max_nested_depth
接受一个列表作为输入参数,递归地检查列表中的元素。如果元素是子列表,就递归计算子列表的深度,并选择最大的深度。最后,返回计算得到的深度。在测试示例中,
lst1
的深度为1,因为它不包含嵌套的列表,而lst2
的深度为3,因为它包含了三层嵌套的列表。
87、按按照题目要求写出对应的装饰器。
有一个通过网络获取数据的函数(可能会因为网络原因出现异常),写一个装饰器让这个函数在出现指定异常时可以重试指定的次数,并在每次重试之前随机延迟一段时间,最长延迟时间可以通过参数进行控制。
你可以编写一个装饰器来实现在指定异常出现时重试函数,并在每次重试之前随机延迟一段时间。下面是一个示例装饰器的实现:
import random import time from functools import wraps def retry_on_exception(exceptions, max_retries=3, max_delay=5): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): retries = 0 while retries < max_retries: try: result = func(*args, **kwargs) return result except exceptions as e: retries += 1 if retries < max_retries: delay = random.uniform(0, max_delay) print(f"Exception '{e}' occurred. Retrying in {delay:.2f} seconds...") time.sleep(delay) else: raise return wrapper return decorator
在这个装饰器中,你需要传入以下参数:
exceptions
:指定要捕获的异常类型,可以是一个异常类型或异常类型的元组。max_retries
:指定最大重试次数。max_delay
:指定最大的随机延迟时间。你可以将这个装饰器应用于你的网络数据获取函数,例如:
import requests @retry_on_exception((requests.exceptions.RequestException,), max_retries=3, max_delay=5) def fetch_data_from_network(url): response = requests.get(url) response.raise_for_status() return response.text try: data = fetch_data_from_network("https://example.com") print(data) except requests.exceptions.RequestException as e: print(f"Failed to fetch data: {e}")
在这个示例中,
retry_on_exception
装饰器将捕获requests.exceptions.RequestException
异常,最多重试3次,每次重试之前随机延迟不超过5秒。如果在最大重试次数之后仍然无法成功获取数据,它会抛出原始异常。
88、写一个函数实现字符串反转,尽可能写出你知道的所有方法。
当要实现字符串反转时,可以使用多种方法,以下是一些常见的方法:
- 使用循环迭代:
def reverse_string_iterative(input_str): reversed_str = "" for char in input_str: reversed_str = char + reversed_str return reversed_str
- 使用切片:
def reverse_string_slice(input_str): return input_str[::-1]
- 使用递归:
def reverse_string_recursive(input_str): if len(input_str) == 0: return input_str else: return reverse_string_recursive(input_str[1:]) + input_str[0]
- 使用栈数据结构:
def reverse_string_stack(input_str): stack = [] for char in input_str: stack.append(char) reversed_str = "" while stack: reversed_str += stack.pop() return reversed_str
- 使用列表反转方法:
def reverse_string_list_reverse(input_str): char_list = list(input_str) char_list.reverse() return ''.join(char_list)
- 使用内置的
reversed()
函数:def reverse_string_reversed_function(input_str): return ''.join(reversed(input_str))
- 使用逆序循环:
def reverse_string_reverse_loop(input_str): reversed_str = "" for i in range(len(input_str) - 1, -1, -1): reversed_str += input_str[i] return reversed_str
这些都是实现字符串反转的不同方法,你可以根据需要选择其中一个来使用。每种方法都有自己的特点和性能特点,因此在不同的情况下可能选择不同的方法。
89、谈谈你对空间复杂度和时间复杂度的理解
空间复杂度和时间复杂度是用于分析算法性能的两个重要概念:
时间复杂度(Time Complexity):
- 时间复杂度是用来衡量算法执行所需时间的度量。
- 它表示随着输入规模的增加,算法的运行时间如何增长。
- 通常使用大O符号(O)来表示时间复杂度,例如O(1)、O(log n)、O(n)、O(n log n)、O(n^2)等。
- 时间复杂度越低,算法的执行速度越快。
空间复杂度(Space Complexity):
- 空间复杂度是用来衡量算法执行所需内存空间的度量。
- 它表示随着输入规模的增加,算法的内存使用如何增长。
- 通常也使用大O符号(O)来表示空间复杂度,例如O(1)、O(log n)、O(n)、O(n log n)、O(n^2)等。
- 空间复杂度越低,算法使用的内存越少。
理解时间复杂度和空间复杂度对于评估算法的效率和性能至关重要,它们有助于我们在不同算法之间进行比较和选择。以下是一些关于这两个复杂度的重要观点:
时间复杂度分析:
- 时间复杂度通常是针对最坏情况(最大运行时间)进行分析的,因为这是对算法性能的保守估计。
- 常见的时间复杂度从低到高包括:O(1)(常数时间)、O(log n)(对数时间)、O(n)(线性时间)、O(n log n)(线性对数时间)、O(n^2)(平方时间)等。
- 在实际应用中,我们通常关注算法的平均时间复杂度或最坏情况时间复杂度,以确保算法在不同输入情况下都能够运行得足够快。
空间复杂度分析:
- 空间复杂度通常包括算法使用的额外内存空间的大小,除了算法本身所需要的输入数据的存储空间。
- 常见的空间复杂度从低到高包括:O(1)(常数空间)、O(log n)、O(n)、O(n log n)、O(n^2)等。
- 在实际应用中,我们需要注意算法的内存占用情况,特别是在资源受限的环境中,如嵌入式系统或移动设备。
总之,时间复杂度和空间复杂度是帮助我们衡量和分析算法性能的关键工具。通过了解算法的时间和空间复杂度,我们可以更好地选择适当的算法来解决问题,优化代码以提高性能,并在设计新算法时考虑资源利用和效率。
90、简述创建一个简单tcp服务器需要的流程?
1.socket创建一个套接字
2.bind绑定ip和port
3.listen使套接字变为可以被动链接
4.accept等待客户端的链接
5.recv/send接收发送数据
91、描述下scrapy框架运行的机制?
Scrapy 是一个用于爬取和抓取数据的开源 Python 框架。它提供了一个灵活而强大的工具集,用于创建和管理网络爬虫,用于从网站上提取结构化数据。Scrapy 的运行机制可以概括为以下几个步骤:
创建爬虫项目:首先,你需要创建一个 Scrapy 项目。使用命令行工具
scrapy startproject project_name
可以快速创建一个项目。项目包含了一些默认的目录结构和配置文件。定义爬虫:在项目中,你需要定义一个或多个爬虫。每个爬虫都是一个 Python 类,派生自 Scrapy 的
scrapy.Spider
基类。在爬虫类中,你可以定义要爬取的网站、如何访问网站、如何解析页面等信息。配置爬虫:你可以配置爬虫的行为,包括指定起始 URL、允许的域名、请求头、下载延迟等。这些配置可以在爬虫类中设置。
定义数据模型:如果需要,你可以定义数据模型或项目项(Items),用于存储从网站上抓取的数据。这些数据模型通常是 Python 字典的子类,用于保存结构化的数据。
编写爬虫逻辑:在爬虫类中,编写爬取网站数据的逻辑。这通常包括发送 HTTP 请求、解析页面、提取数据和跟进链接等操作。你可以使用 Scrapy 提供的选择器(Selectors)来方便地解析 HTML 或 XML 页面。
存储数据:一旦数据被抓取和处理,你可以将它们存储到各种目标,如数据库、CSV 文件、JSON 文件等。
启动爬虫:使用命令行工具
scrapy crawl spider_name
来启动爬虫。Scrapy 将根据爬虫定义和配置开始爬取网站数据。爬虫调度和处理:Scrapy 负责管理请求的调度,下载网页内容,然后将页面传递给指定的爬虫进行处理。爬虫将页面解析并提取数据,然后将新的请求加入到请求队列中,以便继续抓取其他页面。
处理请求队列:Scrapy 会自动处理请求队列中的请求,继续爬取链接,直到没有更多的链接可用或达到设定的抓取限制。
数据处理和存储:在爬取和处理页面后,数据会被传递给存储管道(Pipeline)。存储管道负责进一步处理数据,如数据清洗、验证和保存。你可以配置多个存储管道,将数据存储到不同的目标中。
数据导出:最终,你可以选择将数据导出到所需的目标,如数据库、文件或其他数据存储系统。
总的来说,Scrapy 是一个高度可配置且高度可扩展的爬虫框架,它允许你通过简单的 Python 代码定义和运行爬虫,以获取网站上的结构化数据。它还提供了丰富的工具和中间件来处理各种爬虫任务,如处理 cookies、用户代理、HTTP 请求等。
92、数据库的优化方法有哪些?
1. 优化索引、SQL 语句、分析慢查询;
2. 设计表的时候严格根据数据库的设计范式来设计数据库;
3. 使用缓存,把经常访问到的数据而且不需要经常变化的数据放在缓存中,能节约磁盘IO;
4. 优化硬件;采用SSD,使用磁盘队列技术(RAID0,RAID1,RDID5)等;
5. 采用MySQL 内部自带的表分区技术,把数据分层不同的文件,能够提高磁盘的读取效率;
6. 垂直分表;把一些不经常读的数据放在一张表里,节约磁盘I/O;
7. 主从分离读写;采用主从复制把数据库的读操作和写入操作分离开来;
8. 分库分表分机器(数据量特别大),主要的的原理就是数据路由;
9. 选择合适的表引擎,参数上的优化;d
93、快排/冒泡的思想?
冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
94、谈谈lambda函数作用?
(1)、lambda函数比较轻便,即用即扔,很适合需要完成某一项简单功能,但是这个简单的功能只在此一处使用,连名字都很随意的情况下;
(2)、lambda是匿名函数,一般用来给filter,map,reduce这样的函数式编程服务;
(3)、作为回调函数,可以传递给某些应用,比如消息处理等。
95、内存泄露是什么?如何避免?
内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。导致程序运行速度减慢甚至系统崩溃等严重后果。
有 __del__() 函数的对象间的循环引用是导致内存泄漏的主凶。
不使用一个对象时使用:del object 来删除一个对象的引用计数就可以有效防止内存泄漏问题。通过 Python 扩展模块 gc 来查看不能回收的对象的详细信息。
可以通过 sys.getrefcount(obj) 来获取对象的引用计数,并根据返回值是否为 0 来判断是否内存泄漏。
96、函数装饰器有什么作用?
装饰器本质上是一个 Python 函数,它可以在让其他函数在不需要做任何代码的变动的前提下增加额外的功能。
装饰器的返回值也是一个函数的对象,它经常用于有切面需求的场景。 比如:插入日志、性能测试、事务处理、缓存、权限的校验等场景 有了装饰器就可以抽离出大量的与函数功能本身无关的雷同代码并发并继续使用。
97、Python 中 yield 的用法?
yield 就是保存当前程序执行状态。你用 for 循环的时候,每次取一个元素的时候就会计算一次。用 yield 的函数叫 generator,和 iterator 一样,它的好处是不用一次计算所有元素,而是用一次算一次,可以节省很多空间。
generator每次计算需要上一次计算结果,所以用 yield,否则一 return,上次计算结果就没了。
98、什么是序列化和反序列化?
Pickle模块接受任何Python对象并将其转换为字符串表示形式,并使用dump函数将其转储到文件中,此过程称为pickling。从存储的字符串表示中检索原始Python对象的过程称为unpickling。
99、Python中的module和package是什么?
在Python中,模块是构造程序的方式。每个Python程序文件都是一个模块,它导入其他模块,如对象和属性。
Python程序的文件夹是一个模块包。包可以包含模块或子文件夹。
在 Python 中,**模块(Module)和包(Package)**是组织和管理代码的两个重要概念,它们有助于将代码模块化、组织起来,使代码更可维护和可重用。
- 模块(Module):
- 模块是一个包含 Python 代码的文件,通常以
.py
作为文件扩展名。- 模块可以包含变量、函数、类和可执行的代码。
- 可以通过
import
语句将模块导入到其他 Python 脚本中,以便在其他地方重用模块中的代码。- 模块可以帮助你将代码分割成更小的部分,使代码更易于管理和维护。
示例:创建一个名为
my_module.py
的模块,并导入它:# my_module.py def greet(name): return f"Hello, {name}!" # main.py import my_module message = my_module.greet("Alice") print(message)
- 包(Package):
- 包是一种用于组织模块的目录结构,通常包含一个特殊的
__init__.py
文件(可以为空)以标识目录为包。- 包可以包含多个模块,这些模块可以在包内部相互引用。
- 包的目录结构形成了一个层次化的命名空间,有助于组织和管理相关的模块。
- 可以使用点号
.
表示法来导入包内的模块。示例:创建一个名为
my_package
的包,包含多个模块:my_package/ __init__.py module1.py module2.py
导入包内的模块:
# main.py from my_package import module1, module2 result1 = module1.function1() result2 = module2.function2() print(result1, result2)
模块和包是 Python 中代码组织和复用的基本工具,使代码更具结构性和可维护性。通过将代码拆分为模块和包,你可以更轻松地处理大型项目,并促进代码重用。
100、Python中如何跨模块共享全局变量?
要在单个程序中跨模块共享全局变量,请创建一个特殊模块。在应用程序的所有模块中导入配置模块。该模块将作为跨模块的全局变量提供。
在 Python 中,要在不同的模块之间共享全局变量,可以采用以下方法:
- 使用模块:
- 创建一个单独的模块(例如,
config.py
),在其中定义全局变量。- 在其他模块中导入这个模块并使用全局变量。
示例
config.py
模块:# config.py global_var = 42
在其他模块中导入并使用全局变量:
# main.py import config print(config.global_var) # 访问共享的全局变量
- 使用函数参数:
- 将需要共享的全局变量作为参数传递给需要访问它的函数。
- 这种方法适用于需要传递变量给特定函数而不是全局可用的情况。
示例:
# module1.py def func1(global_var): print(global_var) # module2.py from module1 import func1 global_var = 42 func1(global_var)
- 使用单例模式:
- 创建一个单例类,其中包含需要共享的全局变量。
- 在不同的模块中实例化这个单例类,并通过它来访问全局变量。
示例:
# singleton.py class GlobalVarSingleton: def __init__(self): self.global_var = None def set_global_var(self, value): self.global_var = value def get_global_var(self): return self.global_var # module1.py from singleton import GlobalVarSingleton singleton = GlobalVarSingleton() singleton.set_global_var(42) # module2.py from singleton import GlobalVarSingleton singleton = GlobalVarSingleton() print(singleton.get_global_var())
请注意,共享全局变量通常应小心使用,以避免不必要的复杂性和潜在的问题。在多个模块之间共享数据时,请确保考虑到并发访问和数据同步的问题。
造程序的方式。每个Python程序文件都是一个模块,它导入其他模块,如对象和属性。
Python程序的文件夹是一个模块包。包可以包含模块或子文件夹。
> 在 Python 中,**模块(Module)**和**包(Package)**是组织和管理代码的两个重要概念,它们有助于将代码模块化、组织起来,使代码更可维护和可重用。
>
> 1. **模块(Module)**:
> - 模块是一个包含 Python 代码的文件,通常以 `.py` 作为文件扩展名。
> - 模块可以包含变量、函数、类和可执行的代码。
> - 可以通过 `import` 语句将模块导入到其他 Python 脚本中,以便在其他地方重用模块中的代码。
> - 模块可以帮助你将代码分割成更小的部分,使代码更易于管理和维护。
>
> 示例:创建一个名为 `my_module.py` 的模块,并导入它:
>
> ```python
> # my_module.py
> def greet(name):
> return f"Hello, {name}!"
>
> # main.py
> import my_module
>
> message = my_module.greet("Alice")
> print(message)
> ```
>
> 2. **包(Package)**:
> - 包是一种用于组织模块的目录结构,通常包含一个特殊的 `__init__.py` 文件(可以为空)以标识目录为包。
> - 包可以包含多个模块,这些模块可以在包内部相互引用。
> - 包的目录结构形成了一个层次化的命名空间,有助于组织和管理相关的模块。
> - 可以使用点号 `.` 表示法来导入包内的模块。
>
> 示例:创建一个名为 `my_package` 的包,包含多个模块:
>
> ```
> my_package/
> __init__.py
> module1.py
> module2.py
> ```
>
> 导入包内的模块:
>
> ```python
> # main.py
> from my_package import module1, module2
>
> result1 = module1.function1()
> result2 = module2.function2()
>
> print(result1, result2)
> ```
>
> 模块和包是 Python 中代码组织和复用的基本工具,使代码更具结构性和可维护性。通过将代码拆分为模块和包,你可以更轻松地处理大型项目,并促进代码重用。
#### 100、Python中如何跨模块共享全局变量?
```properties
要在单个程序中跨模块共享全局变量,请创建一个特殊模块。在应用程序的所有模块中导入配置模块。该模块将作为跨模块的全局变量提供。
在 Python 中,要在不同的模块之间共享全局变量,可以采用以下方法:
- 使用模块:
- 创建一个单独的模块(例如,
config.py
),在其中定义全局变量。- 在其他模块中导入这个模块并使用全局变量。
示例
config.py
模块:# config.py global_var = 42
在其他模块中导入并使用全局变量:
# main.py import config print(config.global_var) # 访问共享的全局变量
- 使用函数参数:
- 将需要共享的全局变量作为参数传递给需要访问它的函数。
- 这种方法适用于需要传递变量给特定函数而不是全局可用的情况。
示例:
# module1.py def func1(global_var): print(global_var) # module2.py from module1 import func1 global_var = 42 func1(global_var)
- 使用单例模式:
- 创建一个单例类,其中包含需要共享的全局变量。
- 在不同的模块中实例化这个单例类,并通过它来访问全局变量。
示例:
# singleton.py class GlobalVarSingleton: def __init__(self): self.global_var = None def set_global_var(self, value): self.global_var = value def get_global_var(self): return self.global_var # module1.py from singleton import GlobalVarSingleton singleton = GlobalVarSingleton() singleton.set_global_var(42) # module2.py from singleton import GlobalVarSingleton singleton = GlobalVarSingleton() print(singleton.get_global_var())
请注意,共享全局变量通常应小心使用,以避免不必要的复杂性和潜在的问题。在多个模块之间共享数据时,请确保考虑到并发访问和数据同步的问题。