python实例02
在Python中如何实现单例模式。
在 Python 中,可以使用装饰器或元类来实现单例模式。
使用装饰器
装饰器是 Python 的一种语法糖,可以对一个函数或类进行修饰,添加一些额外的功能。下面是使用装饰器实现单例模式的示例代码:
from functools import wraps
def singleton(cls):
"""单例类装饰器"""
instances = {}
@wraps(cls)
def wrapper(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper
@singleton
class Logger:
def __init__(self, name):
self.logger = logging.getLogger(name)
self.logger.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch = logging.StreamHandler()
ch.setFormatter(formatter)
self.logger.addHandler(ch)
def info(self, message):
self.logger.info(message)
def error(self, message):
self.logger.error(message)
def warning(self, message):
self.logger.warning(message)
这个示例代码定义了一个 singleton
装饰器,将传入的类包装成一个单例,被装饰的类每次实例化时都会返回同一个对象。
在这个示例代码中,被装饰的类是 Logger
,使用 @singleton
装饰器修饰,它会在 Logger
实例化时返回同一个对象。在 singleton
装饰器内部,使用一个字典 instances
来保存每个类的实例。当第一次实例化某个类时,如果该类不在 instances
字典中,就创建一个新的实例并添加到 instances
字典中;否则直接返回已有的实例。
为了保持被装饰类的元信息不变,装饰器使用 functools.wraps
来复制被装饰类的元信息。
在写装饰器的时候,带装饰功能的函数(上面代码中的wrapper
函数)通常都会用functools
模块中的wraps
再加以装饰,这个装饰器最重要的作用是给被装饰的类或函数动态添加一个__wrapped__
属性,这个属性会将被装饰之前的类或函数保留下来,这样在我们不需要装饰功能的时候,可以通过它来取消装饰器,例如可以使用Logger= Logger.__wrapped__
来取消对Logger
类做的单例处理。
在 Logger
类的构造函数中,我们设置了默认的日志级别、格式化对象和输出流,以及添加了一个控制台处理器。然后,我们定义了三个方法 info
、error
和 warning
,可以分别用于输出不同级别的日志。
使用该示例代码,我们可以方便地创建一个单例的 Logger 对象,并在程序的任何地方使用它来记录日志。例如:
logger = Logger('my_logger')
logger.info('This is an info message.')
logger.error('This is an error message.')
这时,程序只会创建一个 Logger
实例,并在控制台输出相应的日志信息。
使用元类
import pymysql
import threading
class SingletonMeta(type):
_instances = {}
_lock = threading.Lock()
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
with cls._lock:
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class MySQLConnection(metaclass=SingletonMeta):
def __init__(self):
# 获取数据库连接参数
config = self.get_config()
# 创建数据库连接
self.connection = pymysql.connect(**config)
def get_config(self):
# 获取数据库连接参数
# ...
我们在定义类时,如果没有给一个类指定父类,那么默认的父类是object
,如果没有给一个类指定元类,那么默认的元类是type
。通过自定义的元类,我们可以改变一个类默认的行为,就如同上面的代码中,我们通过元类的__call__
魔术方法,改变了MySQLConnection
类的构造器那样。
在上面的示例代码中,使用了元类 SingletonMeta
来实现单例模式。通过定义 _instances
和 _lock
变量和 __call__()
方法,确保只有一个实例会被创建,以及避免多线程竞争的情况。
因为多个线程可能同时调用 __call__()
方法,所以必须使用线程锁来保证只有一个线程能够创建实例。在这里,采用了 threading.Lock()
来实现线程锁。每个线程在执行 __call__()
方法时都会获取 _lock
线程锁,然后在创建实例对象前加锁,创建完实例对象后再解锁。
with cls._lock:
就是将 cls._lock
对象作为上下文管理器来使用。当进入这个语句块时,会调用 cls._lock.__enter__()
方法获取线程锁,保证只有一个线程能够同时执行这段代码块。当退出这个语句块时,会调用 cls._lock.__exit__()
方法释放线程锁,使其他线程能够获取线程锁继续执行该代码块。
MySQLConnection
类通过继承 SingletonMeta
元类来实现单例模式,该类只会创建一个MySQL数据库连接,并提供了 get_config()
来获取连接参数。
可以根据具体的应用场景稍作调整,使用该方式来创建只有一个MySQL数据库连接的单例对象。
关于单例模式,在面试中还有可能被问到它的应用场景。通常一个对象的状态是被其他对象共享的,就可以将其设计为单例,例如项目中使用的数据库连接池对象和配置对象通常都是单例,这样才能保证所有地方获取到的数据库连接和配置信息是完全一致的;而且由于对象只有唯一的实例,因此从根本上避免了重复创建对象造成的时间和空间上的开销,也避免了对资源的多重占用。再举个例子,项目中的日志操作通常也会使用单例模式,这是因为共享的日志文件一直处于打开状态,只能有一个实例去操作它,否则在写入日志的时候会产生混乱。
写一个删除列表中重复元素的函数,要求去重后元素相对位置保持不变。
def dedup(items):
no_dup_items = []
seen = set()
for item in items:
if item not in seen:
no_dup_items.append(item)
seen.add(item)
print(no_dup_items)
return no_dup_items
items = [1, 2, 3, 1, 3, 5, 6, 4]
dedup(items)
# 输出
[1, 2, 3, 5, 6, 4]
这段代码定义了一个名为 dedup
的函数,接收一个列表 items
作为参数,并返回去重后的列表。
具体来说,函数的执行过程如下:
定义一个空列表
no_dup_items
,用于存放去重后的列表。定义一个集合
seen
,用于保存已经出现过的元素。遍历列表
items
中的每个元素,如果该元素没有在集合seen
中出现过,则将其添加到no_dup_items
列表中,并将其加入seen
集合中。返回去重后的列表
no_dup_items
。
这个函数使用了集合的特性来进行去重操作,因为集合中的元素是不可重复的。遍历列表 items
中的每个元素时,使用 if item not in seen:
判断该元素是否已经在集合 seen
中出现过,如果没有出现过,则将其添加到 no_dup_items
列表中,并将其加入 seen
集合中。这样一来,集合中只会保存每种不同的元素,达到了去重的效果。
最后,通过在函数末尾使用 return no_dup_items
将去重后的列表返回。同时,函数内部也使用了 print(no_dup_items)
打印出去重后的列表,方便调试和查看执行结果。
或者把上面的函数改造成一个生成器
def dedup(items):
seen = set()
for item in items:
if item not in seen:
yield item
seen.add(item)
print(seen)
res = dedup([1, 2, 3, 1, 3, 5, 6, 4])
for i in res:
print(i)
# 输出
1
2
3
5
6
4
{1, 2, 3, 4, 5, 6}
具体来说,函数的执行过程如下:
定义一个集合
seen
,用于保存已经出现过的元素。遍历列表
items
中的每个元素,如果该元素没有在集合seen
中出现过,则将其yield
出来,并将其加入seen
集合中。通过
yield
返回生成器对象,实现迭代输出列表中不重复的元素。
这个函数使用了生成器的特性来进行去重操作,在遍历列表 items
中的每个元素时,使用 if item not in seen:
判断该元素是否已经在集合 seen
中出现过,如果没有出现过,则通过 yield
将该元素输出。这样一来,生成器会逐个输出列表 items
中的不同元素,达到了去重的效果。
同时,每次将元素加入 seen
集合中,保证了后续不会重复输出该元素。由于该函数使用生成器实现,所以并不会直接返回去重后的列表,而是返回一个生成器对象,可以通过迭代该对象来获取去重后的元素。
Lambda函数是什么,举例说明的它的应用场景。
在Python中,Lambda函数是一种匿名函数,通常是在需要一个函数,但只有短时间使用时才会定义。与普通函数不同,Lambda函数只能有一条表达式,并且不支持语句块。不用写return
关键字。Lambda函数因为没有名字,所以也不会跟其他函数发生命名冲突的问题。主要的作用是通过向函数传入函数或让函数返回函数最终实现代码的解耦合。
Lambda函数的基本语法如下:
lambda arguments: expression
其中,arguments
表示函数参数,可以有多个。expression
表示函数体,只能有一条表达式。
排序:在Python中,可以使用内置函数
sorted()
进行排序,其中可以通过指定key
参数来实现自定义排序。例如,用 Lambda 函数实现按照人口从高到底排序:cities = [ ('Boston', 673184), ('Los Angeles', 3977683), ('San Francisco', 883305)] sorted_cities = sorted(cities, key=lambda x: x[1], reverse=True) print(sorted_cities) # 输出 [('Los Angeles', 3977683), ('San Francisco', 883305), ('Boston', 673184)]
映射:在Python中,可以使用内置函数
map()
进行序列映射,其中可以传入 Lambda 函数进行序列元素的映射操作。例如,用 Lambda 函数实现将列表中每个元素加上 1:
map()
是 Python 内置的高阶函数,它接收一个函数和一个序列(迭代器、列表等),并对序列中的每个元素分别应用该函数,最终返回一个新的可迭代对象,其中包含了所有经过处理后的数据。
nums = [1, 2, 3, 4]
new_nums = list(map(lambda x: x + 1, nums))
print(new_nums)
# 输出 [2, 3, 4, 5]
过滤:在Python中,可以使用内置函数
filter()
进行序列过滤,其中可以传入 Lambda 函数进行条件过滤。例如,用 Lambda 函数实现过滤出列表中的奇数元素:
nums = [1, 2, 3, 4]
odd_nums = list(filter(lambda x: x % 2 == 1, nums))
print(odd_nums)
# 输出 [1, 3]
说说Python中的浅拷贝和深拷贝
浅拷贝(Shallow Copy)指的是创建一个新的对象,这个新对象有着原始对象的一些属性值。但是如果这些属性值是对象,那么浅拷贝只是复制了这个对象的地址,也就是说,新的对象的属性值还是指向原始对象的值。在 Python 中,可以使用以下方式实现浅复制:
new_list = my_list.copy()
new_dict = my_dict.copy()
# 这里 copy() 方法就是浅复制方法。如果需要改变原始对象中嵌套的对象属性值,新对象属性值也会随之改变。
深拷贝(Deep Copy)创建一个新的对象,这个新对象和原始对象有着一模一样的属性值。如果原始对象中有嵌套的对象,深拷贝会递归地将所有嵌套的对象都复制一遍。在 Python 中,可以使用以下方式实现深复制:
import copy
new_list = copy.deepcopy(my_list)
new_dict = copy.deepcopy(my_dict)
# 这里用到了 Python 中的 copy 模块,并调用深拷贝方法 deepcopy() 实现深复制。如果我们需要独立处理原始对象和新对象的嵌套属性值,那么就需要使用深拷贝
深拷贝可能会遇到两个问题:一是一个对象如果直接或间接的引用了自身,会导致无休止的递归拷贝;二是深拷贝可能对原本设计为多个对象共享的数据也进行拷贝。
deepcopy
函数的本质其实就是对象的一次序列化和一次返回序列化,面试题中还考过用自定义函数实现对象的深拷贝操作,显然我们可以使用pickle
模块的dumps
和loads
来做到,代码如下所示。
import pickle
my_deep_copy = lambda obj: pickle.loads(pickle.dumps(obj))
说一下你对Python中迭代器和生成器的理解
迭代器是实现了迭代器协议的对象。跟其他编程语言不通,Python中没有用于定义协议或表示约定的关键字,像interface
、protocol
这些单词并不在Python语言的关键字列表中。Python语言通过魔法方法来表示约定,也就是我们所说的协议,而__next__
和__iter__
这两个魔法方法就代表了迭代器协议。可以通过for-in
循环从迭代器对象中取出值,也可以使用next
函数取出迭代器对象中的下一个值。生成器是迭代器的语法升级版本,可以用更为简单的代码来实现一个迭代器。
扩展:面试中经常让写生成斐波那契数列的迭代器,大家可以参考下面的代码。
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()
import sys
def fib(num):
a, b = 0, 1
for _ in range(num):
a, b = b, a + b
yield a
it = fib(10)
while True:
try:
print(next(it), end=" ")
except StopIteration:
sys.exit()
# 输出
1 1 2 3 5 8 13 21 34 55
# 生成器的语法来改写上面的代码
正则表达式的match方法和search方法有什么区别?
正则表达式的match方法和search方法都是用来在一个字符串中寻找符合某一模式的子字符串。
不同之处在于,match方法从字符串的开头开始匹配,只有当字符串的开头符合模式时才能返回匹配结果;而search方法可以从字符串的任意一个位置开始寻找符合模式的子串,并返回第一个符合模式的子串。
match
方法是从字符串的起始位置进行正则表达式匹配,返回Match
对象或None。search
方法会扫描整个字符串来找寻匹配的模式,同样也是返回Match对象或None。
以Python的re模块为例,这两种方法的使用方式如下:
# -*- coding: utf-8 -*-
import re
# match方法
pattern = 'world'
pattern2 = 'hello'
string = 'hello world'
result = re.match(pattern, string)
print(result)
result1 = re.match(pattern2, string)
print(result1)
# search方法
pattern = 'world'
string = 'hello world'
result = re.search(pattern, string)
print(result)
# 输出
None
<re.Match object; span=(0, 5), match='hello'>
<re.Match object; span=(6, 11), match='world'>
下面这段代码的执行结果是什么
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]
。
这是因为multiply()返回了一个包含4个lambda函数的列表,这些函数都是用来计算参数x与range(4)中各个元素的乘积。但是由于i在lambda表达式中是自由变量,所以在调用lambda函数时,i已经变成了3,因为在 range(4) 中,最后一个元素是 3。因此,后续对于任何一个 lambda 函数来说,它们所引用的 i 都指向了 3。
当我们在调用 [m(100) for m in multiply()]
时,实际上是依次调用每个 lambda 函数,并将其结果存储到列表中。每个 lambda 函数的结果都是 i * x
,而此时的 i 始终等于3,所以最终得到了 4 个 300 组成的列表。
修正这个问题的方法有多种,可以使用闭包、将lambda函数的参数作为默认参数、使用生成器等等。以下是一个使用闭包的例子:
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]
或者使用生成器,让函数获得i
的当前值。
def multiply():
return (lambda x: i * x for i in range(4))
print([m(100) for m in multiply()])
# 输出
[0, 100, 200, 300]
或者
def multiply():
for i in range(4):
yield lambda x: x * i
print([m(100) for m in multiply()])
# 输出
[0, 100, 200, 300]
写一个函数统计传入的列表中每个数字出现的次数并返回对应的字典。
传统方法
# -*- coding: utf-8 -*-
def count_elements(lst):
count_dict = {}
for item in lst:
if isinstance(item, (int, float)):
if item in count_dict:
count_dict[item] += 1
else:
count_dict[item] = 1
return count_dict
dict01 = count_elements([1, '2', 3, 1, '2', 2, 4, 2, 5])
print(dict01)
# 输出
{1: 2, 3: 1, 2: 2, 4: 1, 5: 1}
需要注意的是,在这个实现中,数字元素的类型包括整型和浮点数,通过
isinstance(item, (int, float))
来判断。如果列表中包含其他类型的元素,如字符串、布尔型等,则它们不会被统计在内。
或者使用
# -*- coding: utf-8 -*-
def count_elements(items):
result = {}
for item in items:
if isinstance(item, (int, float)):
result[item] = result.get(item, 0) + 1
return result
dict01 = count_elements([1, '2', 3, 1, '2', 2, 4, 2, 5])
print(dict01)
使用Python代码实现遍历一个文件夹的操作
使用 os.walk()
方法:这种方法可以递归地遍历整个目录树,并返回一个包含当前目录、子目录和文件名称的元组。
# -*- coding: utf-8 -*-
import os
path = "/path/to/folder"
for root, dirs, files in os.walk(path):
for filename in files:
print(os.path.join(path, filename))
for dirname in dirs:
print(os.path.join(path, dirname))
如果题目明确要求不能使用os.walk
函数,那么可以使用os.listdir
函数来获取指定目录下的文件和文件夹,然后再通过循环遍历用os.isdir
函数判断哪些是文件夹,对于文件夹可以通过递归调用进行遍历,这样也可以实现遍历一个文件夹的操作。
import os
path = "/path/to/folder"
for filename in os.listdir(path):
full_path = os.path.join(path, filename)
if os.path.isfile(full_path):
print("File:", filename)
elif os.path.isdir(full_path):
print("Directory:", filename)
现有2元、3元、5元共三种面额的货币,如果需要找零99元,一共有多少种找零的方式?
from functools import lru_cache
@lru_cache()
def change_money(total):
if total == 0:
return 1
if total < 0:
return 0
return change_money(total - 2) + change_money(total - 3) + \
change_money(total - 5)
一个找零钱的函数 change_money
,使用了 Python 标准库中的 functools.lru_cache()
装饰器来缓存函数的结果,避免重复计算。
具体来说,函数接受一个参数 total
,表示需要找回的总金额。函数首先判断特殊情况:如果总金额为 0,则只有一种可能的找法,即不找;如果总金额小于 0,则没有找法。否则,函数递归地考虑从当前金额中找回 2、3、或 5 元钞票的所有可能,并返回它们的总和。这样,通过多次递归调用,就可以计算出总共有多少种不同的找钱方案。
由于递归调用会重复计算相同的结果,因此使用 lru_cache()
装饰器来缓存函数的结果,在第二次调用时直接返回缓存中的值,避免重复计算,从而提高程序的运行效率。
需要注意的是,这个函数只能处理整数类型的总金额,如果传入其他类型的参数,程序会抛出异常。
说说你用过Python标准库中的哪些模块
模块名 | 介绍 |
---|---|
sys | 跟Python解释器相关的变量和函数,例如: |
os | 和操作系统相关的功能,例如: |
re | 和正则表达式相关的功能,例如: |
math | 和数学运算相关的功能,例如: |
logging | 和日志系统相关的类和函数,例如: |
json / pickle | 实现对象序列化和反序列的模块,例如: |
hashlib | 封装了多种哈希摘要算法的模块,例如: |
urllib | 包含了和URL相关的子模块,例如: |
itertools | 提供各种迭代器的模块,例如: |
functools | 函数相关工具模块,例如: |
collections / heapq | 封装了常用数据结构和算法的模块,例如: |
threading / multiprocessing | 多线程/多进程相关类和函数的模块,例如: |
concurrent.futures / asyncio | 并发编程/异步编程相关的类和函数的模块,例如: |
base64 | 提供BASE-64编码相关函数的模块,例如: |
csv | 和读写CSV文件相关的模块,例如: |
profile / cProfile / pstats | 和代码性能剖析相关的模块,例如: |
unittest | 和单元测试相关的模块,例如: |
__init__
和__new__
方法有什么区别?
在 Python 中,__new__
和__init__
都是用于创建对象的特殊方法,不过它们的作用不同。
__new__
方法是一个静态方法,它负责创建并返回一个新的实例,通常是被继承的类或元类的构造方法。在调用 __new__
方法时,首先会生成一个空对象,然后将其传递给 __init__
方法,最终通过 return
语句返回该实例。
__init__
方法是一个实例方法,它在 __new__
方法创建的实例上调用,负责初始化该实例的属性和方法。在 __init__
方法中,通常可以设置默认属性值、初始化实例变量等操作。
因此,两者的作用不同,__new__
负责创建实例对象,并将其返回,而__init__
则负责初始化实例的属性。在一般情况下,我们不需要显式地调用 __new__
方法,而是通过调用类名来创建实例对象,并且__init__
方法在实例化后自动被调用。
输入年月日,判断这个日期是这一年的第几天。
# -*- coding: utf-8 -*-
import datetime
def which_day(year, month, date):
end = datetime.date(year, month, date)
start = datetime.date(year, 1, 1)
return (end - start).days + 1
print(which_day(2023, 3, 28))
# 输出
87
函数参数`arg`和`*kwargs`分别代表什么?
在 Python 中,函数参数可以分为三种类型:位置参数、默认参数和可变参数。其中,可变参数又包括两种:*args
和**kwargs
。
*args
表示接受任意数量的位置参数,它将这些位置参数打包成一个元组(tuple),并将该元组赋值给变量 args
。具体来说,当调用带有 *args
参数的函数时,如果传递了多个位置参数,那么这些位置参数就会被打包成一个元组,作为 args
变量的值传递给函数。
**kwargs
类似于 *args
,但是它用于接受任意数量的关键字参数,可以传递一些额外的键值对作为参数。它将这些关键字参数打包成一个字典(dictionary),并将该字典赋值给变量 kwargs
。具体来说,当调用带有 **kwargs
参数的函数时,如果传递了多个关键字参数,那么这些参数就会被打包成一个字典,作为 kwargs
变量的值传递给函数。
需要注意的是,在函数定义中,*args
和 **kwargs
必须放在位置参数和默认参数的后面。并且,通常情况下,args
和 kwargs
这两个变量名称是约定俗成的,但不是强制要求,可以使用其他变量名称代替。
写一个记录函数执行时间的装饰器
import time
def record_time(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"函数 {func.__name__} 的执行时间为:{end_time - start_time} 秒")
return result
return wrapper
这个装饰器接受一个函数作为参数,返回一个新的函数 wrapper,wrapper 函数内部使用 time 模块获得函数开始执行和结束执行的时间点,并计算两者之间的时间差,最后打印出函数执行的时间。同时,wrapper 函数也会调用原来的函数并返回其执行结果。
使用这个装饰器很简单,只需要在需要进行时间统计的函数上面添加 @record_time 即可。例如:
@record_time
def test_func():
# ... 这里是函数的具体实现 ...
当你调用 test_func()
函数时,就会自动输出这个函数的执行时间。
Python中如何实现字符串替换操作?
Python中实现字符串替换大致有两类方法:字符串的replace
方法和正则表达式的sub
方法。
方法一:使用字符串的replace
方法
message = 'hello, world!'
print(message.replace('o', 'O').replace('l', 'L').replace('he', 'HE'))
# 输出
HELLO, wOrLd!
方法二:使用正则表达式的sub
方法
import re
message = 'hello, world!'
pattern = re.compile('[aeiou]')
print(pattern.sub('#', message))
# 输出
h#ll#, w#rld!
这段代码使用了 Python 的 re 模块,将字符串 message 中的所有元音字母(即 a、e、i、o、u)替换为 # 号,并输出结果。
具体来说,这段代码首先使用 re.compile() 函数创建了一个正则表达式对象 pattern,该正则表达式可匹配所有元音字母。接着,使用 pattern.sub() 方法,将 message 中所有匹配到的元音字母替换为 # 号。
对保存文件名的列表排序,要求文件名按照字母表和数字大小进行排序
例如对于列表filenames = ['a12.txt', 'a8.txt', 'b10.txt', 'b2.txt', 'b19.txt', 'a3.txt'],排序的结果是['a3.txt', 'a8.txt', 'a12.txt', 'b2.txt', 'b10.txt', 'b19.txt']
# -*- coding: utf-8 -*-
import re
def sort_filenames(filenames):
def key_func(filename):
# 正则表达式提取文件名中的字母和数字
# (\D+) 匹配所有非数字字符,(\d+) 匹配所有数字字符
match = re.search(r'(\D+)(\d+)', filename)
# 获取文件名中的字母部分和数字部分,并转换数字部分的数据类型
letter, number = match.group(1), int(match.group(2))
print(letter, number)
return letter, number # 返回一个二元组作为排序的依据
sorted_filename = sorted(filenames, key=key_func)
return sorted_filename
filenames = ['a12.txt', 'a8.txt', 'b10.txt', 'b2.txt', 'b19.txt', 'a3.txt']
sorted_filenames = sort_filenames(filenames)
print(sorted_filenames)
# 输出
a 12
a 8
b 10
b 2
b 19
a 3
['a3.txt', 'a8.txt', 'a12.txt', 'b2.txt', 'b10.txt', 'b19.txt']
在这段代码中,filename 是一个文件名字符串,通过调用 search() 方法并传入正则表达式 r'(\D+)(\d+)',得到一个匹配对象 match。这个 match 对象可以通过调用 group() 方法以及指定组号来获取匹配到的字母部分和数字部分。
例如,match.group(1) 返回匹配到的第一个子表达式(即 (\D+))所匹配到的字母部分,match.group(2) 返回匹配到的第二个子表达式(即 (\d+))所匹配到的数字部分。
在函数 sort_filenames 中,我们首先定义了一个 key_func,该函数接受一个文件名作为参数,使用正则表达式将文件名拆分成字母和数字两部分。然后将字母部分作为第一关键字,数字部分作为第二关键字,返回一个元组作为排序的依据。
在函数体内,我们使用 sorted() 函数对 filenames 进行排序,关键字参数 key 指定排序规则为按照 key_func 函数输出的元组进行排序。
这个函数可以根据文件名的规则排序,即先按字母表顺序排列,再按数字大小排序。
如何使用`random`模块生成随机数、实现随机乱序和随机抽样?
random.random()
函数可以生成[0.0, 1.0)
之间的随机浮点数。random.uniform(a, b)
函数可以生成[a, b]
或[b, a]
之间的随机浮点数。random.randint(a, b)
函数可以生成[a, b]
或[b, a]
之间的随机整数。random.shuffle(x)
函数可以实现对序列x
的原地随机乱序。random.choice(seq)
函数可以从非空序列中取出一个随机元素。random.choices(population, weights=None, *, cum_weights=None, k=1)
函数可以从总体中随机抽取(有放回抽样)出容量为k
的样本并返回样本的列表,可以通过参数指定个体的权重,如果没有指定权重,个体被选中的概率均等。random.sample(population, k)
函数可以从总体中随机抽取(无放回抽样)出容量为k
的样本并返回样本的列表。
如何读取大文件,例如内存只有4G,如何读取一个大小为8G的文件?
with open('...', 'rb') as file:
for data in iter(lambda: file.read(2097152), b''):
pass
with open('...', 'rb') as file:
:使用with
语句打开一个二进制模式的文件,并将其赋值给变量file
。这个文件将在代码块结束时被自动关闭。lambda: file.read(2097152)
:定义了一个 lambda 函数,每次调用会读取file
文件对象中最多 2097152 字节的内容并返回。这里的 2097152 表示每次读取的块大小,即 2MB。iter(lambda: file.read(2097152), b'')
:使用iter()
函数包装上述 lambda 函数,创建一个迭代器。该迭代器将返回file
文件对象中最多 2097152 字节的内容,直到内容读取结束(读取到了空字符b''
)。for data in ...:
:使用for
循环遍历上述迭代器返回的数据块。pass
:在每次循环中不做任何处理,因为这里是示例代码,实际处理时可以根据需要对每个数据块进行处理。
总的来说,这段代码可以实现逐块读取大文件,并在每个数据块上执行相应的处理。这种方法可以避免一次性将整个文件读入内存,从而减少内存消耗。
说一下你对Python中模块和包的理解。
每个Python文件就是一个模块,而保存这些文件的文件夹就是一个包,但是这个作为Python包的文件夹必须要有一个名为__init__.py
的文件,否则无法导入这个包。通常一个文件夹下还可以有子文件夹,这也就意味着一个包下还可以有子包,子包中的__init__.py
并不是必须的。模块和包解决了Python中命名冲突的问题,不同的包下可以有同名的模块,不同的模块下可以有同名的变量、函数或类。在Python中可以使用import
或from ... import ...
来导入包和模块,在导入的时候还可以使用as
关键字对包、模块、类、函数、变量等进行别名,从而彻底解决编程中尤其是多人协作团队开发时的命名冲突问题。
对下面给出的字典按值从大到小对键进行排序。
# -*- coding: utf-8 -*-
prices = {
'AAPL': 191.88,
'GOOG': 1186.96,
'IBM': 149.24,
'ORCL': 48.44,
'ACN': 166.89,
'FB': 208.09,
'SYMC': 21.29
}
print(sorted(prices, key=lambda x: prices[x], reverse=True) )
# 输出
['GOOG', 'FB', 'AAPL', 'ACN', 'IBM', 'ORCL', 'SYMC']
lambda x: prices[x]
:这是一个 lambda 表达式,表示对于给定的键x
,返回其对应的值(即商品价格)。reverse=True
:这个参数表示对结果进行反向排序,从而将价格高的商品排在前面。结果是一个排序后的商品名称列表,其中每个名称对应的价格都是按照由高到低的顺序排列的。
阿拉伯数字转换成中国汉字
# 定义数字转换表
num_dict = {'0': '零', '1': '一', '2': '二', '3': '三', '4': '四',
'5': '五', '6': '六', '7': '七', '8': '八', '9': '九'}
unit_dict = {0: '', 1: '十', 2: '百', 3: '千', 4: '万'}
# 将数字转换成中文汉字
def num2chinese(num):
chinese = ""
num_str = str(num)
num_len = len(num_str)
# 处理万以上的数字
if num_len > 4:
chinese += num_dict[num_str[-5]] + unit_dict[4]
# 依次处理每一位数字
for i in range(num_len):
# 获取对应的数字、位数、单位
digit = int(num_str[-(i+1)])
unit = unit_dict[i % 4]
if i != 0 and i % 4 == 0 and digit != 0:
unit = unit_dict[4] + unit
# 拼接中文汉字
if digit != 0 or (i == 0 and num_len == 1):
chinese = num_dict[str(digit)] + unit + chinese
return chinese
# 测试
num = 1234
print(num2chinese(num))
本文转载修改于懂java的测试
评论