解释器:(其实就是python环境,并且终端输入python启动的环境和pycharm中设置的解释器是不关联的,也就是说在pycharm中设置了A解释器,在terminal中如果不指定A的绝对路径,不一定启动的就是A解释器)
python,python3(兼容中文),Cpython(官方;exit(),ctrl+d),ipython,ipython3(exit,ctrl+d->y)。
ipython支持自动补全,自动缩进,支持bash,shell命令,内置了许多有用的功能和函数.
shell,bash
编译型语言,解释型语言,跨平台。
云服务器。
集成开发环境(IDE),集成了开发软件所需要的所有工具。(图形用户界面,代码编辑器,编译器/解释器,调试器)。
环境变量设置: sysdm.cpl(win+r)
pycharm。
·pycharm中打开一个项目(open),会在目录下新建一个.idea目录,用于保存项目相关信息,例如:解释器版本,项目包含的文件等等。
·第一次打开项目,需耐心等待pycharm对项目进行初始设置。
虚拟环境(.venv),本质是在每个项目里建的一个文件夹,里面安装了该项目运行所需要的东西,比如某些特定版本的库,从而避免了不同项目共同使用系统python环境,造成版本冲突,项目无法正常运行。
虚拟环境管第三方库,不管系统的基础库(基础库->共享系统python)
虚拟环境里的 python.exe(.venv/Scripts/python.exe) = 系统 Python 的 “代理 / 快捷方式 ≠ 新的独立 Python
它的作用:
- 解释器内核 → 用系统的(Miniconda)
- 第三方库位置 → 限定在项目自己的文件夹
- 基础库 → 共享系统的
创建步骤:
Add Python Interpreter → Generate new → Virtualenv 本质等于:
PyCharm 帮你自动执行 python -m venv .venv
生成.venv文件夹,下面有Include,Lib(下面有site-packages//所有安装的包放在这),Scripts(下面有python.exe//项目的解释器)
看到的.venv文件夹本身是空的,但会有这几个目录,以后安装的包,全部放进 Lib/site-packages
uvicorn,用来启动服务器(本机后端服务器或云服务器),不同于python解释器(运行脚本,执行完就结束,不需要开端口,不需要等待别人访问,python本身不会开网站服务),还会监听网络端口,一直运行,不退出(等待浏览器/前端来访问),接收网络请求,把请求交给FastAPI代码处理,处理完再返回给前端。
注释:
单行注释:#(规范:#+" "),解释器不会解释#右侧内容。
多行注释:""" """。
#TODO 注释后面要做的事情 (高亮)
算数运算符:
+,-,*,/,//(取整除),%,**(幂)(幂优先级最高)
*也可用来重复字符串。
'hi'*9-> 'hihihihihihihihihi'
python程序执行原理:操作系统首先让CPU把python解释器加载到内存,python解释器根据语法规则,让cpu从上向下翻译python程序中的代码,cpu负责执行翻译完成的代码(01)。
变量:
在程序内部,为数据在内存中分配的空间叫做变量。程序执行完后,在内存中为这些变量分配的空间被释放。(可用debug验证)
变量=值(每个变量使用前必须赋值,变量赋值以后该变量才会被创建),定义时不需要指定类型。
True(非0即真,参与算数运算时为1),False(0);str,bool,int,long(长整型),float,complex(复数);
字符串,元组,列表,字典 (非数字型)。
python中所有非数字型变量共同特点:
·都是一个序列,可以理解为容器
·取值[]
·遍历 for in
·计算长度,最大值,最小值,比较,删除
·链接(+)和重复(* )
·切片
type(name):查看变量类型。
print(type(name))
解释器遇到超大结果限制转字符串输出时(eg:99**10000):
import sys
sys.set_int_max_str_digits(0)
不同类型变量之间的计算:
1)数字型变量之间可以直接计算(int,float,bool(True=1,False=0)可混合)
2)字符串之间可用+拼接;字符串*数字(字符串的乘法拼接)。
3)数字型变量和字符串之间不能进行其他运算。
4)字符串之间不能用*
键盘输入:
name=input("enter name\n")#input得到的数据,类型都是str
print(name)
类型转换函数:
int(x),float(x)
type(int("123"))
a=int(input("enter:\n"))
print(type(a))
print(chr(97)) # 输出:a
print(chr(65)) # 输出:A
print(chr(49)) # 输出:1 #数字转字符
num = 123 s = str(num) # 结果变成了字符串 "123"
pi = 3.14 s_pi = str(pi) # 结果变成了字符串 "3.14" (数字转字符串)
debug:
断点:执行到此处为止。
开始调试时,第一个断点前的所有程序已执行完毕。
有多个断点时,第一个断点前的代码执行完,下一步执行的是第一个断点和第二个断点之间的代码,往后同理。
debugger框左边的绿色三角,不间断执行到下个断点。shift+f9(调试),shift+f10(运行).
向下箭头,每次执行一行代码。f8(逐行执行)
变量的格式化输出:
%s,%d(有符号十进制整数,%06d表示输出的整数显示位数,如果不到6位,前面使用0补全),%f(浮点数,%.2f表示小数点后只显示两位,若不控制,默认是6位),%%(显示%)。
语法(格式化输出):
print("格式化字符串" % 变量1) #括号里整体还是一个字符串,只不过用后面的填充了前面,形成一个整体。
print("格式化字符串" % (变量1,变量2,……))
name='zhang'
print("my name is %s" % name)
no=0.1
print("num is %06d" % no)
print("num is %.2f%%" % no)#0.10%
print("num is %.2f%%" % no*10)#打印10次字符串
print("num is %.2f%%" % (no*10));#1.00%
标识符:
只能由数字,下划线,字母组成,不能以数字开头,不能是关键字。标识符区分大小写。
查看关键字:
import keyword
print(keyword.kwlist)
条件判断:
name = "zhang"
if name == "zhang":
print(name)
elif name == "li":
print("li")
else:
print("no")
逻辑运算符: and,or,not
tab,shift+tab;
if(()
or()
or()):
随机数的处理:
import random
print(random.randint(1,10))#random.randint(a,b),a<=b,包含a,b,a=b时,只返回a,b这一个数。
while:
i=1
while i<=5:
print("hello")
i+=1;
break: 某一条件满足时,退出循环。
continue: 某一条件满足时,不执行后续代码,跳到条件判断。
print函数增强(换行,制表符,末尾,转义双引): 在默认情况下,print会自动在输出内容末尾加换行。
end="",表示输出内容后不换行。
print("*",end="")#表示输出后,末尾什么都没有
print("*",end="---")#表示输出后末尾是---
print()#换行
print("\n")#换行再换行
print("\t")#**\t 不是固定几个空格,它是 “跳到下一个 8 的倍数位置”**
1234567\t已经 7 个字符 → 补 1 个空格
12345678\t刚好 8 个 → 直接跳到 16,补 8 个空格
print("hello\"hello")#\"可以在控制台输出"
print("name is \"zhang\"")#转义""
模块(文件),包(文件夹,里面包含很多模块,如numpy),工具(模块里的函数,全局变量)。
import hm_01_def
print(hm_01_def.name)
hm_01_def.multi_table()
模块在同级目录或是系统内的目录,直接import即可;如果是其他目录,则:
import sys
sys.path.append(模块所在目录)
import 模块
模块.func();
函数(先定义,后调用):
def multi_table():
i=1
while i<=9:
j=1
while j<=i:
print("%d*%d=%d" % (j,i,i*j),end="\t")
j+=1;
print()
i+=1
函数的名称,模块的名称,都应符合标识符命名规则。
pycharm调试:
F8,stepover,可以单步执行代码,可以把函数调用看做是一行代码直接执行(一次执行完所调用函数内的所有语句)。
F7,stepinto,可以单步执行代码,如果是函数,会进入函数内部(逐步执行函数内语句)。
函数的文档注释:
在定义函数的下方使用"""函数说明""",或用'''函数说明'''(避免多行注释冲突),在函数调用位置ctrl+q可查看(或:view->quik documentation)
"""
def say_hello():
'''打招呼'''
print("hello")
print("hi")
say_hello()
"""
def add(a,b):
c=a+b
return c
print(add(2,3.6))
**字符与字符串:**python中不分字符和字符串,只有字符串,没有字符类型(但有字符这个概念,字符=长度为1的字符串),既可以用双引号,也可以用单引号,不像c++,java那样。
def print_l(c,t):
print(c*t,end="")
c=input("enter a char:\n")
t=int(input("enter a num:\n"))
print_l(c,t)
快捷多行注释:ctrl+/
.pyc文件(项目下的_pycache_目录下的文件,c->comopiled,编译过的文件) 可以提高程序执行速度
把要导入(import)的模块(模块中代码很少修改)编译成二进制文件,避免了对模块中的代码逐行解释与执行,提高了速度。
列表:(可以存储不同类型的数据,但通常存储的都是同种数据)
name_list = ["zhang","wang","zhao"]#从0
print(name_list)
name_list.+tab #查看列表方法
name_list[0] #取值
print(name_list.index("zhang")) #知道数据内容,查找所在索引
name_list[1]="yu" #修改指定位置数据
name_list.append("gao") #append方法向列表末尾追加数据
name_list.insert(1,"yu") #insert 向指定位置插入数据
name_list.extend(name_list) #extend 在列表末尾追加一个列表
name_list.remove("gao") #从列表中删除指定数据,如有多个,只删除第一个
new_list = [x for x in name_list if x != "gao"] #如果想去除列表中的所有"gao"(列表推导式)
name_list.pop() #默认弹出列表中最后一个数据
name_list.pop(3) #弹出指定位置元素
del name_list[2] #del删除列表元素,del 关键字本质是用来把一个变量从内存中删除的,eg. del name
#在日常开发中,建议使用列表提供的方法来删除列表中的数据
print(name_list.count("gao")) #统计列表中数据的个数
name_list.clear() #清空列表
len(name_list) #长度
.sort #升序
.sort(reverse=True) #降序
.reverse #逆序,反转
遍历列表:
for k in name_list:
print(k,end=" ")
**元组(tuple):用()定义,元素不能修改。
info=("zhangsan",19,1.79) #从0
print(type(info))
print(info[0])
print(info.index(19)) #查索引
info=() #定义空元组
info_single = (5) #定义只包含一个元素的元组(这样type是int)
print(type(info_single)) #<class 'int'>
info_single = (5,) #这样才是正确定义只包含一个元素的元组
print(type(info_single)) #<class 'tuple'>
print(info.count("zhangsan"))
print(len(info))
for o in info:
print(o) #python中,可以使用for循环来遍历所有非数字数据类型的变量(列表,元组,字典,字符串)
元组的应用场景:
·函数的参数和返回值,可以让一个函数可以接收任意多个参数或者一次返回多个数据
如果函数返回的类型是元组,小括号可以省略。
info=("zhangsan",19,1.79)
def print_info(person):
print("姓名:%s 年龄:%d 身高:%.3f" % person)
print_info(info)
s="姓名:%s 年龄:%d 身高:%.3f" % info
print(s)
·格式字符串,格式化输出(%后面的())本质上就是一个元组。
·让列表不可以被修改
元组和列表的转换:
·list(元组),tuple(列表)
num_list = [1,2,3,4,5,6,7,8,9]
num_tuple=tuple(num_list) #定义了元组,原来的列表仍然是列表(列表不需要被修改时,可转成元组)
print(num_tuple)
print(num_list)
num_list2=list(num_tuple) #元组需要被修改时可转为列表后再操作
print(num_list2)
print(num_tuple)
字典: 通常用于存储一个个体相关的信息(值可以取任何数据类型,键只能是字符串,数字,元组,即key只能是不可变类型)
字典中,key必须唯一
和列表的区别: 列表有序(怎么存就怎么排,有固定索引0 1 2 3),字典无序(不能靠像列表中的索引取值,只能靠键取值)
xiaoming={
"name": "小明",
"age": 18,
"gender": True,
"height": 1.75
}
print(xiaoming)
print(xiaoming.keys()) #dict_keys(['name', 'age', 'gender', 'height'])
print(xiaoming.values()) #dict_values(['小明', 18, True, 1.75])
print(xiaoming.items())
#dict_items([('name', '小明'), ('age', 18), ('gender', True), ('height', 1.75)])
print(xiaoming["name"])
print(len(xiaoming)) #字典中键值对的数量
for key, value in xiaoming.items():
print(key, value)
for key in xiaoming:
print(key,xiaoming[key])
xiaoming["weight"] = 100 #增
xiaoming["age"] = 19 #改(如果不存在该键值,会增加这个键值对)
xiaoming.pop("name") #删(没有remove方法)
xiaoming.clear() #清空字典
合并字典:
xiaoming={
"name": "小明",
"age": 18,
}
print(xiaoming) #{'name': '小明', 'age': 18}
temp_dic={
"height": 1.75,
"age":20
}
xiaoming.update(temp_dic)
print(xiaoming) #{'name': '小明', 'age': 20, 'height': 1.75}
#如果被合并的字典中包含已经存在的键值对,会覆盖原有的
一个列表中存多个字典:
card_list = [ {"name": "zhangsan", "qq": "12345", "phone": 177}, {"name": "lisi", "qq": "34521", "phone": 178}]
for card in card_list:
print(card)
print(card["name"])
字符串:
string = "hello python"
string1 = "hello "python""
string2= 'hello "python"'
string3= "hello 'python'" # '',""
print(string)
print(string1)
print(string2)
print(string3)
print(len(string))
print(string[6])
string = "hello python"
print(string.count("h")) #统计字符串中某一小字符串出现的次数
print(string.index("llo")) #某一子字符串出现的位置
print(string.upper()) #全转大写
print(string.lower()) #全转小写
print(string.capitalize()) #首字母转大写
print(string.title()) #每个单词的首字母大写
print(string.swapcase()) #大写变小写,小写变大写
print(string.isspace()) #字符串中是否只包含空格(/t/n/r这些不影响空格)
print(string.isalpha()) #是否所有字符都是字母
print(string.isalnum()) #是否所有字符都是字母或数字
#判断数字(这三个方法都不能判断小数)
string = "\u00b3"
print(string.isnumeric()) #是否只包含数字,全角数字,汉字数字……(各种形式)
print(string.isdecimal()) #是否只包含数字,全角数字 (isdecimal只认0-9,最严格,真正的十进制)
print(string.isdigit()) #是否只包含数字,全角数字,(1),\u00b2 (isdigit() :只认0-9和 Unicode 数字字符(如 ① ② ³)
#unicode字符串
print("2\u00b3") #2的3次方
print(string.istitle()) #是否每个单词首字母大写
print(string.isupper()) #是否全是大写
print(string.islower()) #是否全是小写
string = "一千零一"
print(string.startswith("一千"))
print(string.endswith("一"))
print(string.find("零")) #若没找到,返回-1,但index如果指定的字符串不存在,会报错。
str_new = string.replace("一千","一万") #replace方法不会修改原来的字符串
print(str_new)
print(string.rfind("一")) #从右往左查找,但返回的索引仍然是从左往右的正常索引
print(string.rindex("千"))
```
#python能够将一对括号内的代码连接在一起,比如这里都放在一行的话会太长
return ("户型: %s\n总面积:%.2f[剩余:%.2f]\n家具:%s"
% (self.house_type, self.area,
self.free_area, self.item_list))
字符串文本对齐方法:
居中:
poem = [ "登鹳雀楼", "王之涣", "白日依山尽", "黄河入海流", "欲穷千里目", "更上一层楼"]
for poem_str in poem:
print(poem_str.center(9,'-'))
#第一个参数是width,设置的比最长长度的数据长一个偶数长度即可(比如每个数据元素左右添加空格后的总长度),第二个参数是填充字符,可省略。
向左对齐:
poem = [ "登鹳雀楼", "王之涣", "白日依山尽", "黄河入海流", "欲穷千里目", "更上一层楼"]
for poem_str in poem:
print(poem_str.ljust(6,"-"))
#width设置成大于或等于最长数据的长度即可
向右对齐:
poem = [ "登鹳雀楼", "王之涣", "白日依山尽", "黄河入海流", "欲穷千里目", "更上一层楼"]
for poem_str in poem:
print(poem_str.rjust(5,"-"))
#中英文字符显示宽度不同,因此这里中文向右对的不是很齐
去除空白字符(\n,\t,空格):
string = " +python+ "
string_ = " +python + "
print(string.lstrip()) #去除开始的空白字符
print(string)
print(string_.rstrip()) #去除末尾的空白字符
print(string_)
print(string_.strip()) #去除两边的空白字符
poem = [
"\t\n登鹳雀楼",
"王之涣",
"白日依山尽\t\n",
"黄河入海流",
"欲穷千里目",
"更上一层楼"
]
for poem_str in poem:
print(poem_str.strip().center(7))
字符串的拆分与连接:
string.split(str="",num)
#以str为分隔符拆分string,如果num有指定值,则仅分割num+1个子字符串,str默认包含
\t,\r,\n,空格(遇到这些时进行拆分),只把连续的文字或字符拆分到一起。
poem = "\n\t登鹳雀楼\t王之涣\t\n白日依山尽\t\n黄河入海流 欲穷千里目 更上一层楼"
print(poem.split())
#以string为分隔符,将seq中的所有元素(的字符串表示)合并为一个新的字符串。
poem = [
"\n\t登鹳雀楼",
"王之涣",
"白日依山尽\t\n",
"黄河入海流",
"欲穷千里目",
"更上一层楼",
]
print(" ".join(poem)) #\n\t也会进行合并
切片:
切片适用于字符串,列表,元组,字典不能切片) 使用索引值来限定范围,从一个大的字符串中切出小的字符串。
num_str = "0123456789"
print(num_str[2:6]) # 2~5(前闭后开)
print(num_str[2:]) # 到末尾
print(num_str[:6]) # 从起始
print(num_str[:]) # 完整字符串
print(num_str[::2]) # 02468(从头取到尾,每隔一个拿一个)
print(num_str[1::2]) # 13579
print(num_str[1:len(num_str)-1:2])
print(num_str[:-1]) # -1是最后一个位置(倒序),依然是前闭后开
print(num_str[-1::-1]) # 翻转,从最后一个位置向左切片,依然是前闭后开(等同于print(num_str[::-1])
print(num_str[-1::-2]) # 从最后一个位置向左切片,每隔一个
print([1,2,3,4,5,6,7,8,9][::3]) # 列表切片,每隔两个(3-1)
print((1,2,3,4,5,6,7,8,9)[::3]) # 元组切片
python内置函数(作用于字符串,元组,列表,字典等):
#"0"<"A"<"a"
len(item) # 计算容器中元素个数
del(item) # 删除变量 (del有两种方式)
max(item) # 返回容器中元素最大值
min(item) #max,min如果是作用在字典上,只对key进行比较
cmp(item1,item2) # python3.x中已删
st="connectedtopydevdebugger"
print(max(st),min(st),len(st))
del(st)
print([1,1,1]==[2,2,2])
#字符串,列表,元组均可以比较大小(>,<,==,>=,<=), 字典不可以比较大小。
运算符:
# +,*可运用于字符串,列表,元组
print([1,2]+[3,4])
print((1,2)+(3,4))
print("hi"+" python")
print([1,2]*3)
print((1,2)*3)
print("hi "*3)
list = [1,2]
list.extend([3,4])
print(list) # [1, 2, 3, 4]
list.append([5,6]) # 不同于extend方法,append将参数里的李彪整体作为一个元素加到原列表末尾
print(list) # [1, 2, 3, 4, [5, 6]]
# in,not in(成员运算符)可运用于字符串,列表,元组,字典(只能判断键)
print(3 in list)
print(3 not in list)
print(9 in list)
print([5,6] in list)
print('a' in "abc")
dic = {
"name": "zhang"
}
print("name" in dic)
print("zhang" in dic)
完整的for循环语法: 多应用在迭代遍历嵌套的数据类型时 , 如一个元素是多个字典的列表
for 变量 in 集合:
循环体代码
else:
没有通过break退出循环,循环整个结束后,会执行的代码 # 如果break里退出了,就不会执行else
for num in [1,2,3]:
print(num)
else:
print("over") # 1 2 3 over
for num in [1,2,3]:
print(num)
if num == 2:
break # 如果break退出了循环,else里的代码就不会执行
else:
print("over") # 1 2
pass:(空语句)
如果在开发程序时,不希望立刻编写分支内部的代码,可以使用pass关键字,表示一个占位符,能够保证程序的代码结构正确。
添加函数文档注释: 光标放在函数定义那里,点击💡,Insert a docomentation string stub
练习: 名片管理
cards_tools.py
button = 1
id_list = []
def show_choices():
#TODO 显示功能菜单
flag=0
while(not flag):
print("*"*30)
print("1. 新建名片\n2. 显示全部\n3. 查询名片\n\n0. 退出系统")
print("*"*30)
choice = int(input("请输入你想执行的操作:"))
if choice in [0,1,2,3]:
flag=1
else:
print("无该选项")
return choice
def add_card():
id_str = input()
id_dic = dict() # id_dic = {}
message_seq = id_str.split()
id_dic["name"] = message_seq[0]
id_dic["phone"] = message_seq[1]
id_dic["qq"] = message_seq[2]
id_dic["email"] = message_seq[3]
id_list.append(id_dic)
def show_card():
print("姓名\t\t电话\t\t\t\tQQ\t\t\t\t邮箱"+"\n"+"-" * 50)
for person in id_list:
print(person["name"]+"\t\t"+person["phone"]+"\t\t"+person["qq"]+"\t\t"+person["email"])
print("-" * 50)
def remove_card(name):
global id_list
new_list = [x for x in id_list if x["name"] != name] #删除名为name的名片
#函数参数也可改为字典,先找到字典,再在列表中.remove(find_dic)
id_list = new_list
def self_input(person_str,prompt_str):
ans = input(prompt_str+"\n")
if len(ans)==0:
return person_str
else:
return ans
def fix_card(name):
for person in id_list:
if person["name"] == name:
print("-" * 50 )
person["name"] = self_input(person["name"],"姓名: ")
person["phone"] = self_input(person["phone"],"电话: ")
person["qq"] = self_input(person["qq"],"qq: ")
person["email"] = self_input(person["email"],"email: ")
def search_card(name):
""" 查询名片
:param name: 查找的名字
:return: 选择0时返回主菜单
"""
name_list = []
for person in id_list:
name_list.append(person["name"])
if(name not in name_list):
print("没有找到 %s" % name)
else:
print("姓名\t\t电话\t\t\t\tQQ\t\t\t\t邮箱" + "\n" + "-" * 50)
for person in id_list:
if person["name"] == name:
print(person["name"] + "\t\t" + person["phone"] + "\t\t" + person["qq"] + "\t\t" + person["email"])
print("-" * 50+"\n请输入对名片的操作:\n"
"[1] 修改 [2] 删除 [0] 返回上级菜单")
choice = int(input())
match choice:
case 1:
fix_card(name)
case 2:
remove_card(name)
case 0:
return
def action(choice):
global button # global声明后面修改的button是函数外面的全局button,否则不起作用
#对全局变量重新赋值时,需要声明为global
match choice: # match,case语法
case 1:
print("-"*50+"\n"+"请输入你要添加的用户 姓名,电话,qq,email:")
#TODO 执行添加操作(功能完成后删除TODO)
add_card()
case 2:
if len(id_list)==0:
print("用户信息为空")
else:
print("-" * 50 + "\n" + "用户信息如下:")
show_card()
case 3:
print("-"*50+"\n"+"请输入你要搜索的姓名:")
name = input()
search_card(name)
case _:
print("exit")
button = 0
cards_main.py
import cards_tools
receive = 0
while cards_tools.button:
receive = cards_tools.show_choices()
cards_tools.action(receive)
**变量的引用: ** (变量中保存的数据对象的内存地址,这个地址就称为对对象的引用),数据的地址本质上就是一个数字。
python中,变量和数据是分开存储的,数据保存在内存中的一个位置,变量中保存着数据在内存中的地址(变量指向数据且每个变量指向唯一, 但多个变量可以同时指向同一块内存,即共享引用),变量中记录数据的地址就叫做引用。
使用id()函数可查看变量中保存数据所在的内存地址。
变量不等于数据本身,变量等于指向数据的内存地址(引用)。
a = 1
print(id(a))
b = 1
print(id(b))
print(id(a)==id(b)) #print(a is b) (比较引用是否相同)
c = a
print(id(c))
a = 2
print(id(a))
print(id(c))
函数的参数与返回值的传递:
函数的实参/返回值都是通过引用来传递的
def test(num) :
print("在函数内部 %d 对应的内存地址是 %d" % (num,id(num)))
a=10
print("a 变量保存数据的内存地址是 %d" % id(a))
test(a) # 在传入a,调用函数时,函数先开一个num变量,使其和a引用同一个地址(如下图)
def test():
result = "hello"
print("函数要返回数据的内存地址是 %d" % id(result))
return result
a= test()
print("ans变量保存的数据的内存地址是%d" % id(a))
#函数内部,result的引用是"hello"的地址,调用函数时,变量a引用和返回值指向的同一个地址(如下图)
可变和不可变类型:
不可变类型,内存中的数据不允许被修改: 数字类型,字符串,元组
可变类型,内存中的数据可以被修改(内存地址不变): 列表,字典。可变类型数据的变化是通过方法来实现的(pop,remove,clear……)
如果给一个可变类型的变量,赋值了一个新的数据,引用会修改。变量不再对之前的数据引用,改为对新赋值的数据引用。
a = [1,2,3]
print(id(a))
a.append(99)
print(id(a))
a.remove(3)
print(id(a))
a.clear()
print(id(a))
# 以上 列表的地址均未发生变化 且a引用该列表
a = []
print(id(a))
#赋值语句 改变了变量a的引用
d ={
"name": "xiaoming"
}
d['age'] = 18
print(d)
print(id(d))
d.pop("age")
print(d)
print(id(d))
d.clear()
print(d)
print(id(d))
# 以上 字典的地址均未发生变化 且d引用该字典
d = {}
print(id(d))
#赋值语句 改变了变量d的引用
哈希(hash):
python中内置有一个名字叫做hash(0)的函数,接收一个不可变类型数据作为参数,返回结果是一个整数。
哈希是一种算法,用来提取数据的特征码。相同的内容得到相同的结果,不同的内容得到不同的结果。
print(hash(1))
print(hash('hello'))
print(hash((1,)))
局部变量和全局变量:
局部变量是在函数内部定义的变量,只能在函数内部使用(函数执行结束后,函数内部的局部变量会被系统回收,只是临时保存函数内部需要使用的数据)。
全局变量是在函数外部定义的变量(没有定义在某个函数内),所有函数内部都可以使用。为避免混淆,全局变量应该增加g_或者gl_的前缀。
局部变量的生命周期: 变量从被创建到被系统回收的过程。
局部变量在函数执行时才会被创建,函数执行后局部变量被系统回收。局部变量在生命周期内,可以用来存储函数内部临时使用到的数据。
不同的函数,可以定义相同的名字的局部变量,但是彼此之间不会产生影响。
函数不能直接修改全局变量的引用(不声明global时):
a = 10
def f():
#global a
#如果声明了global ,a = 5, 这句就相当于是在修改全局a的引用而不是创建新的局部变量
a=5; # 如果没有声明global,这里是新建局部变量,和外面的a没关系
#函数内部直接赋值 a=5->创建新局部变量,不影响全局。
print(a)
f()
def g():
print(a+1) # 函数里没有定义a,此时会向外去找全局a.
g();
print(a)
利用元组返回多个值:
def mearsure():
temp = 39
witness = 50
return temp,witness
result = mearsure()
print(result)
接收返回元组函数的方式:
def measure():
temp = 39
wetness = 50
return temp,wetness
result = measure()
print(result)
print(result[0],result[1]) # 单独的处理温度或湿度,需要记住索引所指代的内容,不方便
gl_temp,gl_wetness = measure()
print(gl_temp,gl_wetness)
#如果函数返回的类型是元组,同时希望单独处理元组中的元素
#可以使用多个变量,一次接收函数的返回结果
#注意:使用多个变量接受结果时,变量的个数应该和元组中元素的个数保持一致
交换两个变量的值:
a = 20
b = 10
a,b = (b,a) #python专有,用多个变量来接收元组 ()可以省略
print(a,b)
a = a+b
b = a-b #(==a)
a = a-b
print(a,b)
在函数内部针对参数赋值不会影响函数外部实参(看懂上面引用相关内容,下面便可理解):
def f(n):
#global n; 报错:name 'n' is parameter and global
#不能让同一个名字既是函数参数(形参),又是全局变量声明
n = 100 #传入全局n时,这句是将局部n的引用对象变为100,全局n的引用对象依然是101
print(n)
def f2(x):
global n;
n=x;
print(n)
g_n = 99
n = 101
f(g_n)
print(g_n)
f(n) #区分全局n 和 区局n
print(n)
f2(g_n)
print(n)
def d(num,num_list): # 参数可以是可变类型
num = 100
num_list = [1,2,3]
print(num)
print(num_list)
num = 200
num_list = [4,5,6]
d(num,num_list)
print(num,num_list)
如果传递的参数是可变类型,在函数内部,使用方法修改了数据的内容,同样会影响到外部的数据:
def d(num,num_list):
num = 100
num_list.append(666) # 此时,函数调用开的局部变量num_list引用的对象和全局变量num_list相同。
num_list = [1,2,3] #局部num_list引用对象变成[1,2,3],下面的append方法不会再改变全局num_list
num_list.append(4)
print(num)
print(num_list)
num = 200
num_list = [4,5,6]
d(num,num_list)
print(num,num_list)
print(num_list)
*列表变量调用 += 本质上是执行列表变量的extend方法,而不是相加再赋值的操作,不会修改变量的引用:
def f(list_):
#list_+=[7,8,9] # 改了全局
#list_.extend([7,8,9]) #改了全局
list_ = list_+[7,8,9] #相加再赋值,局部list_改了新的引用,所以没改动全局
list_a = [1,2,3,4,5,6]
f(list_a)
print(list_a)
注意对比: 对于不可变类型,即使局部和全局变量的引用对象是一致的,把局部变量引用的值修改,也不会影响到全局变量,因为不可变数据类型只要进行修改,引用对象就一定变了对吗,而可变类型(如列表)不同,因为还可以扩展或是其他操作
eg:
def f(num):
num = 20
num = 10
f(num)
print(num)
缺省参数:
定义函数时,可以给某个参数指定一个默认值,具有默认值的参数就叫做缺省参数
调用函数时,如果没有传入缺省参数的值,则在函数内部使用定义函数时指定的参数默认值
函数的缺省参数,将常见的值设置为参数的缺省值,从而简化函数的调用。
gl_list = [6,3,9]
gl_list.sort() # 默认按照升序排序
print(gl_list)
gl_list.sort(reverse=True) # 只有当需要降序排序时,才需要传递reverse参数(缺省参数)
print(gl_list)
def print_info(name,gender = True):
g_t = "男生" # 假设男生更多
if not gender:
g_t = "女生"
print("%s是%s" % (name,g_t))
print_info("zhang")
print_info("zhang",0)
缺省参数的注意事项:
缺省参数的定义位置:必须保证带有默认值的缺省参数在参数列表末尾
像def print_info(name,gender=True,title):这就是错误的定义
在调用函数时,如果有多个缺省参数,需要指定参数名,这样解释器才能知道参数的对应关系。
def print_info(name,title = "",gender = True):
g_t = "男生" # 假设男生更多
if not gender:
g_t = "女生"
print("[%s]%s是%s" % (title,name,g_t))
print_info("张三",title = "经理")
print_info("豆包",gender = False)
多值参数:
有时需要一个函数能处理的参数个数是不确定的,就可以使用多值参数
python中有两种多值参数:
参数名前增加一个* ** 可以接受元组**;参数名前增加两个* 可以接受字典。
习惯用*args存放元组参数,** kwargs存放字典参数。
def f(num,*args,**kwargs):
print(num)
print(args)
print(kwargs)
f(1,2,3,4,5,6,name = "doubao",age=3,gender = True)
def sum_(*args):
res = 0
for num in args:
res+=num
return res
print(sum_(*range(1,101))) # 解包符号*,*range(……)把range里面的每一个数字,挨个拆出来当成位置参数传入
#相当于sum_(1,2,3,,,100)
#*args会把多个参数打包成元组
print(sum_(1,2,3)) #这里*args接收的不是元组,而是一个个单个数据
#*args把三个单独的参数打包成元组 args(1,2,3)
def _sum_(args):
res = 0
for num in args:
res += num
return res
print(_sum_(1,2,3,)) #此时形参是args而不是*args,因此,传递实参时以元组形式传入
元组和字典的拆包:
在调用带有多值参数的函数时,如果希望:将一个元组变量直接传递给args,或将一个字典变量直接传递给kwargs,就可以使用拆包,简化参数的传递
拆包的方式是:(拆包可以简化元组变量/字典变量的传递)
在元组变量前,增加一个*,在字典变量前,增加两个*
def sum_(*args):
res = 0
for num in args:
res+=num
return res
print(sum_(*range(1,101))) # 解包符号*,*range(……)把range里面的每一个数字,挨个拆出来当成位置参数传入
#相当于sum_(1,2,3,,,100)
print(sum_(*(1,2,3))) # 解包,这里以元组直接传递
然而:
def sum_(*args):
res = 0
# for num in args:
# res += num
return res
print(sum_(*range(1, 101))) # 解包符号*,*range(……)把range里面的每一个数字,挨个拆出来当成位置参数传入
# 相当于sum_(1,2,3,,,100)
print(sum_((1, 2, 3))) # 当函数里没有涉及到把元组当成数字加,此时就算不解包,也不会报错,相当于元组套元组,*args把元组(1,2,3)打包成只有一个元素的元组,这个数据就是(1,2,3),即((1,2,3))。而函数里仍然是res+=num,参数是*args,并且以元组形式sum_((1,2,3))传入时,此时会进行0+(1,2,3),自然报错。
再比如:
def f(*args,**kwargs):
print(args)
print(kwargs)
gl_nums = (1,2,3)
gl_dic = {
"name":"doubao",
"age": 19
}
f(gl_nums,gl_dic) #不解包直接传入的话,和上面的例子同样的道理,会把gl_nums这个元组,gl_dic这个
# 字典,分别作为一个单个的元素传入,然后*args把他俩打包成一个元组,第二个print打印的字典就是空的
f(*gl_nums,**gl_dic) # 正解: 解包传入
f(1,2,3,name="doubao",age=19) # 另一种正确的调用方法
递归: 一个函数内部调用自己(递推+出口)
def f(n):
if(n==0):
return 0
return n+f(n-1)
while True:
n = int(input())
print(f(n))
**面相对象(OOP): **
类名用大驼峰命名法(每一个单词的首字母大写,单词与单词之间没有下划线)
dir内置函数:
使用内置函数dir传入标识符/数据,可以查看对象内的所有属性及方法(变量、数据、函数都是对象)
提示 __方法名__格式的方法是python提供的内置方法/属性
def f():
"""这个函数叫f()"""
print("hello python")
print(dir(f))
print(f.__doc__) # 函数的文档注释
定义简单的类:
类中函数的第一个参数必须是self.
class Cat: # 大驼峰
def eat(self):
print("猫吃鱼")
def drink(self):
print("猫喝可乐")
tom = Cat() # 创建对象: 对象变量 = 类名()
tom.eat()
tom.drink()
print(tom) # 等同于 print("%x" % id(tom))
在面向对象开发中,引用的概念是同样适用的。类创建对象后,tom变量中记录的是对象在内存中的地址,也就是tom变量引用了新的猫对象。使用print输出对象变量,默认情况下,是能够输出这个变量引用的对象是由哪一个类创建的对象,以及在内存中的地址(十六进制表示)(%d可以以10进制输出数字,%x可以以16进制输出数字)
使用同一个类再创建出来一个对象,与之前创建的不是同一个:
class Cat: # 大驼峰
def eat(self):
print("猫吃鱼")
def drink(self):
print("猫喝可乐")
tom = Cat() # 创建对象: 对象变量 = 类名()
tom.eat()
tom.drink()
print(tom) # 等同于 print("%x" % id(tom))
lazy_cat = Cat()
lazy_cat.eat()
lazy_cat.drink()
print(lazy_cat)
lazy_cat2 = lazy_cat # 这两个是同一个
print(lazy_cat2)
在类的外部给对象增加属性:
要给对象设置属性,只需要在类的外部代码中直接通过.设置一个属性即可,但是不推荐这样,因为对象属性的封装应该封装在类的内部。
class Cat: # 大驼峰
def eat(self):
print("猫吃鱼")
def drink(self):
print("猫喝可乐")
tom = Cat() # 创建对象: 对象变量 = 类名()
tom.name = "汤姆"
tom.eat()
tom.drink()
lazy_cat = Cat()
lazy_cat.name = "蓝猫"
lazy_cat.eat()
lazy_cat.drink()
self的使用(创建出一个类对象后,用此对象访问类中属性或方法,可把self看成这个对象来理解):
在类封装的方法内部,self就表示当前调用方法的对象自己,调用方法时,程序员不需要传递self参数,在方法内部可以通过self.访问对象的属性,也可以通过self.调用其他的对象方法。
class Cat: # 大驼峰
def eat(self):
print("%s吃鱼" % self.name)
def drink(self):
print("%s喝可乐" % self.name)
tom = Cat() # 创建对象: 对象变量 = 类名()
tom.name = "汤姆"
tom.eat()
tom.drink()
lazy_cat = Cat()
lazy_cat.name = "蓝猫"
lazy_cat.eat()
lazy_cat.drink()
print(lazy_cat)
lazy_cat2 = lazy_cat # 这两个是同一个
print(lazy_cat2)
初始化方法:
当使用类名()创建对象时,会自动:为对象在内存中分配空间--创建对象,为对象的属性设置初始值--初始化方法init,__init__是对象的内置方法。__init__方法是专门用来定义一个类具有哪些属性的方法。
class Cat:
def __init__(self):
print("这是一个初始化方法")
tom = Cat()
#使用类名()创建对象时,会自动调用初始化方法__init__
在初始化方法内部定义属性:
class Cat:
def __init__(self):
print("这是一个初始化方法")
name = input("enter you name\n")
self.name = name
def eat(self):
print("%s吃鱼" % self.name)
tom = Cat()
#使用类名()创建对象时,会自动调用初始化方法__init__
tom.eat()
print(tom.name)
lazy_cat = Cat()
lazy_cat.eat()
print(lazy_cat.name)
使用参数设置属性初始值:
class Cat:
def __init__(self,name):
print("这是一个初始化方法")
self.name = name
def eat(self):
print("%s吃鱼" % self.name)
tom = Cat("tom")
#使用类名()创建对象时,会自动调用初始化方法__init__
tom.eat()
print(tom.name)
lazy_cat = Cat("lanmao")
lazy_cat.eat()
print(lazy_cat.name)
__del__方法:
当使用类名()创建对象后,为对象分配完空间后,自动调用__init__方法,当一个对象被从内存中销毁前,会自动调用__del__方法,如果希望在对象被销毁前,再做一些事情,可以考虑一下__del__方法。
一个对象从调用类名()创建,生命周期开始,一个对象的__del__方法一旦被调用,生命周期结束,在对象的生命周期内,可以访问对象属性,或者让对象调方法。
class Cat:
def __init__(self,name):
self.name = name
print("%s come" % name)
def __del__(self):
print("%s go" % self.name)
tom = Cat("tom") #tom是一个全局变量
#使用类名()创建对象时,会自动调用初始化方法__init__
#print(tom.name)
del tom # 此时tom go打印在虚线上方,因为tom对象此时要被销毁,调用__del__
print("-"*50)
#对象被内存销毁时才会调用__del__方法,因此tom go在虚线下方,也就是程序要运行结束时才打印。
__str__方法:
如果在开发中,希望使用print输出对象变量时,能够打印自定义的内容(默认打印的是对象所属的类和内存地址),就可以利用str这个内置方法了。
注意:__str__方法必须返回一个字符串
封装示例(跑步):
class Person:
def __init__(self,name,weight):
#self.属性 = 形参
self.name = name
self.weight = weight
def __str__(self):
return "my name is %s,my weigh is %.2f kg" % (self.name,self.weight)
def run(self):
self.weight -= 0.5
print("%s weight down 1 kg,now is %.2f kg" % (self.name,self.weight))
def eat(self):
self.weight += 1
print("%s weight up 1 kg,now is %.2f kg" % (self.name,self.weight))
xiaoming = Person("xiaoming",75)
xiaoming.run()
xiaoming.eat()
xiaomei = Person("xiaomei",65)
xiaomei.eat()
xiaomei.run()
print(xiaoming)
封装案例(家具):
class HouseItem:
def __init__(self, name, area):
self.name = name
self.area = area
def __str__(self):
return "[%s] 占地 %.2f" % (self.name, self.area)
def __repr__(self):
return self.__str__()
bed = HouseItem("席梦思", 4)
chest = HouseItem("衣柜",2)
table = HouseItem("餐桌",1.5)
# print(bed)
# print(chest)
# print(table)
class House:
def __init__(self,house_type,area):
self.house_type = house_type
self.area = area
self.free_area = area
self.item_list = []
def __str__(self):
# 注意这里要把家具列表中的家具名字提取出来放在一个临时列表中
#因为%s能接收元素是字符串的列表,但不能接收元素是类对象的列表,如果传的是self.item_list
#%s收到的只是对象的地址
str_list = [item.name for item in self.item_list]
#python能够将一对括号内的代码连接在一起
return ("户型: %s\n总面积:%.2f[剩余:%.2f]\n家具:%s"
% (self.house_type, self.area,
self.free_area, str_list))
def add_item(self,item):
print("要添加 %s" % item)
self.item_list.append(item)
self.free_area -= item.area
my_home = House("两室一厅",60)
print(my_home)
my_home.add_item(bed)
print(my_home)
my_home.add_item(chest)
print(my_home)
一个对象的属性可以是另外一个类创建的对象:
在定义属性时,如果不知道设置什么初始值,可以设置为None,None关键字表示什么都没有,可以将None赋值给任意一个变量
身份运算符:
用于比较两个对象的内存地址是否一致————是否是对同一个对象的引用,python中针对None比较时,建议使用is,is not 判断,x is y 类似 id(x)==id(y),x is not y 类似id(x)!=id(y).
is与==区别:
is用于判断两个变量引用对象是否为同一个,==用于判断引用变量的值是否相等。
a = [1,2,3]
print(id(a))
b = [1,2]
print(id(b))
b.append(3)
print(id(b))
print(a is b)
print(a==b)
士兵突击:
class Gun:
def __init__(self,model):
self.model = model
self.bullet_count = 0
def add_bullet(self,count):
self.bullet_count += count
def shoot(self):
if self.bullet_count <= 0:
print("%s 没有子弹了" % self.model)
return
self.bullet_count -= 1
print("%s biubiubiu rest: %d" % (self.model,self.bullet_count))
class Sodier:
def __init__(self,name):
self.name = name
self.gun = None #新兵没有枪
def fire(self):
if self.gun is None:
print("%s 没有枪" % self.name)
return
else:
print("%s 开枪了" % self.name)
self.gun.add_bullet(50)
self.gun.shoot()
ak47 = Gun("ak47")
xusanduo = Sodier("xusanduo")
xusanduo.gun = ak47
xusanduo.fire()
print(xusanduo.gun)
私有属性和私有方法:
对象的某些属性或方法,可能只希望在对象的内部被使用,而不希望在外部被访问到。在定义属性或方法时,在属性名或者方法名前增加两个下划线,定义的就是私有属性或方法
class Women:
def __init__(self,name):
self.name = name
#self.age = 20
self.__age = 20
# def secret(self):
# #print("%s的年龄是%d" % (self.name, self.age))
# print("%s的年龄是%d" % (self.name,self.__age))
# #在对象的方法内部,是可以访问对象的私有属性的
def __secret(self):
#print("%s的年龄是%d" % (self.name, self.age))
print("%s的年龄是%d" % (self.name,self.__age))
#在对象的方法内部,是可以访问对象的私有属性的
xiaomei = Women("xiaomei")
xiaomei.secret()
#print(xiaomei.__age) #私有属性,在外界不能够被直接访问
xiaomei.__secret()
#私有方法,同样不允许在外界直接访问
伪私有属性和私有方法:
python中没有真正意义的私有,在给属性、方法命名时,实际是对名称做了一些特征处理,使得外界无法访问到吗(在名称前面加上_类名=>_类名__名称)
如,在外界访问上面类内的私有属性和私有方法,可如下操作:
print(xiaomei._Women__age)
xiaomei._Women__secret()
继承:
实现代码的重用,相同的代码不需要重复的编写。子类拥有父类的所有方法和属性 父类、基类; 子类,派生类。继承具有传递性,C类从B类继承,B类从A类继承,那么C类就具有B类和A类所有的属性和方法。
class 类名(父类名):
pass
class Animal:
def eat(self):
print("eat")
def drink(self):
print("drink")
def run (self):
print("run")
def sleep(self):
print("sleep")
class Cat(Animal):
def catch(self):
print("catch")
class Dog(Animal):
def bark(self):
print("bark")
class XiaoTiaoQuan(Dog):
def fly(self):
print("fly")
X = XiaoTiaoQuan()
X.eat()
X.bark()
X.fly()
C = Cat()
#X.catch() #哮天犬和猫之间没有直接或间接的继承关系,因此不能调用猫类的方法。
方法的重写:
当父类的方法实现不能满足子类需求时,可以对方法进行重写(覆盖父类的方法或对父类方法进行扩展),即在子类中定义一个和父类同名的方法并且实现。
1.覆盖父类方法
class Animal:
def eat(self):
print("eat")
def drink(self):
print("drink")
def run (self):
print("run")
def sleep(self):
print("sleep")
class Dog(Animal):
def bark(self):
print("bark")
class XiaoTiaoQuan(Dog):
def fly(self):
print("fly")
def bark(self):
print("super bark")
xtq = XiaoTiaoQuan()
xtq.bark()
#如果子类中重写了父类的方法,在使用子类对象调用方法时,会调用子类中重写的方法。
2.扩展父类方法:
1>在子类中重写父类的方法
2>在需要的位置使用super().父类方法类调用父类方法的执行
3>代码其他的位置针对子类的需求,编写子类特有的代码实现
class Animal:
def eat(self):
print("eat")
def drink(self):
print("drink")
def run (self):
print("run")
def sleep(self):
print("sleep")
class Dog(Animal):
def bark(self):
print("bark")
class XiaoTiaoQuan(Dog):
def fly(self):
print("fly")
def bark(self):
print("super bark")
super().bark()
print("@#$%^&**&^$%$")
xtq = XiaoTiaoQuan()
xtq.bark()
#如果子类中重写了父类的方法,在使用子类对象调用方法时,会调用子类中重写的方法。
super:
在python中,super是一个特殊的类,super()就是使用super类创建出来的对象,最常使用的场景就是在重写父类方法时,调用在父类中封装的方法实现。
调用父类方法的另外一种方式:
父类名.方法(self) # 这种方法不推荐使用,因为如果父类名修改了,意味着所有调用的地方都需要修改
父类的私有属性和私有方法:
子类对象不能在自己的方法内部,直接访问父类的私有属性或私有方法,子类对象可以通过父类的公有方法间接访问到私有属性或私有方法(子类或其他外界只要调用的是父类的公有方法,该公有方法中无论有没有访问父类私有属性或方法,都不会有问题)。
私有属性、方法是对象的隐私,不对外公开,外界以及子类都不能直接访问。私有属性、方法通常用于做一些内部的事情。
class A:
def __init__(self):
self.num1 = 100
self.__num2 = 200;
def __test(self):
print("私有方法 %d %d" % (self.num1,self.__num2))
def test(self):
print("父类的公有方法 %d" % self.__num2)
class B(A):
def demo(self):
# print("访问父类的私有属性 %d" % self.__num2)
#self.__test() # 在外界不能直接访问对象的私有属性/调用私有方法
print("子类调用")
self.test()
# a = A()
# print(a._A__num2)
b = B()
print(b)
# print(b.__num2) # 在外界不能直接访问对象的私有属性/调用私有方法
# b.__test()
#b.demo()
b.test()
b.demo()
多继承:
子类可以拥有多个父类,并具有所有父类的属性和方法(如孩子会继承父亲和母亲的特性)
class 子类名(父类名1,父类名2,……)
pass
class A:
def test(self):
print("test方法")
class B:
def demo(self):
print("demo方法")
class C(A,B):
pass
c = C()
c.test()
c.demo()
多继承的使用注意事项:
如果不同的父类中存在同名的方法,子类对象在调用方法时,会调用哪一个父类方法呢?开发时应避免这种容易产生混淆的情况,如果父类之间存在同名的属性或者方法,应该尽量避免使用多继承。、
MRO——方法搜索顺序:
python中针对类提供了一个内置属性__mro__,可以查看方法搜索顺序,主要用于在多继承时判断方法、属性的调用路径。
class A:
def test(self):
print("Atest方法")
def demo(self):
print("Ademo方法")
class B:
def demo(self):
print("Bdemo方法")
def test(self):
print("Btest方法")
class C(A,B):
pass
c = C()
c.test()
c.demo()
#确定C类对象调用方法的顺序
print(C.__mro__)
新式类和旧式(经典)类:
object是python为所有对象提供的基类,提供一些内置的属性和方法,可以是用dir函数查看。新式类,即以object为基类的类,推荐使用;经典类,即不以object为基类的类,不推荐使用。
python3.x中定义的类如果没有指定父类,会默认使用object作为该类的基类——python3.x中定义的类都是新式类。python2.x中定义的类如果没有指定父类,则不会以object作为基类。
新式类和经典类在多继承时——会影响到方法的搜索顺序。
class A(object):
pass
class B:
pass
a = A()
b = B()
print(dir(a))
print(dir(b)) # 默认object为其基类
多态:
不同的子类对象调用相同的父类方法,产生不同的执行结果。多态可以增加代码的灵活度,以继承和重写父类方法为前提
class Dog:
def __init__(self,name):
self.name = name
def game(self):
print("dog play")
class Person:
def __init__(self,name):
self.name = name
def game_with_dog(self,dog):
print("play with dog")
dog.game()
class XiaoTianQuan(Dog):
def __init__(self,name):
self.name = name
def game(self):
print("fly dog play")
xiaoming = Person("xiaoming")
xiaohuang = Dog("xiaohuang")
xiaotianquan = XiaoTianQuan("xiaotianquan")
xiaoming.game_with_dog(xiaotianquan)
xiaoming.game_with_dog(xiaohuang)
术语————实例:
创建出来的对象叫做类的实例,创建对象的动作叫做实例化,对象的属性叫做实例属性,对象调用的方法叫做实例方法。
每个对象都有自己独立的内存空间,保存各自不同的属性。多个对象的方法,在内存中只有一份,在调用方法时,需要把对象的引用传递到方法内部。
类是一个特殊的对象:
python中一切皆对象,class AAA:定义的类属于类对象,obj1 = AAA()属于实例对象,在程序运行时,类同样会被加载到内存,类是一个特殊的对象,即类对象,类对象在内存中只有一份,一个类可以创建出多个对象实例。除了封装实例的属性和方法外,类对象还可以拥有自己的属性和方法,即类属性,类方法。通过**类名.**的方式可以访问类的属性或者调用累的方法
类属性和实例属性:
类属性就是给类对象中定义的属性,通常用来记录与这个类相关的特征,类属性不会用于记录具体对象的特征。
示例需求:定义一个工具类,每件工具都有自己的name,需求————知道使用这个类,创建了多少个工具对象?
属性的获取机制:
class Tool:
count = 0
#定义类属性,记录创建工具对象的总数
def __init__(self,name):
self.name = name
Tool.count += 1
tool1 = Tool("斧头")
tool2 = Tool("榔头")
print(Tool.count)
print(tool1.count)
注意:
如果使用对象.类属性 = 值 赋值语句,只会给对象添加一个属性,而不会影响到类属性的值
class Tool:
count = 0
#定义类属性,记录创建工具对象的总数
def __init__(self,name):
self.name = name
Tool.count += 1
tool1 = Tool("斧头")
tool2 = Tool("榔头")
print(Tool.count)
print(tool1.count)
tool1.count = 99 # 这里是在类外对斧头这个对象增加属性count,赋值99
print(Tool.count)
print(tool1.count) # 这里访问的便是斧头的count属性,而不是类属性
类方法:
类方法就是针对类对象定义的方法,在类方法内部可以直接访问类属性或者调用其他的类方法。
@classmethod
def 类方法名(cls):
pass
类方法需要用修饰器**@classmethod来标识,告诉解释器这是一个类方法,类方法第一个参数应该是cls**,由哪一个类调用的方法,方法内cls就是哪一个类的引用,这个参数和实例方法的第一个参数是self类似,通过类名. 调用类方法,在方法内部,可以通过cls. 访问类属性,或调用其他类方法
实例方法能访问实例属性和类属性,类方法只能访问类属性,不能访问实例属性。
class Tool:
count = 0
#定义类属性,记录创建工具对象的总数
def __init__(self,name):
self.name = name
Tool.count += 1
@classmethod
def show_tool_count(cls):
print(cls.count)
tool1 = Tool("斧头")
tool2 = Tool("榔头")
Tool.show_tool_count()
静态方法:
如果需要再类中封装一个方法,这个方法:既不需要访问实例属性或者调用实例方法,也不需要访问类属性或者调用类方法,那么可以把这个方法封装成一个静态方法。
@staticmethod
def 静态方法名():
pass
class Dog:
@staticmethod # 告诉解释器这是静态方法
def run(): # 没有参数
# 不访问实例属性/类属性,不调用实例方法/类方法(如果要访问或调用,
#必须通过self.或cls.,即必须是实例方法或者类方法,而静态方法没有self或cls
print("dog run")
Dog.run()
方法综合案例:
class Game:
top_score = 0
def __init__(self,player_name):
self.player_name = player_name
@classmethod
def show_top_score(cls):
print(cls.top_score)
@staticmethod
def show_help():
print("game help")
def start_game(self):
print("%s start game" % self.player_name)
Game.show_help()
Game.show_top_score()
game = Game("xiaoming")
game.start_game()
确定方法类型:
实例方法————方法内部需要访问实例属性(self.),类方法————方法内部只需要访问类属性,静态方法————方法内部,不需要访问实例属性和类属性,即需要访问实例属性,又需要访问类属性————实例方法(因为访问实例属性只能通过self.,但访问类属性除了通过cls.还可以通过类名.)
设计模式:
针对某一特定问题的成熟的解决方案
单例设计模式:
目的:让类创建的对象,在系统中只有唯一的一个实例,每一次执行类名()返回的对象,内存地址是相同的
__new__方法:
__new__是一个由object基类提供的内置的静态方法,使用类名()创建对象时,python解释器首先会调用__new__方法,__new__方法是在实例化时为实例分配内存空间,并返回引用(这个实例的内存地址),python解释器获得对象的引用后,传给__init__第一个参数self,对实例进行初始化
重写__new__方法:
__new__方法是特设的静态方法,调用父类时必须手动传cls,普通静态方法不需要cls,所以不用传,__new__需要cls(要知道创建哪个类的对象,要分配多大内存),它要给类创建对象
class MusicPlayer:
def __new__(cls,*args,**kwargs):
#创建对象时,new方法会被自动调用
print("new")
#调父类new方法,为对象分配空间,并返回对象引用
#必须返回对象引用,否则python解释器就不会调用初始化方法
return super().__new__(cls)
def __init__(self):
print("播放器初始化")
player = MusicPlayer()
print(player)
player2 = MusicPlayer()
print(player2)
单例设计模式的实现:
class MusicPlayer:
#类属性
instance = None
init_flag = 0 # 让初始化只进行一次
def __new__(cls,*args,**kwargs):
#创建对象时,new方法会被自动调用
print("new")
if cls.instance is not None:
return cls.instance
#调父类new方法,为对象分配空间,并返回对象引用
#必须返回对象引用,否则python解释器就不会调用初始化方法
cls.instance = super().__new__(cls)
return cls.instance
def __init__(self):
if MusicPlayer.init_flag == 0:
print("播放器初始化") # 初始化操作
MusicPlayer.init_flag = 1
else:
return
player = MusicPlayer()
print(player)
player2 = MusicPlayer()
print(player2)
异常的概念:
程序在运行时,如果python解释器遇到一个错误,会停止程序的执行,并提示一些错误信息,这就是异常。程序停止执行并且提示错误信息这个动作,称之为:抛出异常。
开发时,很难将所有特殊情况都处理的面面俱到,通过异常捕获了可以针对突发事件做集中的处理,从而保证程序的稳定性和健壮性。
try:
# 不能确定正确执行的代码(若没有异常,则只执行try里的代码)
num = int(input("enter a integer:\n")) # 字母是不可以转为int的
print(num)
except:
#错误的处理代码(如果出现了异常,来这里执行)
print("error")
错误类型捕获:
在程序执行时,可能会遇到不同类型的异常,并且需要针对不同类型的异常,做出不同的相应,此时就需要捕获错误类型了。
try:
#尝试执行的代码
pass
except 错误类型1:
#针对错误类型1,对应的代码处理
pass
except (错误类型2,错误类型3):
#针对错误类型2和3,对应的代码处理
pass
except Exception as result:
print("未知错误 %s" % result)
当python解释器抛出异常时,最后一行错误信息的第一个单词就是错误类型
try:
num = int(input("enter a integer:\n"))
print(8/num)
except ValueError:
print("not integer input")
except ZeroDivisionError:
print("your input is zero")
捕获未知错误:
try:
num = int(input("enter a integer:\n"))
print(8/num)
except ValueError:
print("not integer input")
except Exception as result: # 捕获未知错误
print("未知错误 %s" % result)
# division by zero,此时我们便知道要对除0这种情况做单独的异常捕获
异常捕获完整语法:
try:
#尝试执行的代码
pass
except 错误类型1:
#针对错误类型1,对应的代码处理
pass
except 错误类型2:
#针对错误类型2,对应的代码处理
pass
except (错误类型3,错误类型4):
#针对错误类型3和4,对应的代码处理
pass
except Exception as result:
print("未知错误 %s" % result)
else:
#没有异常才会执行的代码
pass
finally:
#无论是否有异常,都会执行的代码
print("finally")
try:
num = int(input("enter a integer:\n"))
print(8/num)
except (ValueError,ZeroDivisionError):
print("your input is not or a integer or your input is zero")
except Exception as result: # 捕获未知错误
print("未知错误 %s" % result)
else:
print("else")
finally:
print("finally")
异常的传递:
当函数/方法出现异常,会将异常传递给函数/方法的调用一方,如果传递到主程序,仍然没有异常处理,程序才会被终止。
#demo1出现异常时会传到demo2,demo2会传到主程序,此时如果没有异常捕获程序就会终止。
def demo1():
return int(input("enter a integer:\n"))
def demo2():
return demo1()
#print(demo2())
try:
print(demo2())
except Exception as e:
print("未知异常 %s" % e)
抛出异常:
python中提供了一个Exception异常类,如果要抛出异常,可以创建一个Exception对象,使用raise关键字抛出异常对象
def input_password():
pwd = input("enter password:")
if len(pwd) <= 8:
return pwd
print("主动抛出异常")
# 语法没有错误,python并不会发现错误,而是开发者根据现实需求判定异常
#创建异常对象 可以使用错误信息字符串作为参数
ex = Exception("len is not right")
#括号里的是提示信息,并不是异常类型
#主动抛出异常
raise ex
try:
print(input_password())
except Exception as e:
print(e)
模块的两种导入方式:
1)import导入:
import 模块名1,模块名2
import 模块名1
import 模块名2
通过模块名.使用模块提供的工具————全局变量、函数、类
#hm_测试模块1.py
#全局变量
title = "模块1"
#函数
def say_hello():
print("我是%s" % title)
#类
class Dog:
pass
#hm_测试模块2.py
title = "模块2"
def say_hello():
print("我是%s" % title)
class Cat:
pass
#hm_导入模块.py
import hm_测试模块1
import hm_测试模块2
hm_测试模块1.say_hello()
hm_测试模块2.say_hello()
dog = hm_测试模块1.Dog()
print(dog)
cat = hm_测试模块2.Cat()
print(cat)
如果模块的名字太长,可以使用as指定模块的名称,以便在代码中的使用:
import 模块名1 as 模块别名
注意:模块别名使用大驼峰命名法
import hm_测试模块1 as DogModule
import hm_测试模块2 as CatModule
DogModule.say_hello()
CatModule.say_hello()
dog = DogModule.Dog()
cat = CatModule.Cat()
print(dog)
print(cat)
2)from...import导入
如果希望从一个模块中,导入部分工具,就可以使用from...import的方式,import 模块名是一次性把模块中所有工具全部导入,并且通过模块名/别名访问。
#从模块导入某一工具
from 模块名 import 工具名
导入后,不需要通过模块名.,可以直接使用模块提供的工具————全局变量,函数,类
#hm_导入模块.py
from hm_测试模块1 import Dog
from hm_测试模块2 import say_hello
say_hello()
wangcai = Dog()
print(wangcai)
注意: 如果两个模块,存在同名的函数,那么后导入模块的函数,会覆盖掉先导入的函数。开发时import代码应该统一写在代码的顶部,容易及时发现冲突,一旦发现冲突,可以使用as关键字给其中一个工具起一个别名。
from hm_测试模块1 import say_hello
from hm_测试模块2 import say_hello #as say_hello2
#后导入的同名函数覆盖先导入的
say_hello()
#say_hello2()
from...import * # 从模块导入所有工具
注意: 这种方式不推荐使用,因为函数重名并没有任何提示,出现问题不好排查(导入不同模块中有同名函数、变量时,还是会后导入的覆盖先导入的)
模块的搜索顺序:
python解释器在导入模块时,会搜索当前目录指定模块名的文件,如果有就直接导入,如果没有,再搜索系统目录
python中每一个模块都有一个内置属性__file__,可以查看模块的完整路径
import random
print(random.randint(1,3))
print(random.__file__)
如果当前目录下,存在一个random.py文件,程序就无法正常执行了
每一个文件都应该是可以被导入的:
一个独立的python文件就是一个模块,在导入文件时,被导入文件中所有没有任何缩进的代码都会被执行一遍,之后才会执行后续代码
#hm__name__模块.py
#全局变量、函数、类,注意: 直接执行的代码不是向外界提供的工具
def say_hello():
print("hello world")
#文件被导入时,能直接被执行的代码不需要被执行
print("小明开发的模块")
say_hello() # 测试函数
#hm_test.py
import hm__name__模块
print("_"*50)
__name__属性:
每个python文件都会自动有这个属性
下面个别__name__前加了“嗯”,是因为下划线显示不出来。
嗯__name__属性可以做到,测试模块的代码(#hm__name__模块.py)只在测试情况下被运行,而在被导入时不会被执行,嗯__name__是python的一个内置属性,记录着一个字符串,如果是被其他文件导入的,那么__name__就是模块名,如果是当前执行的程序__name__是__main__.
#hm__name__模块.py
#全局变量、函数、类,注意: 直接执行的代码不是向外界提供的工具
def say_hello():
print("hello world")
# 如果直接执行模块,__main__
if __name__ == "__main__":
print(__name__) # 这里是在测试模块内部,输出的是__main__
# 文件被导入时,能直接被执行的代码不需要被执行
print("小明开发的模块")
say_hello() # 测试函数
#hm_test.py
import hm__name__模块
# 这里导入测试模块,测试模块的__name__属性不再是__main__,而是其文件名,因此if的测试代码不会执行
包:
包是一个包含多个模块的特殊目录(通常把多个相关联的模块进行打包),目录下有一个特殊的文件__init__.py,包名的命名方式和变量名一致,小写字母+_ ,使用import 包名 可以一次性导入包中的所有模块
嗯__init__.py:
要在外界使用包中的模块,需要在__init__.py中指定对外界提供的模块列表
如:现在创建了一个包hm_message,里面有文件__init__.py,receive_message.py,send_message.py三个文件,若要在外界导入这个包,import hm_message,就必须在__init__.py中指定要用到的功能所属的模块
#send_message.py
def send(text):
print("send %s" % text)
#receive_message.py
def receive():
return "这是来自 100xx 的短信"
#__init__.py
from . import send_message
from . import receive_message
#hm_test.py
import hm_message
hm_message.send_message.send("hello python")
print(hm_message.receive_message.receive())
发布模块:
如果希望自己开发的模块,分享给其他人,可以按照以下步骤操作:
安装模块:
项目里不都有刚才压缩的这些模块了吗,不能直接使用吗,为什么还要安装呢?
:安装 = 把模块复制到 Python 的 “官方仓库” 里,这样 任何项目、任何地方都能直接 import!
安装模块操作流程: cd dist进入dist目录,执行安装命令pip install hm_message-1.0.tar.gz(注意此时我又安装到了conda环境,改用"C:\Users\86185\AppData\Local\Programs\Python\Python313\python.exe" -m pip install hm_message-1.0.tar.gz安装到系统python,即现在正在用的环境)。
验证安装模块是否成功:
新建项目12_安装模块,看能否直接导入hm_message或其中的模块,若可以,则模块压缩包成功安装到了硬盘,同系统自带模块具有了一样的地位。
pip安装第三方模块:
第三方模块通常是指由知名的第三方团队开发的并且被程序员广泛使用的python包/模块
pip是一个现代的,通用的python包管理工具,提供了对python包的查找,下载,安装,卸载等功能。
如:安装pygame模块到硬盘的系统python环境:
"C:\Users\86185\AppData\Local\Programs\Python\Python313\python.exe" -m pip install pygame
文件的概念:
计算机的文件,就是存储在长期存储设备上的一段数据,文件以二进制的方式保存在磁盘上。cpu要使用文件时,文件要先从硬盘、U盘等加载到内存。文本文件可以使用文本编辑软件查看,本质上还是二进制文件,如python源程序,二进制文件不能使用文本编辑软件查看。
文件的基本操作:
操作文件,一共包含三个步骤:打开文件,读(将文件内容读入内存)、写文件(将文件内容写入文件),关闭文件
操作文件的函数/方法:
open函数负责打开文件,并且返回文件操作对象,read/write/close三个方法都需要通过文件对象来调用
open函数的第一个参数是要打开的文件名(区分大小写),如果文件存在,返回文件操作对象,如果文件不存在,会抛出异常。
read方法可以一次性读入并返回文件的所有内容,close方法负责关闭文件。如果忘记关闭文件,会造成系统资源消耗,而且会影响到后续对文件的访问。
注意:方法执行后,会把文件指针移动到文件的末尾。
# README.md
hello python
我是code boy
# hm_test.py
file = open("README.md",encoding="utf-8")
text = file.read()
print(text)
file.close()
文件指针:
标记从哪个位置开始读取数据,第一次打开文件时,通常文件指针会指向文件的开始位置,当执行了read方法后,文件指针对移动到读取内容的末尾。因此,如果执行了一次read方法,文件指针移动到了文件末尾,再次调用read方法,不会读取到任何内容
# hm_test.py
file = open("README.md",encoding="utf-8") # 文件中有中文
text = file.read()
print(text,len(text))
print("-"*50)
text1 = file.read()
print(text1,len(text1))
file.close()
打开文件的方式:
open函数默认以只读方式打开文件,并且返回文件对象
f = open("文件名","访问方式")
按行读取文件的内容:
read方法默认会把文件的所有内容一次性读入到内存 按行读取文件的内容:
read方法默认会把文件的所有内容一次性读入到内存,如果文件太大,对内存的占用会非常严重
readline方法:
一次读取一行内容,方法执行后,会把文件指针移动到下一行,准备再次读取
# hm_test.py
file = open("README.md")
while True:
line = file.readline()
if not line: # 空字符串,空字典,空列表,None,False,0都可以这样判断
break
print(line,end="")
# 这里可以理解为每读入的一行末尾都有一个换行,如果不加end="",print自带换行会使不同文本行之间空出一行
file.close()
小文件复制:
# hm_test.py
file_read = open("README.md")
file_write = open("README[附件]","w")
file_write.write(file_read.read())
file_read.close()
file_write.close()
大文件复制:
# hm_test.py
file_read = open("README.md")
file_write = open("README[附件]","w")
while True:
line = file_read.readline()
if not line:
break
file_write.write(line)
file_read.close()
file_write.close()
文件/目录的常用管理操作:
在终端/文件浏览器中可以执行常规的文件/目录管理操作,如:创建、重命名、删除、改变路径、查看目录内容、……,在pyhton中,如果希望实现上述功能,需要导入os模块
#hm_test.py
import os
print(os.listdir("."))
# 查看当前文件所在目录(12_安装模块)下所有的子目录和文件
print(os.listdir(".."))
# 查看当前文件所在目录(12_安装模块) 所在的目录(python_study_project)下的所有子目录和文件
print(os.listdir(r"D:\python_study_project\12_安装模块"))
# 绝对路径,路径里有,加 r ,不转义
os.rename("hm_test.py", "hm_test1.py")
#修改与当前文件同根目录下的文件名
eval函数:
eval 干的就两件事:1 脱引号,2 执行脱完引号后的 Python 代码
eval()函数十分强大————将字符串当成有效的表达式来求值并返回计算结果
question = input("enter a math problem:\n")
print(eval(question))
print(type(eval("[1,2,3]")))
print(type(eval('{"name":"zhang"}'))) # 注意这里不要都用双引号
# 字符串字典 → 真字典
data = eval('{"name":"zhang","age":20}')
print(type(data)) # <class 'dict'>
# 字符串列表 → 真列表
lst = eval("[1,2,3,4]")
print(type(lst)) # <class 'list'>
a = 10
b = 20
print(eval("a + b")) # 30
# 输入:1+2+3
s = input()
print(eval(s)) # 输出 6
不要滥用eval:
os.system() 就是让 Python 去调用你电脑的终端,执行一条命令
在开发时,千万不要使用eval直接转换input的结果:
message = input("enter your question:\n")
# 输入:__import__('os').listdir('.')
# 相当于import os,os.listdir('.')
print(eval(message))
#eval 去引号,执行import os,os.listdir('.'),print输出目录查找到的目录内容
**飞机大战: **
pygame安装:
"C:\Users\86185\AppData\Local\Programs\Python\Python313\python.exe" -m pip install pygame
验证安装:
先导入pygame
终端(cmd里输入,pycharm终端不支持)输入:
python -m pygame.examples.aliens
使用pygame创建图形窗口:
quit()只负责关闭所有pygame子模块,释放内存里的图形/声音/窗口资源,结束pygame运行状态。
import pygame
pygame.init()
print("游戏中的代码")
pygame.quit()
游戏中的坐标系:
import pygame
hero_rect = pygame.Rect(100,500,130,135)
print("英雄的原点 %d %d" % (hero_rect.x,hero_rect.y)
print("英雄的尺寸 %d %d" % (hero_rect.width,hero_rect.height)
print("%d %d" % hero_rect.size)