Day12 别再把代码堆成山:类型注解、拆模块,再让对象自己干活
发布时间:2026/11/4 12:15:15 锦皓数字建站

Day12 别再把代码堆成山类型注解、拆模块再让对象自己干活摘要今天的重点是把能跑往好维护推进一步先用Type Hints把变量和函数的意图写清再用模块与包把代码拆开最后进入类、对象、self和魔法方法。结尾用一套能增删改查、会校验成绩的教务系统把零散知识点串成真正能落地的代码。一、类型注解 Type Hints1.1 变量类型注解Python 是动态类型语言变量本身不限制类型。但项目一大光看代码很难判断某个变量到底该装什么。类型注解就是来解决可读性和提前发现问题的。语法很简单变量名:数据类型值fromtypingimportAny a:int695score:float98.5hobby:strPythonflag:boolTruepic:str|NoneNonedata:AnyNonenames:list[str][A,B]phones:set[str]{13309091111}options:dict[str,int]{count:0,total:0}numbers:tuple[int,...](10,20,30)goods:tuple[str,int,int](手机,5999,1)str | None表示字符串或 Nonelist[str]表示元素是字符串的列表tuple[int, ...]表示任意长度的整数元组。Any则表示这里可以放任意类型通常用在你确实无法提前确定数据结构的场景。要记住类型注解只是提示不是强制约束。Python 运行时不会因为你写了int就拒绝字符串但 PyCharm 这类编辑器会提前给你警告。1.2 类型推断如果变量是直接赋值、参与运算或由推导式生成Python 会根据值得到对象的实际类型编辑器也能据此做类型推断这时不用再手写注解a695# 推断为 intscore98.5# 推断为 floatflagTrue# 推断为 bool1.3 函数类型注解函数的参数和返回值也能注解defcount_vowel(string:str)-int:count0forchinstring:ifchinaeiouAEIOU:count1returncountstring: str约束参数- int约束返回值。多个参数同理每个参数后面加上: 类型。配合 Docstring 使用效果更好defcalc_max_min_avg(scores:list[int|float])-tuple[int|float,...]: 计算最高分、最低分、平均分 :param scores: 成绩列表 :return: 最高分、最低分、平均分 max_scoremax(scores)min_scoremin(scores)avg_scoreround(sum(scores)/len(scores),1)returnmax_score,min_score,avg_score二、模块与包2.1 模块是什么在 Python 里一个 .py 文件就是一个模块。模块是程序的基本组织单位里面可以定义变量、函数、类也可以写可执行代码。比如circle_fun.py里定义了两个函数另一个main.py想用就必须先导入# circle_fun.pydefcircle_area(r):return3.14*r*rdefcircle_len(r):return2*3.14*r# main.pyimportcircle_funprint(circle_fun.circle_area(10))2.2 导入模块的几种写法常用的导入方式有五种写法示例调用方式import 模块import randomrandom.randint()import 模块 as 别名import random as rdrd.randint()from 模块 import 功能from random import randintrandint()from 模块 import 功能 as 别名from random import randint as rintrint()from 模块 import *from random import *randint()importrandomasrdprint(rd.randint(1,100))fromrandomimportrandint,shuffle nums[1,2,3,4,5]shuffle(nums)print(nums)import *写起来省事但容易污染命名空间实际项目里不推荐大面积使用。2.3 自定义模块与__all__自己写的.py文件也是模块。可以在模块里定义常量和函数# my_fun.pyPI3.1415926deflog_separator1():print(- *30)deflog_separator2():print( *30)# main.pyimportmy_funprint(my_fun.PI)my_fun.log_separator1()还可以用__all__控制from module import *到底导入哪些内容# my_fun.py__all__[log_separator1,PI]不写__all__时import *会导入所有不以下划线开头的名字写了之后就只导入列表里的。2.4__name__与模块入口模块里经常要写一些测试代码。如果这些测试在模块被导入时也自动执行就会影响其他文件。这时可以用内置变量__name__判断当前模块是不是被直接运行# my_fun.pydeflog_separator():print(- *30)if__name____main__:# 只有直接运行 my_fun.py 时这段代码才会执行log_separator()直接运行python my_fun.py时__name__的值是__main__在别的文件里import my_fun时__name__的值是my_fun。所以把测试代码放进if __name__ __main__:里就能做到直接运行会测试被导入时不打扰别人。2.5 软件包 package当模块多起来就把它们放进文件夹里管理这个文件夹就是包。包里通常有一个__init__.py用来描述包的信息和import *的行为。utils/ ├── __init__.py ├── my_var.py └── my_log.py# utils/__init__.py__all__[my_var,my_log]# main.pyfromutilsimportmy_varprint(my_var.PI)# 或者importutils.my_log utils.my_log.log()注意用from 包名 import *导入全部模块时必须在__init__.py里用__all__指定允许导入的模块列表。包的导入方式可以按需要选择写法示例使用方式import 包名.模块名import utils.my_varutils.my_var.PIfrom 包名 import 模块名from utils import my_varmy_var.PIfrom 包名.模块名 import 功能from utils.my_log import log_separator1log_separator1()from 包名 import *from utils import *需先用__all__指定模块三、面向对象3.1 面向过程 vs 面向对象前面写的购物车管理系统、教务系统基本都是面向过程把需求拆成一步步的流程然后按顺序执行。这种方式直接、适合简单任务但需求一复杂所有逻辑都堆在一起维护会越来越难。面向对象换了个思路关注谁有什么能力把数据和操作打包成对象让对象各司其职。还是盖房子的例子面向过程设计 → 采购 → 施工 → 验收一步一步来面向对象找设计师、找施工队、找监理让不同对象完成各自的工作。3.2 类与对象类是模板、图纸描述一类事物共同的属性和方法对象是根据类创建出来的具体实例。classCar:passc1Car()c1.brand小米c1.modelSU7c1.price238888print(c1.brand,c1.model,c1.price)这种方式是先创建对象再动态加属性能跑但不推荐属性没有在类里声明代码可读性和可维护性都差。类名建议用大驼峰命名法比如UserInfo、EduManagement。3.3__init__与self推荐写法是在类里定义__init__创建对象时自动调用用来给属性赋初始值classCar:def__init__(self,brand,model,price):self.brandbrand self.modelmodel self.priceprice c1Car(小米,SU7,238888)print(c1.__dict__)self表示当前正在创建或使用的对象。调用Car(小米, ...)时Python 会自动把c1传给self所以我们调用时不需要手动传。c1.__dict__会把对象当前的属性以字典形式展示出来调试时很方便。比如这里会看到brand、model、price三个键。__init__属于魔法方法以双下划线开头和结尾不需要手动调用Python 会在合适时机自动执行。3.4 实例方法定义在类里的函数叫方法第一个参数通常是self通过对象调用classCar:def__init__(self,brand,model,price):self.brandbrand self.modelmodel self.pricepricedefstart(self):print(f{self.brand}{self.model}启动了)deftotal_cost(self,discount,tax_rate):returnself.price*discount*(1tax_rate)c1Car(小米,SU7,238888)c1.start()print(c1.total_cost(0.8,0.01))方法内部可以通过self访问对象自己的属性这是实例方法最核心的能力。3.5 魔法方法常见魔法方法方法作用__init__初始化对象__str__print/str()时的友好描述__repr__面向开发者的精确描述__eq__定义比较规则__lt__/__le__/__gt__/__ge__定义大小比较规则classCar:def__init__(self,brand,model,price):self.brandbrand self.modelmodel self.pricepricedef__str__(self):returnf{self.brand}{self.model}{self.price}def__eq__(self,other):returnself.brandother.brandandself.modelother.modelandself.priceother.pricedef__lt__(self,other):returnself.priceother.price没有__str__时直接print(c1)打印的是对象内存地址有了之后输出就变成可读的属性描述。如果类里没有定义__str__print()和str()会退而使用__repr__。所以想同时照顾用户和开发者可以两个都定义。3.6 实例属性与类属性属性分两种实例属性属于每个对象各对象独立类属性属于类本身所有对象共享。classCar:wheel4# 类属性tax_rate0.05# 类属性def__init__(self,brand,model,price):self.brandbrand# 实例属性self.modelmodel self.priceprice c1Car(小米,SU7,238888)c2Car(宝马,X7,438888)print(Car.wheel)# 4Car.wheel6print(c1.wheel)# 6实例没有时找类属性c1.wheel8# 这不是改类属性而是给 c1 新增实例属性print(c2.wheel)# 6print(c1.wheel)# 8查找顺序是先找实例属性找不到再找类属性。3.7 案例面向对象版教务管理系统用面向对象重构教务系统至少需要两个类Student存学生姓名和三科成绩EduManagement管理学生列表提供增删改查方法。classStudent:def__init__(self,name,chinese,math,english):self.namename self.chinesechinese self.mathmath self.englishenglishdef__str__(self):returnf{self.name}语文:{self.chinese}数学:{self.math}英语:{self.english}classEduManagement:def__init__(self):self.stu_list[]deffind_by_name(self,name):forstuinself.stu_list:ifstu.namename:returnstureturnNonedefinput_score(self,subject):scorefloat(input(f{subject}))ifscore0orscore100:print(成绩必须在 0 到 100 之间)returnNonereturnscoredefadd_stu(self):nameinput(姓名)ifself.find_by_name(name)isnotNone:print(学生已存在)returnchineseself.input_score(语文)mathself.input_score(数学)englishself.input_score(英语)ifNonein(chinese,math,english):returnself.stu_list.append(Student(name,chinese,math,english))print(添加成功)defupdate_stu(self):nameinput(姓名)stuself.find_by_name(name)ifstuisNone:print(学生不存在)returnchineseself.input_score(语文)mathself.input_score(数学)englishself.input_score(英语)ifNonein(chinese,math,english):returnstu.chinesechinese stu.mathmath stu.englishenglishprint(修改成功)defdelete_stu(self):nameinput(姓名)stuself.find_by_name(name)ifstuisNone:print(学生不存在)returnself.stu_list.remove(stu)print(删除成功)deffind_one(self):nameinput(姓名)stuself.find_by_name(name)ifstuisNone:print(学生不存在)returnprint(stu)defshow_all(self):ifnotself.stu_list:print(暂无学生信息)returnforstuinself.stu_list:print(stu)defstart(self):whileTrue:opinput(1添加 2修改 3删除 4查询 5全部 6退出)ifop1:self.add_stu()elifop2:self.update_stu()elifop3:self.delete_stu()elifop4:self.find_one()elifop5:self.show_all()elifop6:print(已退出)breakelse:print(不支持的操作)if__name____main__:emEduManagement()em.start()这个案例把类、对象、__init__、self、实例方法、实例属性、模块入口全部用了一遍。input_score()负责统一校验成绩find_by_name()负责按姓名定位学生添加、修改、删除、查询再分别调用它们。以后功能继续增加也不需要把所有逻辑都塞进start()里。四、Day12 技术栈总结把今天的内容压缩成几条类型注解变量: 类型、参数: 类型 - 返回类型只是提示不强制模块一个.py文件就是一个模块用import复用__name__ __main__用来区分直接运行和作为模块导入包文件夹 __init__.py用__all__控制导入类模板对象实例__init__初始化对象self指向当前对象实例方法通过对象调用魔法方法让对象支持打印、比较等操作实例属性各对象独立类属性所有对象共享教务系统案例把增删改查、成绩校验和模块入口串成了一套完整流程。五、写在最后面向对象刚接触时容易觉得抽象但它的核心其实就一句话把数据和行为打包到一起让对象自己负责自己的事。写代码时可以多问自己两个问题这段数据和操作是不是应该属于同一个对象这个方法是谁的动作如果是某个对象自己的行为就应该放进类里。等你能自然地设计出Student、EduManagement这类类并且知道它们之间怎么协作面向对象就算真正入门了。感谢阅览如果这篇对你有帮助欢迎点赞、收藏、关注。面向对象建议多写几个小系统练手教务管理、购物车都是很好的题材。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。