-
在 Python 中,要对包含中文字符串的列表按照拼音顺序进行排序,可以使用 pypinyin 库将中文转换为拼音,然后基于拼音进行排序。以下是具体实现方法:方法一:使用 pypinyin 库首先安装 pypinyin 库:pip install pypinyin代码示例:from pypinyin import pinyin, Style def sort_chinese_list(lst): # 使用pypinyin获取每个字符串的拼音,并保留原字符串 decorated = [(item, pinyin(item, style=Style.NORMAL)) for item in lst] # 根据拼音排序 decorated.sort(key=lambda x: x[1]) # 提取排序后的原字符串 return [item[0] for item in decorated] # 示例列表 chinese_list = ["张三", "李四", "王五", "赵六", "阿七"] sorted_list = sort_chinese_list(chinese_list) print(sorted_list) 输出:['阿七', '李四', '王五', '张三', '赵六'] 方法二:使用 locale 模块(仅限部分系统)如果系统支持中文 locale,可以使用 locale.strcoll 进行本地化排序:import locale def sort_chinese_list(lst): # 设置中文locale(根据系统可能需要调整) locale.setlocale(locale.LC_COLLATE, 'zh_CN.UTF-8') return sorted(lst, key=lambda x: locale.strxfrm(x)) chinese_list = ["张三", "李四", "王五", "赵六", "阿七"] sorted_list = sort_chinese_list(chinese_list) print(sorted_list) 注意:locale 方法依赖系统配置,可能在某些环境中不生效,推荐优先使用 pypinyin 方法。方法三:简化版(仅首字母排序)如果只需要按首字母排序:from pypinyin import pinyin, Style chinese_list = ["张三", "李四", "王五", "赵六", "阿七"] sorted_list = sorted(chinese_list, key=lambda x: pinyin(x, style=Style.FIRST_LETTER)[0][0]) print(sorted_list) 关键点说明pypinyin 的 Style.NORMAL 会生成完整拼音(如 “张三” → [['zhang'], ['san']])如果字符串包含非中文字符,pypinyin 会保留原字符对于多音字,pypinyin 默认使用常见读音,可通过参数调整性能优化对于大型列表,可以预先计算拼音并缓存:from pypinyin import lazy_pinyin chinese_list = ["张三", "李四", "王五", "赵六", "阿七"] # 预先生成拼音映射 pinyin_map = {item: lazy_pinyin(item) for item in chinese_list} sorted_list = sorted(chinese_list, key=lambda x: pinyin_map[x]) print(sorted_list) 选择哪种方法取决于你的具体需求和运行环境。pypinyin 是最通用可靠的解决方案。
-
在 Python 中,要从一个字符串数组(列表)中删除另一个字符串数组中存在的相同元素,可以使用列表推导式(List Comprehension)或集合(Set)操作。以下是几种实现方法:方法 1:列表推导式(保留顺序)list1 = ["apple", "banana", "cherry", "date"] list2 = ["banana", "date", "fig"] # 删除 list1 中存在于 list2 的元素 result = [item for item in list1 if item not in list2] print(result) # 输出: ['apple', 'cherry'] 说明:遍历 list1,仅保留不在 list2 中的元素。保持原始顺序,适合对顺序敏感的场景。方法 2:集合操作(高效去重)list1 = ["apple", "banana", "cherry", "date"] list2 = ["banana", "date", "fig"] # 转换为集合后求差集,再转回列表(顺序可能丢失) result = list(set(list1) - set(list2)) print(result) # 输出顺序可能变化,如: ['cherry', 'apple'] 说明:集合操作效率更高(O(1) 查找),但会丢失原始顺序和重复元素。适合不关心顺序且元素唯一的场景。方法 3:使用 filter 函数list1 = ["apple", "banana", "cherry", "date"] list2 = ["banana", "date", "fig"] result = list(filter(lambda x: x not in list2, list1)) print(result) # 输出: ['apple', 'cherry'] 说明:功能与列表推导式类似,但可读性稍差。方法 4:处理重复元素(保留 list1 的重复项)如果 list1 有重复元素且需全部保留(即使与 list2 重复):from collections import defaultdict list1 = ["apple", "banana", "cherry", "date", "banana"] list2 = ["banana", "date"] # 统计 list2 的元素出现次数 count_dict = defaultdict(int) for item in list2: count_dict[item] += 1 # 仅删除 list1 中与 list2 匹配的次数 result = [] for item in list1: if count_dict[item] > 0: count_dict[item] -= 1 else: result.append(item) print(result) # 输出: ['apple', 'cherry', 'banana'] 说明:仅删除 list1 中与 list2 等数量的重复项(例如 list2 有 1 个 "banana",则删除 list1 中的 1 个 "banana")。总结推荐方法 1(列表推导式):简单直观,保留顺序。高效去重:用方法 2(集合操作),但注意顺序和重复项。复杂需求(如保留部分重复项):用方法 4。
-
Python、Git技术干货合集:1、Python中实现JSON数据读写的编程实战指南cid:link_02、Python在类中强制规定编码的高级开发指南cid:link_13、Python-pywin32库的使用高级操作方法cid:link_24、从Git历史中删除误提交文件并保留本地文件的解决方案cid:link_85、 Git撤回合并提交的多种方法cid:link_96、清理Git分支从查看到批量删除无效分支的全流程cid:link_37、Git推送代码遭遇403 Forbidden错误的原因和解决方法cid:link_48、 在Git中撤回最近的commit的多种方式小结cid:link_59、 Git用户名与邮箱的配置指南cid:link_610、 Git推送代码遭遇403 Forbidden错误的原因和解决方法cid:link_1011、 在Git中撤回最近的commit的多种方式小结cid:link_1112、Pandas DataFrame进行数据拼接方法详解cid:link_1213、 Python+FastAPI构建一个企业级AI Agent微服务的完整指南cid:link_1314、Python中获取13位和10位时间戳的方法cid:link_715、Python Playwright 语法知识详解(推荐)https://bbs.huaweicloud.com/forum/thread-0282197791923244005-1-1.html
-
面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。万事万物皆为对象:被子、各类事务、逻辑事件、广电,对象,都可以对 对象进行归类。面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。人以群分、物以类聚!我们以一个例子来说明面向过程和面向对象在程序流程上的不同之处。假设我们要处理学生的成绩表,为了表示一个学生的成绩,面向过程的程序可以用一个dict表示: 而处理学生成绩可以通过函数实现,比如打印学生的成绩: 如果采用面向对象的程序设计思想,我们首选思考的不是程序的执行流程,而是Student这种数据类型应该被视为一个对象,这个对象拥有name和score这两个属性(Property)。如果要打印一个学生的成绩,首先必须创建出这个学生对应的对象,然后,给对象发一个print_score消息,让对象自己把自己的数据打印出来。 给对象发消息实际上就是调用对象对应的关联函数,我们称之为对象的方法(Method)。面向对象的程序写出来就像这样: 面向对象的设计思想是从自然界中来的,因为在自然界中,类(Class)和实例(Instance)的概念是很自然的。Class是一种抽象概念,比如我们定义的Class——Student,是指学生这个概念,而实例(Instance)则是一个个具体的Student,比如,Bart Simpson和Lisa Simpson是两个具体的Student。所以,面向对象的设计思想是抽象出Class,根据Class创建Instance。面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法。 小结数据封装、继承和多态是面向对象的三大特点,我们后面会详细讲解。类和实例面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。仍以Student类为例,在Python中,定义类是通过class关键字: class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的,继承的概念我们后面再讲,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。定义好了Student类,就可以根据Student类创建出Student的实例,创建实例是通过类名+()实现的: 可以看到,变量bart指向的就是一个Student的实例,后面的0x10a67a590是内存地址,每个object的地址都不一样,而Student本身则是一个类。类:分静态特征和动态特征,静态特征:属性;动态特征:函数可以自由地给一个实例变量绑定属性,比如,给实例bart绑定一个name属性: 由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__方法,在创建实例的时候,就把name,score等属性绑上去: 注意1.特殊方法__init__前后分别有两个下划线!!!2.__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。3.有了__init__方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去: >>> bart = Student('张三', 59)>>> bart.name'张三'>>> bart.score59和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。数据封装面向对象编程的一个重要特点就是数据封装。在上面的Student类中,每个实例就拥有各自的name和score这些数据。我们可以通过函数来访问这些数据,比如打印一个学生的成绩: >>> def print_score(std):... print('%s: %s' % (std.name, std.score))...>>> print_score(bart)Bart Simpson: 59但是,既然Student实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在Student类的内部定义访问数据的函数,这样,就把“数据”给封装起来了。这些封装数据的函数是和Student类本身是关联起来的,我们称之为类的方法:class Student(object): def __init__(self, name, score): self.name = name self.score = score def print_score(self): print('%s: %s' % (self.name, self.score)) 要定义一个方法,除了第一个参数是self外,其他和普通函数一样。要调用一个方法,只需要在实例变量上直接调用,除了self不用传递,其他参数正常传入:>>> bart.print_score() Bart Simpson: 59这样一来,我们从外部看Student类,就只需要知道,创建实例需要给出name和score,而如何打印,都是在Student类的内部定义的,这些数据和逻辑被“封装”起来了,调用很容易,但却不用知道内部实现的细节。封装的另一个好处是可以给Student类增加新的方法,比如get_grade:class Student(object): ... def get_grade(self): if self.score >= 90: return 'A' elif self.score >= 60: return 'B' else: return 'C'同样的,get_grade方法可以直接在实例变量上调用,不需要知道内部实现细节:class Student(object): def __init__(self, name, score): self.name = name self.score = score def get_grade(self): if self.score >= 90: return 'A' elif self.score >= 60: return 'B' else: return 'C' lisa = Student('Lisa', 99) bart = Student('Bart', 59) print(lisa.name, lisa.get_grade()) print(bart.name, bart.get_grade())小结类是创建实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响;方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据;封装:通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节。访问限制在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。但是,从前面Student类的定义来看,外部代码还是可以自由地修改一个实例的name、score属性:>>> bart = Student('Bart Simpson', 59) >>> bart.score 59 >>> bart.score = 99 >>> bart.score 99如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,所以,我们把Student类改一改:class Student(object): def __init__(self, name, score): self.__name = name self.__score = score def print_score(self): print('%s: %s' % (self.__name, self.__score))改完后,对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量.__name和实例变量.__score了:>>> bart = Student('Bart Simpson', 59) >>> bart.__name Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute '__name'这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。但是如果外部代码要获取name和score怎么办?可以给Student类增加get_name和get_score这样的方法:class Student(object): ... def get_name(self): return self.__name def get_score(self): return self.__score如果又要允许外部代码修改score怎么办?可以再给Student类增加set_score方法:class Student(object): ... def set_score(self, score): self.__score = score你也许会问,原先那种直接通过bart.score = 99也可以修改啊,为什么要定义一个方法大费周折?因为在方法中,可以对参数做检查,避免传入无效的参数:class Student(object): ... def set_score(self, score): if 0 <= score <= 100: self.__score = score else: raise ValueError('bad score')需要注意的是,在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__、__score__这样的变量名。有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量:>>> bart._Student__name 'Bart Simpson'但是强烈建议你不要这么干,因为不同版本的Python解释器可能会把__name改成不同的变量名。总的来说就是,Python本身没有任何机制阻止你干坏事,一切全靠自觉。最后注意下面的这种错误写法:>>> bart = Student('Bart Simpson', 59) >>> bart.get_name() 'Bart Simpson' >>> bart.__name = 'New Name' # 设置__name变量! >>> bart.__name 'New Name'表面上看,外部代码“成功”地设置了__name变量,但实际上这个__name变量和class内部的__name变量不是一个变量!内部的__name变量已经被Python解释器自动改成了_Student__name,而外部代码给bart新增了一个__name变量。不信试试:>>> bart.get_name() # get_name()内部返回self.__name 'Bart Simpson'练习请把下面的Student对象的gender字段对外隐藏起来,用get_gender()和set_gender()代替,并检查参数有效性:class Student(object): def __init__(self, name, gender): self.name = name self.gender = gender # 测试: bart = Student('Bart', 'male') if bart.get_gender() != 'male': print('测试失败!') else: bart.set_gender('female') if bart.get_gender() != 'female': print('测试失败!') else: print('测试成功!')继承和多态在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。比如,我们已经编写了一个名为Animal的class,有一个run()方法可以直接打印:class Animal(object): def run(self): print('Animal is running...')当我们需要编写Dog和Cat类时,就可以直接从Animal类继承:class Dog(Animal): pass class Cat(Animal): pass对于Dog来说,Animal就是它的父类,对于Animal来说,Dog就是它的子类。Cat和Dog类似。继承有什么好处?最大的好处是子类获得了父类的全部功能。由于Animal实现了run()方法,因此,Dog和Cat作为它的子类,什么事也没干,就自动拥有了run()方法:dog = Dog() dog.run() cat = Cat() cat.run()运行结果如下:Animal is running... Animal is running...当然,也可以对子类增加一些方法,比如Dog类:class Dog(Animal): def run(self): print('Dog is running...') def eat(self): print('Eating meat...')继承的第二个好处需要我们对代码做一点改进。你看到了,无论是Dog还是Cat,它们run()的时候,显示的都是Animal is running...,符合逻辑的做法是分别显示Dog is running...和Cat is running...,因此,对Dog和Cat类改进如下:class Dog(Animal): def run(self): print('Dog is running...') class Cat(Animal): def run(self): print('Cat is running...')再次运行,结果如下:Dog is running... Cat is running...当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态。要理解什么是多态,我们首先要对数据类型再作一点说明。当我们定义一个class的时候,我们实际上就定义了一种数据类型。我们定义的数据类型和Python自带的数据类型,比如str、list、dict没什么两样:a = list() # a是list类型 b = Animal() # b是Animal类型 c = Dog() # c是Dog类型判断一个变量是否是某个类型可以用isinstance()判断:>>> isinstance(a, list) True >>> isinstance(b, Animal) True >>> isinstance(c, Dog) True看来a、b、c确实对应着list、Animal、Dog这3种类型。但是等等,试试:>>> isinstance(c, Animal) True看来c不仅仅是Dog,c还是Animal!不过仔细想想,这是有道理的,因为Dog是从Animal继承下来的,当我们创建了一个Dog的实例c时,我们认为c的数据类型是Dog没错,但c同时也是Animal也没错,Dog本来就是Animal的一种!所以,在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来就不行:>>> b = Animal() >>> isinstance(b, Dog) FalseDog可以看成Animal,但Animal不可以看成Dog。要理解多态的好处,我们还需要再编写一个函数,这个函数接受一个Animal类型的变量:def run_twice(animal): animal.run() 当我们传入Animal的实例时,run_twice()就打印出:>>> run_twice(Animal()) Animal is running...当我们传入Dog的实例时,run_twice()就打印出:>>> run_twice(Dog()) Dog is running... Dog is running...当我们传入Cat的实例时,run_twice()就打印出:>>> run_twice(Cat()) Cat is running... Cat is running...看上去没啥意思,但是仔细想想,现在,如果我们再定义一个Tortoise类型,也从Animal派生:class Tortoise(Animal): def run(self): print('Tortoise is running slowly...')当我们调用run_twice()时,传入Tortoise的实例:>>> run_twice(Tortoise()) Tortoise is running slowly... Tortoise is running slowly...你会发现,新增一个Animal的子类,不必对run_twice()做任何修改,实际上,任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。多态的好处就是,当我们需要传入Dog、Cat、Tortoise……时,我们只需要接收Animal类型就可以了,因为Dog、Cat、Tortoise……都是Animal类型,然后,按照Animal类型进行操作即可。由于Animal类型有run()方法,因此,传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的run()方法,这就是多态的意思:对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用run()方法,而具体调用的run()方法是作用在Animal、Dog、Cat还是Tortoise对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:对扩展开放:允许新增Animal子类;对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。继承还可以一级一级地继承下来,就好比从爷爷到爸爸、再到儿子这样的关系。而任何类,最终都可以追溯到根类object,这些继承关系看上去就像一颗倒着的树。比如如下的继承树:┌───────────────┐ │ object │ └───────────────┘ │ ┌────────────┴────────────┐ │ │ ▼ ▼ ┌─────────────┐ ┌─────────────┐ │ Animal │ │ Plant │ └─────────────┘ └─────────────┘ │ │ ┌─────┴──────┐ ┌─────┴──────┐ │ │ │ │ ▼ ▼ ▼ ▼ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ Dog │ │ Cat │ │ Tree │ │ Flower │ └─────────┘ └─────────┘ └─────────┘ └─────────┘
-
在 Python 中,将类实例转换为 JSON 字符串通常需要先将对象转换为字典(dict),然后再使用 json.dumps() 方法进行序列化。以下是几种常见的方法:方法 1:手动实现 __dict__ 或自定义方法如果类的属性是简单的键值对,可以直接使用 __dict__ 属性(但需注意它可能包含不需要的属性,如 _private 或方法)。import json class Person: def __init__(self, name, age): self.name = name self.age = age # 创建实例 p = Person("Alice", 30) # 转换为 JSON json_str = json.dumps(p.__dict__) print(json_str) # 输出: {"name": "Alice", "age": 30} 问题:如果类包含非序列化属性(如日期、自定义对象),会抛出 TypeError。方法 2:自定义 to_dict() 方法更灵活的方式是定义一个方法,显式控制哪些属性需要序列化。import json from datetime import datetime class User: def __init__(self, name, created_at): self.name = name self.created_at = created_at # datetime 对象无法直接序列化 def to_dict(self): return { "name": self.name, "created_at": self.created_at.isoformat() # 转换为字符串 } # 创建实例 u = User("Bob", datetime.now()) # 转换为 JSON json_str = json.dumps(u.to_dict()) print(json_str) 方法 3:使用 dataclasses(Python 3.7+)如果使用 dataclass,可以通过 asdict() 函数自动转换。from dataclasses import dataclass, asdict import json @dataclass class Product: id: int name: str price: float # 创建实例 prod = Product(1, "Laptop", 999.99) # 转换为 JSON json_str = json.dumps(asdict(prod)) print(json_str) # 输出: {"id": 1, "name": "Laptop", "price": 999.99} 方法 4:继承 json.JSONEncoder通过继承 JSONEncoder,自定义复杂对象的序列化逻辑。import json from datetime import datetime class CustomEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, datetime): return obj.isoformat() elif hasattr(obj, "__dict__"): return obj.__dict__ else: return super().default(obj) # 其他类型抛出 TypeError class Event: def __init__(self, name, timestamp): self.name = name self.timestamp = timestamp # 创建实例 e = Event("Meeting", datetime.now()) # 使用自定义 Encoder json_str = json.dumps(e, cls=CustomEncoder) print(json_str) 方法 5:使用第三方库(如 marshmallow)对于复杂场景(如验证、嵌套对象),推荐使用库如 marshmallow。from marshmallow import Schema, fields class Artist: def __init__(self, name, genre): self.name = name self.genre = genre class ArtistSchema(Schema): name = fields.Str() genre = fields.Str() # 创建实例 artist = Artist("Tool", "Rock") # 序列化 schema = ArtistSchema() json_str = schema.dumps(artist) print(json_str) # 输出: {"name": "Tool", "genre": "Rock"} 注意事项循环引用:如果对象属性互相引用,会导致无限递归。需手动处理或使用 ignore 参数。性能:频繁序列化时,dataclasses 或 marshmallow 比手动 __dict__ 更高效。安全性:避免直接序列化用户输入的对象,防止信息泄露(如 __dict__ 包含敏感字段)。
-
在 Python 中,你可以使用切片(slice)来获取数组(列表)的最后 3 项。以下是几种常见的方法:方法 1:使用负数索引arr = [1, 2, 3, 4, 5, 6, 7, 8, 9] last_three = arr[-3:] # 从倒数第3项到末尾 print(last_three) # 输出: [7, 8, 9] 方法 2:使用 len() 计算索引arr = [1, 2, 3, 4, 5, 6, 7, 8, 9] last_three = arr[len(arr)-3:] # 等价于 arr[-3:] print(last_three) # 输出: [7, 8, 9] 方法 3:使用 slice 对象(适用于复杂场景)arr = [1, 2, 3, 4, 5, 6, 7, 8, 9] last_three = arr[slice(-3, None)] # None 表示到末尾 print(last_three) # 输出: [7, 8, 9] 注意事项数组长度不足 3 时:如果数组长度小于 3,切片会返回整个数组,不会报错。arr = [1, 2] print(arr[-3:]) # 输出: [1, 2] 空数组:如果数组为空,切片会返回空列表。arr = [] print(arr[-3:]) # 输出: [] 完整示例代码def get_last_three_items(arr): return arr[-3:] # 测试 test_cases = [ [1, 2, 3, 4, 5, 6, 7, 8, 9], # 正常情况 [1, 2], # 长度不足3 [], # 空数组 ["a", "b", "c", "d"] # 非数字数组 ] for arr in test_cases: print(f"数组: {arr}, 最后3项: {get_last_three_items(arr)}") 输出结果数组: [1, 2, 3, 4, 5, 6, 7, 8, 9], 最后3项: [7, 8, 9] 数组: [1, 2], 最后3项: [1, 2] 数组: [], 最后3项: [] 数组: ['a', 'b', 'c', 'd'], 最后3项: ['b', 'c', 'd'] 总结推荐方法:直接使用 arr[-3:],简洁高效。边界情况:无需额外处理,Python 切片会自动处理数组长度不足或为空的情况。
-
Python是一门非常强大的编程语言,它不仅拥有简洁的语法,还包含了许多高级特性,这些特性能够让你的代码更加优雅、高效。今天,我们就一起来深入理解Python中的高级特性,帮助你在编程中更加得心应手。本文讲解了Python的一些高级特性,主要是为了更好的方便你的python操作,还有一些代码的理解,因为有大牛用的就是高级特性。包括迭代器推导式、生成器、装饰器、上下文管理器、函数式编程、多线程和多进程、协程、类型注解、元编程。切片取一个list或tuple的部分元素是非常常见的操作。比如,一个list如下:>>> L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']取前3个元素,应该怎么做?笨办法:>>> [L[0], L[1], L[2]] ['Michael', 'Sarah', 'Tracy']之所以是笨办法是因为扩展一下,取前N个元素就没辙了。取前N个元素,也就是索引为0-(N-1)的元素,可以用循环:>>> r = [] #r:新的空的列表 >>> n = 3 >>> for i in range(n): #0 1 2 ... r.append(L[i]) #追加操作,L[0] L[1] L[2],逐个添加 ... >>> r ['Michael', 'Sarah', 'Tracy']对这种经常取指定索引范围的操作,用循环十分繁琐,因此,Python提供了切片(Slice)操作符,能大大简化这种操作。对应上面的问题,取前3个元素,用一行代码就可以完成切片:#列表名[索引0:结束位置),包左不包右 >>> L[0:3] ['Michael', 'Sarah', 'Tracy']L[0:3]表示,从索引0开始取,直到索引3为止,但不包括索引3。即索引0,1,2,正好是3个元素。如果第一个索引是0,还可以省略:>>> L[:3] ['Michael', 'Sarah', 'Tracy']也可以从索引1开始,取出2个元素出来:>>> L[1:3] ['Sarah', 'Tracy']类似的,既然Python支持L[-1]取倒数第一个元素,那么它同样支持倒数切片,试试:>>> L[-2:] ['Bob', 'Jack'] >>> L[-2:-1] ['Bob']记住倒数第一个元素的索引是-1。切片只能正着切,不管正索引、反索引,都是正切。 切片操作十分有用。我们先创建一个0-99的数列:>>> L = list(range(100)) >>> L [0, 1, 2, 3, ..., 99]可以通过切片轻松取出某一段数列。比如前10个数:>>> L[:10] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]后10个数:>>> L[-10:] [90, 91, 92, 93, 94, 95, 96, 97, 98, 99]前11-20个数:>>> L[10:20] [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]前10个数,每两个取一个:>>> L[:10:2] [0, 2, 4, 6, 8]所有数,每5个取一个:>>> L[::5] [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]甚至什么都不写,只写[:]就可以原样复制一个list:>>> L[:] [0, 1, 2, 3, ..., 99]tuple也是一种list,唯一区别是tuple不可变。因此,tuple也可以用切片操作,只是操作的结果仍是tuple:>>> (0, 1, 2, 3, 4, 5)[:3] (0, 1, 2)字符串'xxx'也可以看成是一种list,每个元素就是一个字符,本质是一个字符列表。因此,字符串也可以用切片操作,只是操作结果仍是字符串:>>> 'ABCDEFG'[:3] 'ABC' >>> 'ABCDEFG'[::2] 'ACEG'在很多编程语言中,针对字符串提供了很多各种截取函数(例如,substring),其实目的就是对字符串切片。Python没有针对字符串的截取函数,只需要切片一个操作就可以完成,非常简单。练习利用切片操作,实现一个trim()函数,去除字符串首尾的空格,注意不要调用str的strip()方法:def trim(s): return s #s[0] s[-1]这两种情况下,如果有一个是空格,则去掉;重新赋值; # 测试: if trim('hello ') != 'hello': print('测试失败!') elif trim(' hello') != 'hello': print('测试失败!') elif trim(' hello ') != 'hello': print('测试失败!') elif trim(' hello world ') != 'hello world': print('测试失败!') elif trim('') != '': print('测试失败!') elif trim(' ') != '': print('测试失败!') else: print('测试成功!')迭代如果给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)。在Python中,迭代是通过for ... in来完成的,而很多语言比如C语言,迭代list是通过下标完成的,比如C代码:for (i=0; i<length; i++) { n = list[i]; }可以看出,Python的for循环抽象程度要高于C的for循环,因为Python的for循环不仅可以用在list或tuple上,还可以作用在其他可迭代对象上。list这种数据类型虽然有下标,但很多其他数据类型是没有下标的,但是,只要是可迭代对象,无论有无下标,都可以迭代,比如dict就可以迭代:>>> d = {'a': 1, 'b': 2, 'c': 3} >>> for key in d: ... print(key) ... a c b因为dict的存储不是按照list的方式顺序排列,所以,迭代出的结果顺序很可能不一样。默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.values(),如果要同时迭代key和value,可以用for k, v in d.items()。由于字符串也是可迭代对象,因此,也可以作用于for循环:>>> for ch in 'ABC': ... print(ch) ... A B C所以,当我们使用for循环时,只要作用于一个可迭代对象,for循环就可以正常运行,而我们不太关心该对象究竟是list还是其他数据类型。 那么,如何判断一个对象是可迭代对象呢?方法是通过collections.abc模块的Iterable类型判断:>>> from collections.abc import Iterable >>> isinstance('abc', Iterable) # str是否可迭代,instance:实例 True >>> isinstance([1,2,3], Iterable) # list是否可迭代 True >>> isinstance(123, Iterable) # 整数是否可迭代 False最后一个小问题,如果要对list实现类似Java那样的下标循环怎么办?Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身:>>> for i, value in enumerate(['A', 'B', 'C']): ... print(i, value) ... 0 A 1 B 2 C上面的for循环里,同时引用了两个变量,在Python里是很常见的,比如下面的代码:>>> for x, y in [(1, 1), (2, 4), (3, 9)]: ... print(x, y) ... 1 1 2 4 3 9列表生成式列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。举个例子,要生成list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]可以用list(range(1, 11)):>>> list(range(1, 11)) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]但如果要生成[1x1, 2x2, 3x3, ..., 10x10]怎么做?方法一是循环:>>> L = [] >>> for x in range(1, 11): ... L.append(x * x) ... >>> L [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]但是循环太繁琐,而列表生成式则可以用一行语句代替循环生成上面的list:>>> [x * x for x in range(1, 11)] [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]写列表生成式时,把要生成的元素x * x放到前面,后面跟for循环,就可以把list创建出来,十分有用,多写几次,很快就可以熟悉这种语法。for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:>>> [x * x for x in range(1, 11) if x % 2 == 0] [4, 16, 36, 64, 100]还可以使用两层循环,可以生成全排列:>>> [m + n for m in 'ABC' for n in 'XYZ'] ['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']三层和三层以上的循环就很少用到了。运用列表生成式,可以写出非常简洁的代码。例如,列出当前目录下的所有文件和目录名,可以通过一行代码实现:>>> import os # 导入os模块,模块的概念后面讲到 >>> [d for d in os.listdir('.')] # os.listdir可以列出文件和目录 ['.emacs.d', '.ssh', '.Trash', 'Adlm', 'Applications', 'Desktop', 'Documents', 'Downloads', 'Library', 'Movies', 'Music', 'Pictures', 'Public', 'VirtualBox VMs', 'Workspace', 'XCode']for循环其实可以同时使用两个甚至多个变量,比如dict的items()可以同时迭代key和value:>>> d = {'x': 'A', 'y': 'B', 'z': 'C' } >>> for k, v in d.items(): ... print(k, '=', v) ... y = B x = A z = C因此,列表生成式也可以使用两个变量来生成list:>>> d = {'x': 'A', 'y': 'B', 'z': 'C' } >>> [k + '=' + v for k, v in d.items()] ['y=B', 'x=A', 'z=C']最后把一个list中所有的字符串变成小写:>>> L = ['Hello', 'World', 'IBM', 'Apple'] >>> [s.lower() for s in L] ['hello', 'world', 'ibm', 'apple']if ... else使用列表生成式的时候,有些童鞋经常搞不清楚if...else的用法。例如,以下代码正常输出偶数:>>> [x for x in range(1, 11) if x % 2 == 0] [2, 4, 6, 8, 10]但是,我们不能在最后的if加上else:>>> [x for x in range(1, 11) if x % 2 == 0 else 0] File "<stdin>", line 1 [x for x in range(1, 11) if x % 2 == 0 else 0] ^ SyntaxError: invalid syntax这是因为跟在for后面的if是一个筛选条件,不能带else,否则如何筛选?另一些童鞋发现把if写在for前面必须加else,否则报错:>>> [x if x % 2 == 0 for x in range(1, 11)] File "<stdin>", line 1 [x if x % 2 == 0 for x in range(1, 11)] ^ SyntaxError: invalid syntax这是因为for前面的部分是一个表达式,它必须根据x计算出一个结果。因此,考察表达式:x if x % 2 == 0,它无法根据x计算出结果,因为缺少else,必须加上else:>>> [x if x % 2 == 0 else -x for x in range(1, 11)] [-1, 2, -3, 4, -5, 6, -7, 8, -9, 10]上述for前面的表达式x if x % 2 == 0 else -x才能根据x计算出确定的结果。可见,在一个列表生成式中,for前面的if ... else是表达式,而for后面的if是过滤条件,不能带else。生成器通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:>>> L = [x * x for x in range(10)] >>> L [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] >>> g = (x * x for x in range(10)) >>> g <generator object <genexpr> at 0x1022ef630>创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator。我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值:>>> next(g) 0 >>> next(g) 1 >>> next(g) 4 >>> next(g) 9 >>> next(g) 16 >>> next(g) 25 >>> next(g) 36 >>> next(g) 49 >>> next(g) 64 >>> next(g) 81 >>> next(g) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration我们讲过,generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。当然,上面这种不断调用next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象:>>> g = (x * x for x in range(10)) >>> for n in g: ... print(n) ... 0 1 4 9 16 25 36 49 64 81所以,我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误。generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:1, 1, 2, 3, 5, 8, 13, 21, 34, ...斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:def fib(max): n, a, b = 0, 0, 1 while n < max: print(b) a, b = b, a + b n = n + 1 return 'done'注意,赋值语句:a, b = b, a + b相当于:t = (b, a + b) # t是一个tuple a = t[0] b = t[1]但不必显式写出临时变量t就可以赋值。上面的函数可以输出斐波那契数列的前N个数:>>> fib(6) 1 1 2 3 5 8 'done'仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。 __name__函数对象有一个__name__属性(注意:是前后各两个下划线),可以拿到函数的名字:#函数名.__name__ >>> now.__name__ 'now' >>> f.__name__ 'now'#只输入函数的名字,可以得到该函数的地址信息>>> fib<function fib at 0x7f9df9edff70>
-
调用python aclllite库的CameraCapture模块的read()函数,报错acl malloc dvpp data failed, dataSize 1382400, error 107002
-
运维中需批量检测 10.0.0.1 到 10.0.0.10 网段的 IP 连通性,要求:使用 subprocess 执行 ping 命令(Windows 用 -n 1 -w 1000,Linux 用 -c1 -W1);循环遍历 10 个 IP,逐个检测;输出每个 IP 的连通状态(“可达” 或 “不可达”);统计并输出最终 “可达 IP 数量” 和 “不可达 IP 数量”。import subprocess import platform def batch_ping_check(): # 初始化统计变量 reachable_count = 0 unreachable_count = 0 # 遍历 10.0.0.1 ~ 10.0.0.10 for ip_last in range(1, 11): ip = f"10.0.0.{ip_last}" # 自动适配系统参数和编码 system = platform.system() if system == "Windows": ping_cmd = ["ping", "-n", "1", "-w", "1000", ip] encoding = "gbk" else: ping_cmd = ["ping", "-c1", "-W1", ip] encoding = "utf-8" # 执行 ping 命令 result = subprocess.run( ping_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False, encoding=encoding ) # 判断连通性并统计 if result.returncode == 0: print(f"✅ {ip} - 可达") reachable_count += 1 else: print(f"❌ {ip} - 不可达") unreachable_count += 1 # 输出统计结果 print("\n" + "="*30) print(f"总检测 IP 数:10") print(f"可达 IP 数:{reachable_count}") print(f"不可达 IP 数:{unreachable_count}") if __name__ == "__main__": batch_ping_check()
-
在 Linux 中监控 CPU、内存、磁盘、网络的指令非常丰富,以下是 最常用、实用的监控指令,按 “指标分类 + 指令详解 + 核心用法” 整理,新手也能快速上手:一、CPU 监控(核心:查看负载、使用率、进程占用)1. top(实时监控,最常用)功能:实时显示系统整体 CPU 负载、进程 CPU 占用排名(默认每 3 秒刷新)。核心用法:top # 直接运行,进入实时监控界面 界面关键信息:第一行:Cpu(s): 20.0%us(用户态 CPU 使用率)、5.0%sy(内核态 CPU 使用率)、75.0%id(空闲 CPU 使用率)。第三行:%Cpu0~%CpuN(多核心 CPU 各自使用率)。进程列:%CPU(单个进程占用 CPU 百分比)。常用快捷键:P:按 CPU 使用率排序(默认正序)。1:显示所有 CPU 核心的详细使用率。q:退出监控。2. htop(top 增强版,更直观)功能:比 top 界面更友好,支持鼠标操作、颜色区分进程状态。核心用法: # 先安装(Ubuntu/Debian):sudo apt install htop# 先安装(CentOS/RHEL):sudo yum install htophtop # 运行后直接查看,默认按 CPU 排序 3. mpstat(查看多 CPU 核心详情)功能:专门统计单个 / 所有 CPU 核心的使用率,适合分析 CPU 负载不均衡问题。核心用法mpstat # 查看所有 CPU 平均使用率mpstat -P ALL # 显示每个 CPU 核心的详细数据(0 代表第一个核心,1 代表第二个,以此类推)mpstat 1 5 # 每 1 秒刷新一次,共刷新 5 次(适合持续监控) 4. pidstat(查看单个进程的 CPU 占用)功能:精准定位某个进程的 CPU 使用情况(避免 top 刷屏)。核心用法: pidstat -u 1 3 # 每 1 秒统计一次所有进程的 CPU 使用率,共 3 次pidstat -u -p 1234 1 3 # 只监控 PID=1234 的进程,每 1 秒刷新,共 3 次 二、内存监控(核心:查看总内存、已用 / 空闲内存、进程内存占用)1. free(快速查看内存使用概况)功能:显示物理内存(RAM)和交换分区(Swap)的总容量、已用、空闲数据。核心用法: free -h # -h:以人类可读单位(GB/MB)显示(推荐)free -m # 以 MB 为单位显示 输出示例: total used free shared buff/cache availableMem: 15Gi 2.3Gi 10Gi 342Mi 3.1Gi 12GiSwap: 19Gi 0B 19Gi 关键指标:available(实际可分配给新进程的内存,含空闲 + 缓存可释放部分),比 free 更能反映真实内存状态。2. top/htop(实时查看进程内存占用)核心用法:运行 top 或 htop 后,关注进程列:%MEM:进程占用物理内存的百分比。VIRT:进程虚拟内存大小(含共享库、交换空间)。RES:进程实际占用的物理内存大小(不含共享库,核心指标)。快捷键:M(按内存使用率排序)。3. vmstat(监控内存 + CPU+IO 整体状态)功能:综合监控内存交换、页面调度、CPU 负载,适合排查内存瓶颈。核心用法: vmstat 1 5 # 每 1 秒刷新一次,共 5 次 关键输出:si(Swap in):从交换分区读入内存的数据量(越大说明内存不足,频繁使用 Swap)。so(Swap out):从内存写入交换分区的数据量(同上,si/so 长期非 0 代表内存紧张)。三、磁盘监控(核心:查看磁盘容量、IO 负载、分区使用)1. df(查看磁盘分区容量)功能:显示所有挂载分区的总容量、已用空间、空闲空间、使用率。核心用法: df -h # -h:人类可读单位(GB/MB),推荐df -T # 显示分区文件系统类型(ext4、xfs 等)df -h /home # 只查看 /home 分区的容量情况 2. du(查看目录 / 文件占用磁盘空间)功能:统计单个目录或文件的磁盘占用大小(df 看分区整体,du 看具体文件 / 目录)。核心用法: du -sh /var/log # 统计 /var/log 目录总占用(-s:只显示总和,-h:人类可读)du -h --max-depth=1 /home # 显示 /home 下一级目录的占用情况(不递归子目录)du -h /home/user/*.log # 统计指定类型文件的占用 3. iostat(监控磁盘 IO 负载)功能:查看磁盘的读写速度、IO 等待时间(判断磁盘是否繁忙)。核心用法: iostat -x 1 5 # -x:显示详细 IO 指标,每 1 秒刷新,共 5 次iostat -x -d sda 1 5 # 只监控 sda 磁盘(如 /dev/sda)的 IO 状态 关键指标:%util:磁盘 IO 使用率(接近 100% 说明磁盘繁忙,可能是瓶颈)。tps:每秒 IO 请求数(读 + 写)。rMB/s/wMB/s:每秒读 / 写数据量(MB)。4. iotop(磁盘 IO 进程排名)功能:类似 top,但按磁盘 IO 使用率排序,精准定位 “谁在占用磁盘 IO”。核心用法: # 安装:sudo apt install iotop(Ubuntu)/ sudo yum install iotop(CentOS)iotop # 运行后查看,默认按 IO 使用率排序iotop -o # 只显示正在进行 IO 操作的进程(过滤空闲进程,更清晰) 四、网络监控(核心:查看网络连接、带宽占用、网卡状态)1. ifstat(查看网卡带宽占用)功能:实时显示每个网卡的收发带宽(字节 / 秒、包 / 秒)。核心用法: # 安装:sudo apt install ifstat(Ubuntu)/ sudo yum install ifstat(CentOS)ifstat # 实时监控所有网卡带宽ifstat -i eth0 1 5 # 只监控 eth0 网卡,每 1 秒刷新,共 5 次 输出示例: eth0 wlan0 KB/s in KB/s out KB/s in KB/s out0.00 0.00 0.00 0.001.20 3.40 0.00 0.00 # 实时收发速率 2. iftop(网络带宽进程排名)功能:按进程 / IP 的网络带宽占用排序,直观查看 “谁在占用网络”。核心用法: # 安装:sudo apt install iftop(Ubuntu)/ sudo yum install iftop(CentOS)iftop -i eth0 # 监控 eth0 网卡的带宽占用 界面快捷键:N:显示 IP 地址(默认显示主机名,按 N 切换)。P:显示端口号(如 80、443)。q:退出。3. netstat(查看网络连接状态)功能:列出所有网络连接(TCP/UDP)、监听端口、进程 PID。核心用法(常用组合参数): netstat -tuln # 查看所有监听的 TCP/UDP 端口(-t:TCP,-u:UDP,-l:监听,-n:数字显示端口)netstat -anp # 查看所有网络连接(含 ESTABLISHED 连接)及对应进程 PID(-a:所有连接,-p:显示进程)netstat -anp | grep 80 # 过滤端口 80 的连接(排查 HTTP 服务) 4. ss(netstat 替代版,更快更高效)功能:与 netstat 功能一致,但性能更好(大并发连接下不卡顿),推荐优先使用。核心用法: ss -tuln # 等价于 netstat -tuln(查看监听端口)ss -anp # 等价于 netstat -anp(查看所有连接+进程)ss -anp | grep 443 # 过滤 HTTPS 端口(443)的连接 5. ping(测试网络连通性)功能:测试与目标 IP / 域名的网络连通性(基于 ICMP 协议)。核心用法: ping baidu.com # 持续 ping 百度,测试连通性ping -c 4 baidu.com # 只 ping 4 次(避免持续刷屏) 五、综合监控工具(一次性监控所有指标)如果想同时监控 CPU、内存、磁盘、网络,推荐用以下工具:1. glances(全能监控工具)功能:一站式监控所有系统指标,支持 Web 界面、远程监控。核心用法: # 安装:sudo apt install glances(Ubuntu)/ sudo yum install glances(CentOS)glances # 本地实时监控(界面含 CPU、内存、磁盘、网络、进程排名)glances -w # 启动 Web 服务,浏览器访问 http://服务器IP:61208 查看监控 总结:常用指令速查表监控指标快速查看指令详细分析指令CPUtop / htopmpstat、pidstat -u内存free -htop(按 M 排序)、vmstat磁盘容量df -hdu -sh 目录磁盘 IOiostat -xiotop网络带宽ifstatiftop网络连接ss -tuln / ss -anpnetstat -anp综合监控glances 根据需求选择即可:快速排查用 top+free -h+df -h+ss;精准定位问题用 pidstat+iotop+iftop。
-
在 Python 中,pop() 是序列类型(列表 list、元组 tuple 不支持,因为元组不可变) 和字典 dict 的方法,但作用逻辑不同 —— 核心是:列表的 pop () 按「索引位置」删除元素,字典的 pop () 按「键(key)」删除元素。下面分场景详细说明(重点是列表,因为你之前关注元组 / 列表,字典是拓展补充):一、列表(list)的 pop ():按「索引位置」删除列表是有序序列,pop() 专门根据「索引值」删除对应位置的元素,且会返回被删除的元素(这是常用特性)。1. 基本用法(带索引参数)语法:list.pop(索引)索引是整数,从 0 开始(正向索引),或 -1 开始(反向索引,-1 代表最后一个元素)若索引超出列表范围,会报错 IndexError示例:python 运行# -*- coding: utf-8 -*-lst = ['a', 'b', 'c', 'd']# 1. 按正向索引删除(删除第2个元素,索引1)deleted = lst.pop(1)print("删除的元素:", deleted) # 输出:bprint("删除后列表:", lst) # 输出:['a', 'c', 'd']# 2. 按反向索引删除(删除最后一个元素,索引-1)deleted = lst.pop(-1)print("删除的元素:", deleted) # 输出:dprint("删除后列表:", lst) # 输出:['a', 'c']# 3. 删除第一个元素(索引0)deleted = lst.pop(0)print("删除的元素:", deleted) # 输出:aprint("删除后列表:", lst) # 输出:['c'] 2. 无参数用法(默认删除最后一个元素)如果 pop() 不传入索引,默认删除列表的「最后一个元素」(等价于 pop(-1)):python lst = [10, 20, 30]lst.pop() # 无参数,删除最后一个元素 30print(lst) # 输出:[10, 20] 3. 注意:元组(tuple)不支持 pop ()元组是不可变类型,无法修改元素,所以没有 pop() 方法,调用会报错: t = ('a', 'b', 'c')t.pop(1) # 报错:AttributeError: 'tuple' object has no attribute 'pop' 二、字典(dict)的 pop ():按「键(key)」删除(拓展补充)字典是键值对结构,pop() 不按位置(字典无序,Python 3.7+ 虽保留插入顺序,但不支持索引访问),而是按「键名」删除,同样会返回被删除的「值(value)」。语法:dict.pop(key, 默认值)必须传入 key:删除对应键值对可选「默认值」:若 key 不存在,不会报错,返回默认值(无默认值则报错 KeyError)示例:dic = {'name': '张三', 'age': 20, 'gender': '男'} # 按 key 删除(删除 'age' 键对应的键值对) deleted_value = dic.pop('age') print("删除的值:", deleted_value) # 输出:20 print("删除后字典:", dic) # 输出:{'name': '张三', 'gender': '男'} # key 不存在时,指定默认值(不报错) deleted_value = dic.pop('height', '未知') print("删除的值:", deleted_value) # 输出:未知 print("删除后字典:", dic) # 输出:{'name': '张三', 'gender': '男'} 总结类型pop () 作用依据核心特点示例列表(list)索引位置(0/-1)有序,按索引删,返回被删元素;无参删最后一个lst.pop(1) 删第 2 个元素字典(dict)键(key)无序,按键删,返回对应值;支持默认值dic.pop('name') 删 name 键元组(tuple)不支持不可变类型,无 pop () 方法调用会报错
-
win32api:对Windows API函数直接访问。win32con:定义了各种Windows常量,用于与其他模块一起使用。警告提示对框通常可以通过弹出一个提示窗口,通过获取用户选择得到返回值后做其他操作,函数 win32api.MessageBox(hwnd, text, title, type)参数:hwnd: 父窗口的句柄。如果为0或None,则消息框没有父窗口。text: 消息框中显示的文本内容。title: 消息框的标题。type: 指定消息框的类型和按钮组合。可以是以下常量或常量的组合:MB_OK---显示“确定”按钮。MB_OKCANCEL---显示“确定”和“取消”按钮。MB_YESNO---显示“是”和“否”按钮。MB_YESNOCANCEL---显示“是”、“否”和“取消”按钮。MB_ICONQUESTION---显示问号图标。MB_ICONINFORMATION---显示信息图标。MB_ICONWARNING---显示警告图标。MB_ICONASTERISK---含图标、固定按钮 提示信息框。win32con.MB_ICONERROR---显示错误图标。MB_HELP---不含图标、可改变按钮 说明信息框。MB_RETRYCANCEL---不含图标,重试信息框。MB_ICONSTOP---显示X号图标。type为常量整数,所以可以直接使用数字:0: 确定1: 确定,取消2: 终止,重试,忽略3: 是,否,取消4: 是,否5: 重试,取消6: 取消,再试一次,继续函数返回值:1: 确定2: 取消3: 终止4: 重试5: 忽略6: 是7: 否10:再试一次11:继续1234import win32api, win32guifrom win32con import *mes = win32api.MessageBox(None, "这是一个测试对话窗口", "警告", MB_RETRYCANCEL)print(mes) # 打印结果为4 可以使用组合按钮,如下,12mes = win32api.MessageBox(None, "测试1", "test", MB_YESNO | MB_ICONQUESTION)print(mes)123456# win32con.IDOK: 用户点击了“确定”按钮。# win32con.IDCANCEL: 用户点击了“取消”按钮。# win32con.IDYES: 用户点击了“是”按钮。# win32con.IDNO: 用户点击了“否”按钮。mes = win32api.MessageBox(None, "测试2", "test", IDOK | IDCANCEL)print(mes)删除和复制文件12win32api.DeleteFile(r'E:\桌面\99\测试图片\1.jpg') # 删除文件win32api.CopyFile(r'E:\桌面\测试文件\23.png', r'E:\桌面\测试文件\123.png') # 复制文件,参数分别是源文件和目标文件获取时间12print(win32api.GetLocalTime()) # 计算机时间(2024, 4, 1, 15, 13, 37, 26, 781)print(win32api.GetSystemTime()) # 格林尼治时间(2024, 4, 1, 15, 5, 37, 26, 781)系统警告提示音MessageBeep() 函数用于使计算机发出声音,通常用于向用户发出警告或通知。1win32api.MessageBeep(type=MB_ICONQUESTION)type 参数可以设置为以下几种预定义的值,这些值决定了播放哪种声音:win32con.MB_OK:系统默认声音,通常是一个短促的“嘟”声。win32con.MB_ICONASTERISK:一个连续的“嘟”声,直到下一个 MessageBeep() 调用。win32con.MB_ICONEXCLAMATION:一个长“嘟”声。win32con.MB_ICONHAND:一个双“嘟”声。win32con.MB_ICONQUESTION:一个“嘟-嘟”声(两个短促的声音)。自定义声音播放Beep()用于通过计算机的主板扬声器(或声卡模拟)发出指定频率和时长的蜂鸣声。与 MessageBeep()(播放系统预定义声音)不同,Beep() 允许自定义声音的频率和持续时间。1win32api.Beep(frequency, duration)参数名类型描述frequencyint声音的频率(单位:赫兹,Hz)。有效范围:37 Hz 到 32767 Hz。262 Hz (中音C)、440 Hz (标准音A)、1000 Hz (高频提示音)durationint声音的持续时间(单位:毫秒,ms)。有效范围:正整数(如 500=0.5秒)。演示一段音符, 1234567891011# 定义音符频率和时长(毫秒)melody = [ (262, 300), # C4 (294, 300), # D4 (330, 300), # E4 (349, 300), # F4 (392, 600), # G4]for freq, dur in melody: win32api.Beep(freq, dur) time.sleep(0.05) # 音符间短暂间隔检索系统级度量信息GetSystemMetrics() 可以返回多种与系统相关的度量值,比如屏幕尺寸、工作区尺寸、系统字体大小等。win32api.GetSystemMetrics() 函数的一些常用参数及其含义:0:返回屏幕宽度(像素)。1:返回屏幕高度(像素)。SM_CXSCREEN(等同于 0):屏幕宽度(像素)。SM_CYSCREEN(等同于 1):屏幕高度(像素)。SM_CXWORK:工作区的宽度(像素),即屏幕宽度减去系统任务栏等区域的宽度。SM_CYWORK:工作区的高度(像素),即屏幕高度减去系统任务栏等区域的高度。SM_CXFULLSCREEN:全屏窗口的宽度(像素)。对于多显示器设置,这通常是主显示器的宽度。SM_CYFULLSCREEN:全屏窗口的高度(像素)。SM_CXFIXEDFRAME:窗口边框的宽度(像素)。SM_CYFIXEDFRAME:窗口边框的高度(像素)。SM_CXSIZEFRAME:窗口可调边框的宽度(像素)。SM_CYSIZEFRAME:窗口可调边框的高度(像素)。SM_CXFRAME:等同于 SM_CXSIZEFRAME。SM_CYFRAME:等同于 SM_CYSIZEFRAME。SM_CXMIN:窗口的最小宽度(像素)。SM_CYMIN:窗口的最小高度(像素)。SM_CXVSCROLL:垂直滚动条的宽度(像素)。SM_CYHSCROLL:水平滚动条的高度(像素)。SM_CYCAPTION:标题栏的高度(像素)。SM_CXBORDER:窗口边框的宽度(像素)。SM_CYBORDER:窗口边框的高度(像素)。SM_CXDLGFRAME:对话框边框的宽度(像素)。SM_CYDLGFRAME:对话框边框的高度(像素)。SM_CYMENU:菜单的高度(像素)。SM_CXFULLSCREEN、SM_CYFULLSCREEN、SM_CXICON、SM_CYICON 等其他度量值。123# 获取屏幕宽度和高度screen_width = win32api.GetSystemMetrics(0)screen_height = win32api.GetSystemMetrics(1)打开系统程序1win32api.WinExec('notepad.exe')win32guiwin32gui:操作Windows图形用户界面,包括窗口、菜单、对话框等。窗口句柄及参数12345678910111213141516import win32guifrom win32con import *hwnd = win32gui.FindWindow(None, "釘釘")if hwnd: print(f'窗口句柄:{hwnd}') print(f'窗口标题:{win32gui.GetWindowText(hwnd)}') print(f'窗口类名:{win32gui.GetClassName(hwnd)}') rect = win32gui.GetWindowRect(hwnd) left, top, right, bottom = rect print("窗口位置:(左:{}, 上:{})".format(left, top)) print("窗口大小:(宽:{}, 高:{})".format(right - left, bottom - top)) parent_hwnd = win32gui.GetParent(hwnd) child_hwnds = [] win32gui.EnumChildWindows(hwnd, lambda hwnd, param: param.append(hwnd), child_hwnds) print("父窗口句柄:", parent_hwnd) print("子窗口句柄:", child_hwnds)向窗口发送命令SendMessage 和 PostMessage 函数用于向窗口发送消息。SendMessage 会等待消息处理完成才返回,而 PostMessage 则立即返回。12# 向窗口发送一个关闭消息(WM_CLOSE)win32gui.SendMessage(hwnd, win32con.WM_CLOSE, 0, 0)修改窗口标题1win32gui.SetWindowText(hwnd, "新的窗口标题") # 修改窗口标题窗口显示win32gui.ShowWindow()函数可以显示、隐藏或最大化、最小化窗口。1win32gui.ShowWindow(hwnd, SW_HIDE) # SW_HIDE表示隐藏窗口其他模块win32com:提供了对COM(Component Object Model)对象的访问,可用于操作如Microsoft Office等应用程序。win32process:用于管理Windows进程。12345# 进程信息获取import win32processprocesses = win32process.EnumProcesses()for process_id in processes: print(process_id)
-
一、类编码约定的基础与价值1.1 为什么需要在类级别强制编码约定类作为Python面向对象编程的基本单元,其编码质量直接影响整个系统的可维护性。在类级别强制编码约定的主要价值体现在:一致性保证:确保团队所有成员编写的类具有统一的结构和风格,减少认知负担。根据实践数据,统一编码风格可以提高代码审查效率40%以上。错误预防:通过约定强制避免常见陷阱,如不恰当的命名、缺少文档字符串或错误的属性设计。研究表明,编码约定可以预防15%-20%的常见编码错误。自动化支持:规范的代码更易于静态分析和自动化检查,与CI/CD管道无缝集成。统一的类约定使自动化测试和代码质量检查更加有效。知识传递:新团队成员能够快速理解代码结构,降低项目入门门槛。良好的类约定作为活文档,显式传递设计意图和用法规范。1.2 PEP 8与Python Cookbook中的类设计原则PEP 8作为Python官方编码规范,为类设计提供了基础指导,而Python Cookbook则提供了实用技巧:命名规范:类名应采用PascalCase风格,首字母大写,如DataProcessor而非data_processor。私有类可用单下划线开头,如_InternalClass。方法组织:公共方法应在前,私有方法在后,魔法方法如__init__应放在类开头。方法之间用空行分隔,增强可读性。文档要求:每个类应有文档字符串,描述其目的、用法和重要属性。PEP 257规定了文档字符串的标准格式。属性设计:实例属性应在__init__中初始化,避免动态添加属性。公共属性应简明扼要,私有属性以单下划线开头。这些原则为类设计提供了基础,但要实现强制约束,需要更高级的技术。二、通过元类强制编码约定2.1 元类基础与约定执行机制元类是Python的高级特性,被称为"类的类",能够在类创建时干预其生成过程。通过元类,我们可以在类定义阶段强制执行编码约定。元类通过继承type并重写__new__或__init__方法来实现对类创建的控制。以下是一个基础示例:123456789101112131415161718192021222324252627282930class EnforcingMeta(type): """强制编码约定的元类示例""" def __new__(cls, name, bases, namespace): # 在类创建前执行约定检查 cls._validate_class_name(name) cls._validate_docstring(name, namespace) cls._validate_methods(namespace) return super().__new__(cls, name, bases, namespace) @staticmethod def _validate_class_name(name): """验证类名是否符合PascalCase约定""" if not name[0].isupper() or '_' in name: raise NameError(f"类名 '{name}' 必须使用PascalCase风格且不含下划线") @staticmethod def _validate_docstring(name, namespace): """验证类是否有文档字符串""" if '__doc__' not in namespace or not namespace['__doc__']: raise ValueError(f"类 '{name}' 必须包含文档字符串") @staticmethod def _validate_methods(namespace): """验证方法命名约定""" for attr_name, attr_value in namespace.items(): if callable(attr_value) and not attr_name.startswith('__'): if not attr_name.islower() or ' ' in attr_name: raise NameError(f"方法名 '{attr_name}' 必须使用snake_case风格")此元类在类创建时验证类名、文档字符串和方法命名,确保符合基本约定。2.2 实现复杂的约定验证对于更复杂的约定,元类可以检查方法签名、属性类型和类结构。以下高级元类示例演示了更全面的验证:12345678910111213141516171819202122232425262728293031323334353637383940import inspectfrom typing import get_type_hints class ComprehensiveEnforcingMeta(type): """全面强制编码约定的元类""" def __init__(cls, name, bases, namespace): super().__init__(name, bases, namespace) # 类创建后的验证 cls._validate_property_types() cls._validate_public_interface() cls._enforce_naming_conventions() def _validate_property_types(cls): """验证类型注解的正确性""" if hasattr(cls, '__annotations__'): for attr_name, attr_type in cls.__annotations__.items(): if not isinstance(attr_type, type) and not hasattr(attr_type, '__origin__'): raise TypeError(f"属性 '{attr_name}' 的类型注解无效") def _validate_public_interface(cls): """验证公共API的完整性""" public_methods = [name for name in dir(cls) if not name.startswith('_') and callable(getattr(cls, name))] if public_methods: # 检查公共方法是否有文档字符串 for method_name in public_methods: method = getattr(cls, method_name) if method.__doc__ is None: raise ValueError(f"公共方法 '{method_name}' 必须包含文档字符串") def _enforce_naming_conventions(cls): """强制命名约定""" for attr_name in dir(cls): if attr_name.startswith('_') and not attr_name.startswith('__'): # 私有成员应以单下划线开头 if not attr_name[1:].islower(): raise NameError(f"私有成员 '{attr_name}' 必须使用snake_case风格")这种元类确保了类在定义时即符合多种约定,大大提高了代码质量。2.3 元类在实际项目中的应用在实际项目中,元类可以用于强制执行领域特定的约定。例如,在Web框架中确保控制器类符合特定结构:123456789101112131415161718192021222324252627282930class ControllerMeta(type): """Web控制器元类,强制RESTful约定""" def __new__(cls, name, bases, namespace): # 确保类名以"Controller"结尾 if not name.endswith('Controller'): raise NameError(f"控制器类名 '{name}' 必须以'Controller'结尾") # 验证HTTP方法的存在和签名 http_methods = ['get', 'post', 'put', 'delete'] for method in http_methods: if method in namespace: func = namespace[method] sig = inspect.signature(func) if 'self' not in sig.parameters: raise TypeError(f"方法 '{method}' 必须包含'self'参数") return super().__new__(cls, name, bases, namespace) # 使用示例class UserController(metaclass=ControllerMeta): """用户资源控制器""" def get(self, user_id): """获取用户信息""" pass def post(self, user_data): """创建用户""" pass这种领域特定的元类确保了项目内部的一致性,减少了配置错误。三、使用装饰器强化类约定3.1 类装饰器基础类装饰器是另一种强制编码约定的有效工具,它在类定义后修改类行为。与元类相比,装饰器语法更简洁,适用于简单的约定强制。基础类装饰器示例:123456789101112131415161718192021def enforce_style(cls): """强制编码风格的类装饰器""" original_init = cls.__init__ def new_init(self, *args, **kwargs): # 在实例化时验证约定 self._validate_instance() original_init(self, *args, **kwargs) cls.__init__ = new_init return cls def validate_interface(cls): """验证类接口的装饰器""" # 检查公共方法命名 for name in dir(cls): if not name.startswith('_') and callable(getattr(cls, name)): if not name.islower() or ' ' in name: raise NameError(f"方法名 '{name}' 必须使用snake_case风格") return cls装饰器可以通过组合实现复杂的约定检查,且语法更直观。3.2 参数化装饰器实现灵活约定对于需要配置的约定,可以使用参数化装饰器:1234567891011121314151617181920212223242526def enforced_class(*, require_doc=True, validate_names=True, check_types=False): """参数化类装饰器工厂""" def decorator(cls): if require_doc and cls.__doc__ is None: raise ValueError("类必须包含文档字符串") if validate_names: for attr_name in dir(cls): if not attr_name.startswith('_'): if not attr_name.islower(): raise NameError(f"公共成员 '{attr_name}' 必须使用小写字母") if check_types and hasattr(cls, '__annotations__'): # 类型验证逻辑 pass return cls return decorator # 使用示例@enforced_class(require_doc=True, validate_names=True)class DataProcessor: """数据处理类""" def process_data(self, input_data): pass参数化装饰器提供了灵活性,允许根据不同场景调整约定严格度。3.3 装饰器与元类的结合使用在实际项目中,可以结合装饰器和元类实现多层次的约定强制:12345678910111213141516class BaseMeta(type): """基础元类""" def __init__(cls, name, bases, namespace): super().__init__(name, bases, namespace) cls._base_validation() # 基础验证 def domain_specific_decorator(cls): """领域特定装饰器""" # 领域逻辑验证 return cls # 结合使用class SpecializedClass(metaclass=BaseMeta): pass SpecializedClass = domain_specific_decorator(SpecializedClass)这种组合方式既保证了基础约定的强制性,又提供了领域特定约定的灵活性。四、强制编码约定的高级技巧4.1 通过描述符控制属性约定描述符协议可以用于强制属性级别的编码约定,如类型检查、取值范围验证等:1234567891011121314151617181920212223242526272829class ValidatedAttribute: """属性验证描述符""" def __init__(self, name, expected_type, required=True): self.name = name self.expected_type = expected_type self.required = required def __get__(self, instance, owner): if instance is None: return self return instance.__dict__.get(self.name) def __set__(self, instance, value): if value is None and self.required: raise ValueError(f"属性 '{self.name}' 是必需的") if value is not None and not isinstance(value, self.expected_type): raise TypeError(f"属性 '{self.name}' 必须为 {self.expected_type} 类型") instance.__dict__[self.name] = value class EnforcedClass: # 使用描述符强制属性约定 name = ValidatedAttribute('name', str, required=True) age = ValidatedAttribute('age', int, required=False) def __init__(self, name, age=None): self.name = name # 自动触发验证 self.age = age描述符提供了细粒度的控制,确保属性级别的约定得到遵守。4.2 利用抽象基类定义接口约定抽象基类(ABC)可以强制子类实现特定接口,确保类层次结构的一致性:123456789101112131415161718192021222324252627282930from abc import ABC, abstractmethodfrom typing import List class DataSource(ABC): """数据源抽象基类,强制接口约定""" @abstractmethod def connect(self, connection_string: str) -> bool: """连接数据源""" pass @abstractmethod def fetch_data(self, query: str) -> List[dict]: """获取数据""" pass @abstractmethod def disconnect(self) -> bool: """断开连接""" pass @classmethod def __subclasshook__(cls, subclass): """检查子类是否实现了所有抽象方法""" if cls is DataSource: required_methods = {'connect', 'fetch_data', 'disconnect'} if all(any(method in B.__dict__ for B in subclass.__mro__) for method in required_methods): return True return NotImplemented抽象基类确保了所有子类都实现必要的接口,维护了类层次的一致性。4.3 通过代码分析工具集成强制约定除了运行时强制,还可以集成静态分析工具在开发阶段实施约定:12345678910111213141516171819202122# 预提交钩子示例,使用flake8和pylint检查编码约定import subprocessimport sys def pre_commit_hook(): """预提交钩子,检查编码约定""" tools = [ ['flake8', '--max-line-length=88', '--select=E,W', '.'], ['pylint', '--disable=all', '--enable=naming-convention', 'src/'] ] for tool in tools: result = subprocess.run(tool, capture_output=True, text=True) if result.returncode != 0: print(f"工具 {tool[0]} 检查失败:") print(result.stdout) sys.exit(1) print("所有编码约定检查通过") if __name__ == "__main__": pre_commit_hook()将静态分析集成到开发流程中,可以在代码提交前捕获约定违规。五、实际应用场景与最佳实践5.1 Web框架中的类约定强制在Web框架开发中,强制类约定可以确保路由、控制器和中间件的一致性:1234567891011121314151617181920212223242526272829303132333435363738class RouteEnforcingMeta(type): """路由强制元类""" def __init__(cls, name, bases, namespace): super().__init__(name, bases, namespace) # 自动注册路由 if hasattr(cls, 'route_prefix') and hasattr(cls, 'get_routes'): routes = cls.get_routes() for route in routes: full_path = f"{cls.route_prefix}{route['path']}" register_route(full_path, route['handler'], route['methods']) class RESTController(metaclass=RouteEnforcingMeta): """RESTful控制器基类""" route_prefix = '/api' @classmethod def get_routes(cls): """子类必须实现此方法返回路由配置""" raise NotImplementedError("子类必须实现get_routes方法") # 具体控制器实现class UserController(RESTController): @classmethod def get_routes(cls): return [ {'path': '/users', 'handler': cls.get_users, 'methods': ['GET']}, {'path': '/users', 'handler': cls.create_user, 'methods': ['POST']} ] @staticmethod def get_users(): return {"users": []} @staticmethod def create_user(): return {"status": "created"}这种设计确保了所有控制器类都符合框架的路由约定,减少了配置错误。5.2 数据模型类中的约定强制在数据密集型应用中,模型类的约定强制可以保证数据一致性和验证逻辑:123456789101112131415161718192021222324252627282930313233343536373839404142class ModelMeta(type): """模型元类,强制数据验证约定""" def __new__(cls, name, bases, namespace): # 自动为有类型注解的属性生成验证逻辑 if '__annotations__' in namespace: annotations = namespace['__annotations__'] for attr_name, attr_type in annotations.items(): if not hasattr(namespace, f'_validate_{attr_name}'): # 自动生成验证方法 namespace[f'_validate_{attr_name}'] = cls._create_validator(attr_name, attr_type) return super().__new__(cls, name, bases, namespace) @staticmethod def _create_validator(attr_name, attr_type): """创建属性验证器""" def validator(self, value): if not isinstance(value, attr_type): raise TypeError(f"属性 {attr_name} 必须为 {attr_type} 类型") return value return validator class BaseModel(metaclass=ModelMeta): """模型基类""" def __init__(self, **kwargs): for key, value in kwargs.items(): if hasattr(self, f'_validate_{key}'): validated_value = getattr(self, f'_validate_{key}')(value) setattr(self, key, validated_value) else: setattr(self, key, value) # 使用示例class User(BaseModel): name: str age: int email: str # 自动获得验证能力user = User(name="Alice", age=25, email="alice@example.com")这种模式确保了数据模型的一致性,自动处理验证逻辑。5.3 测试类中的约定强制在测试框架中,强制约定可以确保测试的一致性和可维护性:123456789101112131415161718192021222324252627282930313233343536373839404142import unittest class TestCaseMeta(type): """测试用例元类""" def __new__(cls, name, bases, namespace): # 验证测试方法命名 test_methods = [name for name in namespace.keys() if name.startswith('test_') and callable(namespace[name])] for method_name in test_methods: if not method_name.replace('_', '').isalnum(): raise NameError(f"测试方法名 '{method_name}' 只能包含字母、数字和下划线") method = namespace[method_name] if method.__doc__ is None: raise ValueError(f"测试方法 '{method_name}' 必须包含文档字符串") return super().__new__(cls, name, bases, namespace) class EnforcedTestCase(unittest.TestCase, metaclass=TestCaseMeta): """强制约定的测试基类""" def setUp(self): """每个测试前的设置""" pass def tearDown(self): """每个测试后的清理""" pass # 具体测试类class TestCalculator(EnforcedTestCase): """计算器功能测试""" def test_addition(self): """测试加法运算""" self.assertEqual(1 + 1, 2) def test_subtraction(self): """测试减法运算""" self.assertEqual(5 - 3, 2)这种约定确保了测试代码的质量和一致性。总结在类中强制规定编码约定是Python高级开发的重要技术,它通过元类、装饰器、描述符等机制,在类定义和实例化阶段实施一致性约束。本文系统性地探讨了各种强制约定的技术方案,从基础实现到高级应用,为读者提供了完整的解决方案。关键技术回顾通过本文的学习,我们掌握了:元类技术:通过干预类创建过程强制执行命名、文档和结构约定装饰器应用:使用类装饰器实现灵活的参数化约定强制描述符协议:在属性级别实施类型验证和业务规则抽象基类:定义接口约定确保类层次结构的一致性工具集成:结合静态分析工具在开发流程中提前发现约定违规核心价值类级别约定强制的核心价值在于其主动预防能力和一致性保证:质量提升:通过自动验证减少人为错误,提高代码质量协作效率:统一规范降低团队协作成本,提高开发效率维护便利:一致的代码结构降低维护难度和风险知识沉淀:约定作为显性知识,促进团队经验传承实践建议在实际项目中实施类约定强制时,建议遵循以下最佳实践:渐进式采用:从关键类开始逐步推广,避免过度工程化平衡严格性:根据项目阶段和团队成熟度调整约定严格程度文档完善:为自定义约定编写清晰文档,说明其目的和用法工具链集成:将约定检查集成到CI/CD管道,实现自动化验证
-
一 什么是 JSONJSON 全称是 JavaScript Object Notation,即 JavaScript 对象表示法,它是一种轻量级的数据交换格式,具有以下特点使用键值对存储数据 类似于 Python 的字典结构清晰 可嵌套支持多种基本数据类型语言无关 可跨平台一个典型的 JSON 示例123456{ "name": "Alice", "age": 25, "is_student": false, "skills": ["Python", "Java", "SQL"]}在 Python 中 这种结构与字典类型非常相似 因此二者可以轻松互转二 JSON 与 Python 数据类型对应关系JSON 类型Python 类型objectdictarrayliststringstrnumberint 或 floattrueTruefalseFalsenullNone这种对应关系让 JSON 数据与 Python 数据结构之间的转换非常自然三 Python JSON 模块常用方法Python 内置的 json 模块提供了四个核心方法方法作用json.dumps()将 Python 对象转换为 JSON 字符串json.loads()将 JSON 字符串解析为 Python 对象json.dump()将 Python 对象写入 JSON 文件json.load()从 JSON 文件读取并转换为 Python 对象四 Python 对象转 JSON 字符串12345678910import json data = { "name": "Alice", "age": 25, "skills": ["Python", "Data Analysis"]} json_str = json.dumps(data, ensure_ascii=False, indent=4)print(json_str)输出结果{ "name": "Alice", "age": 25, "skills": [ "Python", "Data Analysis" ]}参数说明ensure_ascii=False:防止中文被转义indent=4:设置缩进格式 让输出更易读五 JSON 字符串转 Python 对象12345import json json_str = '{"name": "Alice", "age": 25, "skills": ["Python", "Data Analysis"]}'data = json.loads(json_str)print(data["name"])输出Alice六 写入 JSON 文件将数据保存到文件非常常见 例如保存配置或用户信息12345678910111213import json user = { "id": 1001, "name": "Bob", "vip": True, "balance": 99.8} with open("user.json", "w", encoding="utf-8") as f: json.dump(user, f, ensure_ascii=False, indent=4) print("JSON 文件写入完成")执行后 你将在当前目录生成一个名为 user.json 的文件七 从文件中读取 JSON 数据1234567import json with open("user.json", "r", encoding="utf-8") as f: user_info = json.load(f) print(user_info)print(type(user_info))输出{'id': 1001, 'name': 'Bob', 'vip': True, 'balance': 99.8}<class 'dict'>说明从文件中读取的 JSON 已成功转换为 Python 字典对象八 实战 案例 读取配置文件并修改假设我们有一个配置文件 config.json12345{ "version": "1.0", "debug": true, "users": ["admin", "guest"]}我们想读取其中内容 并更新某些字段123456789101112131415import json # 读取配置with open("config.json", "r", encoding="utf-8") as f: config = json.load(f) # 修改参数config["debug"] = Falseconfig["users"].append("developer") # 保存回文件with open("config.json", "w", encoding="utf-8") as f: json.dump(config, f, ensure_ascii=False, indent=4) print("配置文件已更新")九 异常处理与健壮性在处理 JSON 文件时 可能会遇到文件缺失或格式错误 可以使用 try...except 捕获异常1234567891011import json try: with open("data.json", "r", encoding="utf-8") as f: data = json.load(f)except FileNotFoundError: print("文件未找到")except json.JSONDecodeError: print("JSON 格式错误")else: print("读取成功")十 小结JSON 是 Python 与外部世界交互的桥梁 无论是 API 通信 还是数据存储 JSON 都是最常见也最重要的数据格式之一学习要点使用 json.dumps() 与 json.loads() 实现内存中数据的转换使用 json.dump() 与 json.load() 操作文件养成使用 with open() 的好习惯处理异常 提高代码健壮性
推荐直播
-
HDC深度解读系列 - Serverless与MCP融合创新,构建AI应用全新智能中枢2025/08/20 周三 16:30-18:00
张昆鹏 HCDG北京核心组代表
HDC2025期间,华为云展示了Serverless与MCP融合创新的解决方案,本期访谈直播,由华为云开发者专家(HCDE)兼华为云开发者社区组织HCDG北京核心组代表张鹏先生主持,华为云PaaS服务产品部 Serverless总监Ewen为大家深度解读华为云Serverless与MCP如何融合构建AI应用全新智能中枢
回顾中 -
关于RISC-V生态发展的思考2025/09/02 周二 17:00-18:00
中国科学院计算技术研究所副所长包云岗教授
中科院包云岗老师将在本次直播中,探讨处理器生态的关键要素及其联系,分享过去几年推动RISC-V生态建设实践过程中的经验与教训。
回顾中 -
一键搞定华为云万级资源,3步轻松管理企业成本2025/09/09 周二 15:00-16:00
阿言 华为云交易产品经理
本直播重点介绍如何一键续费万级资源,3步轻松管理成本,帮助提升日常管理效率!
回顾中
热门标签