python基础

Python基础阶段笔记

补充:
Ctrl  + /    注释快捷键
ctrl + alt +l   代码格式化
Ctrl + shift + f10  运行代码快捷键

变量的类型:
str -- 字符串
bool -- 布尔(真假)
int -- 整数
float -- 浮点数(小数)

标识符命名规则:
由字母,下划线和数字组成
不能以数字开头
不能与关键字重名
建议不要和类型同名,如:int,str

python 是动态强类型语
	动态是指python在定义变量时无需指定数据类型,这就导致在编程时无法确定程序运行后的数据类型,导致不确定的报错;
	强制类型转换,例如在 字符加数字时 a + 1,python必须将1转换成字符串类型才可以相加 print('a'+str(1))  

python中的内存管理机制:
1.变量无需事先声明,也不需要指定类型,这是动态语言的特性
2.python编程中一般无须关心变量的存亡,一般也不用关心内存的管理
3.python使用引用计数,记录所有对象的引用次数(sys.getrefcount()查看变量引用次数,常量的引用计数会非常高,因为常量在内存中只有一份,解释器都会引用)
4.当对象引用数变为0,它就可以被垃圾回收GC(机制java也有这种机制)
5.如果内存空间中都是分散的内存地址,python解释器会对这些零散的内存做规整,整理出一段连续的内存空间,供其他数据使用(自己总结)
6.计数增加:
	赋值给其他变量就是增加引用计数,例如x=3;y=x
	实参传参,如foo(y)
7.计数减少
	函数运行结束时,局部变量就会被自动销毁,对象引用计数减少
	变量被赋值给其他对象。例如x=3,y=x
8.有关性能的时候,就需要考虑变量的引用问题,但是该释放内存还是尽量不释放内存,看需求
# 时间复杂度和空间复杂度
		在衡量一个算法所编写出程序的运行效率时,数据结构中,用时间复杂度来衡量程序运行时间的多少;用空间复杂度来衡量程序运行所需内存空间的大小

1.常用的格式化输出符

%d 表示有符号的十进制整数
%s 表示字符串
%f 表示浮点数
不常用的格式化输出符
%c 字符
%u 无符号十进制整数
%o 八进制整数
%x 十六进制整数(小写ox)
%X 十六进制整数(大写OX)
%e 科学计数法 (小写e)
%E 科学计数法(大写E)
%g %f和%e的简写
示例 :
age = 18
name = 'luojie'
weight = 75.5
stu_id = 1

print('我的名字是%s,我的年龄是%d,我的体重是%.2f,我的学号是%03d' % (name, age, weight, stu_id))

​ 注:上述中%.2f表示显示是对浮点数只保留小数点后两位数,%03d表示输出的整数显示位数,不足的位以0补全,超出当前位数则原样输出

1.1 格式化字符串拓展

拓展解释1:

​ %s表示输出的为字符串,但整数和浮点数也可以用%s字符串格式输出,并且也支持变量引用时 的变量运算

示例:

name = 'tom'
age = 18
weight = 75.5
hight = 175

#我的名字是x,今年x岁了,体重是x公斤
print('我的名字是%s,今年%s岁了,我明年%s岁了,体重%s公斤' % (name, age, age+1, weight))

​ 拓展解释2 :

​ %s格式化字符串的效果也可以用 f'{表达式}' 语法来代替,并且 f 的格式化输出字符串是python3.6中新增的格式化方法,该方法更简单易读,也可以对变量进行字符运算

​ 示例:

'格式化字符串f的用法在python3.5 ,3.6 之前无法使用,使用会报错'
# %s格式化字符串的效果也可以用 f'{表达式}' 语法来代替 ;或者 F'{表达式}'
name = 'tom'
age = 18
weight = 75.5
hight = 175

print(f'我的名字是{name} ,我的年龄是{age} ,后年我{age + 2},我的体重是{weight} 公斤,下一次我的体重是{weight - 30}公斤')

扩展:格式化字符串函数 format

format函数格式化字符串语法 ---- python鼓励使用
	"{}{xxx}".format(*args, **kwargs) -> str
  args 是可变位置参数,是一个元组
  kwargs是可变关键字参数,是一个字典
  花括号表示占位符
  {}表示按照顺序匹配位置参数,{n}表示取位置参数索引为n的值
  {xxx}表示在关键子参数中搜索名称一致的
  {{}} 表示打印花括号
示例:
# 利用dattime模块获取当前时间
>>> import datetime
>>> d = datetime.datetime.now()
>>> d
datetime.datetime(2020, 11, 20, 15, 32, 44, 632804)
>>> "{:%Y-%m-%d %H:%M:%S}".format(d)		# 格式化字符串
'2020-11-20 15:32:44'

# 把ip地址转换成16进制
>>> "{:x} {:x} {:x} {:x}".format(127,100,10,20)
'7f 64 a 14'
>>> "{:#02x}{:02X}{:0x}{:02X}".format(127,100,10,20)
'0x7f64a14'

# 格式化字符串
>>> "{} * {} = {}".format(2, 3, 2*3)
'2 * 3 = 6'
>>> "{}*{}={:<2}".format(2, 3, 2*3)  # <左对齐
'2*3=6 '
>>> "{}*{}={:>2}".format(2, 3, 2*3) # >右对齐
'2*3= 6'
>>> "{}*{}={:^20}".format(2, 3, 2*3) # 居中
'2*3=         6          '
>>> octets = [192,168,1,1]
>>> '{:02X}{:02X}{:02X}{:02X}'.format(*octets) # 参数解构,转换成16进制
'C0A80101'

1.2 转译字符

\n : 换行

\t : 制表符,一个tab键(4个空格)的距离

print('hello\nworld')
print('hello\tworld')

1.3 结束符

在python中,print(),默认自带end="\n"这个换行结束符,所以导致每两个print直接会换行展示,用户可以按需求更改结束符

格式:print('内容',end="")

注:end冒号中可以为任意字符,不限于换行符和制表符

print('hello py', end="\t")
print('python')

1.4 输入

输入的语法:input("提示信息")

输入的特点:

​ 当程序执行到input,等待用户输入,输入完成之后才继续向下执行。

​ 在python中,input 接收用户输入后,一般存储到变量中,方便使用

​ 在python中,input 会把接收到的任意用户输入的数据当作字符串处理

示例:

passwd = input("请输入密码:")
print(f'输入的密码是{passwd}')
#查看数据类型
print(type(passwd))

2. 转换数据类型

目的:用户输入的内容默认为字符串类型,将其转换为其他数据类型时可以利用数据类型转换的函数来完成,如下:

函数 说明
int(x [,base]) 将x转换为一个整数, 取整数部分
float(x) 将x转换为一个浮点数
str(x) 将对象x转换为字符串
eval(str) 用来计算在字符串中的有效python表达式,并返回一个对象,原本的类型
tuple(s) 将序列s转换为一个元组
list(s) 将序列s转换为一个列表
complex(real [,imag]) 创建一个复数,real为实部,imag为虚部
repr(x) 将对象x转换为表达式字符串
chr(x) 将一个整数转换为一个unicode字符
ord(x) 将一个字符转换为它的ascii整数值
round() 元整,四舍六入取偶
floor() 向下取整
ceil() 向上取整
// 整除且向下取整
isinstance(x,(str,float, str,bool,int)) 判断字符x是否属于元组(str,float, str,bool,int) 中的任意一个类型,是则返回True,否则False
isinstance()判断字符类型:
示例:
>>> isinstance(11,int)
True
>>> isinstance(11,(float, str, bool))
False
>>> isinstance(11,(float, str, bool, int))
True

示例1:

passwd = input("输入密码")
#此时数据类型是字符型
print(type(passwd))
#此时转换后的数据类型是整数型
print(type(int(passwd)))

示例2:

#将字符串或者整数转换为符点类型
num1 = 1
str1 = '10'

print(type(float(num1)))
print(float(num1))
print(float(str1))

# str() 将数字转换为字符类型
print(str(num1))

# tuple() 将序列转换为元组
list1 = [10, 20, 30]
print(tuple(list1))

# list() 将一个序列转换为列表
t1 = (100, 200, 300)
print(list(t1))

# eval() 判断变量的字符中有效的python表达式,返回一个对象,及值对应的类型
# 自动判断变量的字符类型,并且输出字符的对应的类型
str2 = '1'
str3 = '1.1'
str4 = '(1000, 2000, 3000)'
str5 = '[1000, 2000, 3000]'
print(type(eval(str2)))
print(type(eval(str3)))
print(type(eval(str4)))
print(type(eval(str5)))

3. 运算符的分类

算数运算符, 赋值运算符, 复合赋值运算符, 比较运算符, 逻辑运算符

注:

3.1 算数运算符:+ - * / // % **

​ 加,减,乘,除,取整数除,取余,指数

​ 算数混合运算时,优先级顺序为() > ** > * > / > // > % > + , -

​ 优先级a +b > c and (加减 > 比较 > 逻辑);先算等号右边再算左边

​ // 取整输除时遵循向下取整,就是取比结果小的整数数,特别注意负数取整时-2.5取整是-3

​ 异或运算;相异出1

magedu补充知识--二进制运算:
原码,反码,补码,负数表示法:
	原码(是给人看的):5(原码) => 0b101(编译后的机器表示), 1 => 0b1, -1 => -0b1, bin(-1)
  反码:正数的反码与原码相同;负数的反码符号位不变其余按位取反
  补码(机器能读的编码叫做补码):正数的补码与原码相同;负数的补码符号位不变其余按位取反后+1
  负数表示法:
  	早期数字电路的cpu中的运算器实现了加法器,但是没有减法器,减法要转换成加法
    负数在计算机中使用补码存储,-1的补码为 1111 1111
    5-1 => 5+(-1)直觉上是 0b101-0b1, 其实计算机中是 0b101+0b11111111,溢出位舍弃
    ~12(把12按位取反)为什么是 -13     
			0000 1100  <- 12 (十进制)
			1111 0011	 <- (按位取反)最高位是1,表示负数,认为是负数的补码
      1000 1101  —> -0x0d -0xd    得到结果 -13 
    10^9(异或运算)等于?10^-9等于?为什么
    	1010   = 10
      1001   = 9
      -----
      0011   = 3  (10异或 9为 3)  10^9是-3?

3.2 赋值运算符:

二进制转换到十进制:
1001
8421。是 十进制的 9
= 等号赋值,将等号右边的值赋值给左边的变量中
= 等号用于变量赋值,注意在多变量赋值时,左边的变量名用逗号隔开,右边的值也用逗号隔开,达到一一对应的赋值效果,用法如下:

a, b, c = "tom" , 1, 3.24
print(a)
print(b)
print(c)

#多变量赋相同的值,作用时化简代码量
e = f = 100
print(e)
print(f)

3.3 复合赋值运算符

运算符 描述 示例
+= 加法赋值运算 c+=a 等价于 c = c + a
-= 减法赋值运算 c-=a 等价于 c=c-a
*= 乘法赋值运算 c*=a 等价于 c=c*a
/= 除法赋值运算 c/=a 等价于 c=c/a
//= 整除赋值运算 c//=a 等价于 c=c//a
%= 取余赋值运算 c%=a 等价于 c=c%a
**= 幂赋值运算 c**=a 等价于 c=c**a
~x 按位取反 bit位 1001 取反是 0110
x is y, x is not y 等同测试
x in y, x not in y 成员判断
s = 10
s += 1
#输出时的表达式为 s = s + 1
print(s)

# 下例中先做等号右边的运算,再来赋值运算,下述结果为30
d = 10
d *= 1 +2
print(d)

注:在运算时等号右边的值赋值给等号左边的值 ,如果等号右边的值存在运算时,则先做右边值的运算再赋值

3.4 比较运算符

​ == 等于

​ != 不等于

​ < 小于

​ > 大于

​ <= 小于等于

​ >= 大于等于

3.5 逻辑运算符

​ and 与(并且);都真才真(短路运算符)

​ or 或 ;只要一个为真就为真,都假才假 (短路运算符)

​ not 非 ;取反

成员运算符:in , not in

身份运算符:is(是),is not(不是)

示例:

a = 1
b = 2
c = 3
print((a<b) and (b<c))

注:在使用逻辑运算符时,最好加上小括号避免歧义,并且有助于阅读代码

拓展:

# and 运算符在做数值逻辑运算时,只要有一个值为0,则结果为0,否则结果为最后一个非0数字

# or 运算符在做数值逻辑运算时,只要所有值为0结果才为0,否则结果为第一个非0数字

4. 条件语句

4.1 if 循环语句

语法:
if 条件:
	执行代码
	...
	else:
	执行代码
	...

注:在上述的【执行代码】块必须是缩进状态的,不是缩进状态的【执行代码】不会在 if 中执行


示例:

num = input("请输入学号:") #或者将字符转换成整数在做下面的比较 num = int(input("请输入学号")),下面在比较时就可以不带冒号
if num == "10":
    print('恭喜你的学号中奖了')
else:
    print('不好意思,未中奖!')

4.2 多重条件判断 if...else...

条件成立则执行代码1,条件不成立则执行代码2

多重判断也可以和else配合使用,一般else放到整个if语句的最后,表示以上条件都不成立的时候执行的代码

语法:

if 条件1:
    条件1成立执行的代码1
    条件1成立执行的代码2
    ...
elif (条件2) and (条件3):
    条件2和条件3都成立执行的代码1
    条件2和条件3都成立执行的代码2
    ...
...
else:
    以上条件都不成立执行的代码

拓展:如果条件判断年龄是 age >= 18 and age <= 60 可以简化为 18 <= age <= 60

4.3 if 嵌套

语法:

if 条件1:
    条件1成立执行的代码1
    条件1成立执行的代码2
		if 条件1:
        条件1成立执行的代码1
        条件1成立执行的代码2

注意:条件2的if也是出于条件1的缩进关系内部

示例:

# 坐公交:如果有钱可以上车,否则不能上车,如果上车,判断是否能坐下
men = 1
seat = 1
if men == 1 :
    print("请上车")
    if seat == 1:
        print("请坐下")
    else:
        print("请站着")
else:
    print("不能上车")

5. 三目运算符/三元表达式

注:但是这种简化的表达式有局限性,只能表达一行代码判断,不能多行判断

说明:要读带有三目运算符的表达式时要从if开始读,代码1 if (条件) else 代码2 左边是条件成立执行的代码1,条件不成立执行代码2 。这样书写的目的是简化程序

示例:

a = 1
b = 2
# 将运行结果赋值给c,并且打印c
c = a if a>b else b
print(c)

#例2 执行判断若aa大于bb,则用aa-bb,反之亦然,将结果赋值给cc
aa = 10
bb = 20
cc = aa -bb if aa > bb else bb - aa
print(cc)

5.1 While 循环

语法:

while 条件判断:
    循环执行语句
    ...	
    

示例:

#例1:执行代码10次
num = 1
while num <= 10:
    print("i am sorry %d " % num)
    num += 1

# 例2 求1 加到 100 的和
步骤分析:
1。定义初始值,做加法
2。定义总和变量来赋值加法结果
3。打印总和变量

i = 1
result = 0
num3 = 9999
while i <= num3:
    result = result + i
    i = i + 1
print("1+2+3+4+...+%d =%d" % (num3,result))

# 求100以内的偶数相加
i = 1
num4 = 0
cyc = 100
while i <= cyc:
    if i % 2 == 0:
        num4 = num4 + i
    i = i +1
print("2+4+6+8+...+%d=%d" %(cyc,num4))

# 求100以内的奇数相加
i = 1
num6 = 0
while i <= cyc:
    if i % 2 != 0:
        num6 = num6 + i
    i = i +1
print("1+3+5+7+...+%d=%d" %(cyc,num6))

5.2 while嵌套

先写外层循环,再写内层循环,然后将内层循环移动到外层循环中

# 通过循环嵌套打印一个正方形
# 先写外部循环体,循环5次,其中pass代表还没有替换的内循环体
while i < 5:
    pass
    print()
    i += 1

# 内循环体,打印5个连续的*  
 while j < i + 1:
        print("*", end=" ")
        j += 1
        
结果为:
while i < 5:
    while j < i + 1:
        print("*", end=" ")
        j += 1
    print()
    i += 1

#示例2

# 外循环体
i = 0
while i < 5:
    print("已经跑了第%d圈了" % (i + 1))
    pass
    i += 1
# 内循环
j = 0
while j < 10:
    print("做了第%d个俯卧撑了" % (j + 1))
    j += 1
 
结果为:
i = 0
while i < 5:
    print("已经跑了第%d圈了" % (i + 1))
    j = 0
		while j < 10:
    		print("做了第%d个俯卧撑了" % (j + 1))
    		j += 1
    i += 1

        

5.3 Break 和 continue和 else

break 的作用是跳出循环,后续不再执行
continue 的作用是跳过指定循环,下一循环继续执行
while ...循环体... else... 语句:

​	当循环体中没有遇到 break 语句时while 条件执行完后继续执行else 分支中的条件

注:与if ... else... 中不同,if 中只会执行其中的一个分支

​		而while 语句中只要没有break就会都执行

5.4 字符串(下标,切片)

补充:
一个个字符组成的有序的序列,是字符的集合;'有序序列都可以使用索引'
使用单引号,双引号,三引号引住的字符序列
字符串是'不可变'对象,不能再被修改
python3起,字符串就是Unicode类型

(1). 书写字符串时,可以用单引号,双引号和三引号;其中三引号的特点时可以换行输出需要打印的内容

(2). 下标 又叫做 索引 ,即位置编号

(3). 下标查找示例:

namm = 'hello world'
print(namm[0])

(4). 切片是指对操作的对象截取其中一部分的操作,字符串,列表,元组都支持切片操作

​ 语法:序列[开始位置下标:结束位置下标:步长]

​ 左闭右开的选择区间

​ 注意:不包括结束位置的下标所包含的数据,正负整数均可,步长是选取间隔,正负整数均可,默认步长为1

a = '123456789'
print(a[-1:-5:-1])
b = 'abcdefg'
print(b[-1:-5:-2])

6. 字符串常用操作方法

字符串的常用操作方法有查找,修改和判断三大类

注:字符串是不可被修改的,是不可变元素,所有修改操作返回的一定是一个新值或者新变量

6.1 查找


# find(): 字符串序列.find(子串,开始位置下标,结束位置下标)
	查找某个字串是否包含在这个字符串中,如果在则返回这个字串开始的位置下标,否则则返回-1
	注:开始和结束位置下标可以省略,表示在整个字符串序列中查找

# index():index(sub[,start[,end]]) -> int
	在指定的区间[start, end],从左至右,查找子串sub。找到返回索引,没找到抛出异常ValueFrror;同为查找功能,与find() 语法相同但不同点是在查找不存在的子串时会直接报错

# count():count(sub[,start[,end]]) -> int
	在指定的区间,从左至右,统计子串sub出现的次数;统计某子串在整个字符串中出现的次数,语法与上述相同

# rfind(): 和find()功能相同,但查找方向为右侧开始

# rindex(): rindes(sub[,start[,end]]) -> int 
  在指定的区间[start, end],从左至右,查找子串sub,找到返回索引,没找到抛出异常ValueError ;和index()功能相同,但查找方向为右侧开始

6.2 修改: replace(),split(),join()

replace() 修改,替换可以修改字符串,指的就是通过函数的形式修改字符串中的数据,并没有更改变原始字符串,返回的是一个新字符串

replace(old, new[,count]) -> str
	字符串中找到匹配替换为新子串,返回新字符串
  count表示替换几次,不指定就是全部替换
语法:字符串序列.replace(旧子串,新子串,替换次数)
示例:
mystr = ("hello world and hello python")
print(mystr.replace('hello', 'haha'))

split(): 按照指定字符分割字符串

split() --分割, 返回一个列表,会丢失分割字符
rsplit() -- 反向分割
'默认使用空白字符和换行符分割,立即返回一个列表'
		按照行来切分字符串
  	keepends 指的是是否保留分隔符
    行分隔符包括\n, \r\n, \r等
语法:
	字符串序列.split(分割字符, num)
注意:num表示的是分割字符出现的次数,即将来返回数据个数为num+1个

示例:
mystr = ("hello world and hello python")
list = mystr.split('and')
print(list)
##返回 ['hello world ', ' hello python']
>>> s1 = 'a,b,c,d,e,f'
>>> s1.split("d")
['a,b,c,', ',e,f']

--------------------------------------------------------------------

partition() -- 分割,必须要有分割符,只切一刀,结果为元组,并且是一个三元组
rpartition() -- 反向分割
>>> s1 = 'a,b,c,d,e,f'
>>> s1.partition("d")
('a,b,c,', 'd', ',e,f')

join(): 用一个字符或子串合并字符串,即是将多个字符串合并为一个新的字符串。

join() ---合并列表里面的字符串数据为一个大字符串
语法:
		字符或子串.jion(多字符串组成的序列)
示例1:
mylist = ['aa', 'bb', 'cc']
new_list = '...'.join(mylist)
print(new_list)
## 返回  aa...bb...cc

示例2:
>>> ",,,,".join(['1','3','9','9'])
'1,,,,3,,,,9,,,,9'

示例3:每个int类型的数字都给str转换为字符后进行连接
>>> ":".join(map(str, range(9)))
'0:1:2:3:4:5:6:7:8'

6.3 其他非重点的修改函数:

	zfill(): 填充0,给字符串按位填充,不够的用0代替
>>> str(10).zfill(10)
'0000000010'
字母大小写转换: 
	capitalize(): 将字符串第一个字符转换成大写
  	注:capitalize()函数转换后,只整个字符串的第一个字符大写,其他字	符全部小写。
	title():将字符串每个单词首字母转换成大写
	lower(): 将字符串中大写转小写
	upper(): 将字符串中小写转大写
	lstrip(): 删除字符串左侧空白字符

删除空白字符: 
	lstrip(): 删除字符串左侧空白字符
	rstrip(): 删除字符串右侧空白字符
	strip(): 删除字符串两侧空白字符
    
字符串对齐:
	ljust(): 返回一个原字符串左对齐,并使用指定字符(默认空格)填充至对应长度的新字符串。
    语法:
    		字符串序列.ljust(长度,填充字符)
      示例:
						_mystr = ('hello')
						new_mystr = _mystr.ljust(10, '.')
						print(new_mystr)
            ## 返回 hello.....
 
rjust(): 返回一个原字符右对齐,并使用指定字符填充对应长度的新字符串,语法与上述相同
  
center(): 返回一个原字符的中间对齐状态,并使用指定字符填充对应长度的新字符串,语法与上述相同
  

6.4 判断

所谓判断既是判断真假,返回的结果是布尔型数据类型:True 或 False

# startswith():字符串序列.startswith(子串,开始位置下标,结束位置下标)
  检查字符串是否是以指定子串开头,是则返回 True ,否则返回 False。 如果同时设置了开始和结束位置下标,则在指定范围内检查。
  

# endswith():字符串序列.endswith(子串,开始位置下标,结束位置下标)
  检查字符串是否以指定子串结尾,是则返回 True ,否则返回 False 。如果设置了开始和结束位置下标,则在指定范围内检查。
  
  
# 其他字符串判断函数:
  isalpha(): 如果字符串至少有一个字符并且所有字符都是字母则返回 True,否则返回 False
    
  isdigit(): 如果字符串只包含数字则返回 True 否则返回 False
    
  isalnum(): 如果字符串至少有一个字符并且所有字符都是字母或数字则返回 True,否则返回 False 
    
  isspace(): 如果字符串中包含空白,则返回 True, 否则 返回 False

7. 列表

7.1 列表可以一次性存储多个数据(连续排列的)

	列表:列表中可以存储多个数据类型,但一般只存储一种数据类型,一个连续的内存空间,是一个队列,一个排列整齐的队伍,python中有垃圾回收机制,可以整理碎片化的内存空间,开辟一个连续的内存空间出来;但是列表的坏处也在与此,当列表中添加或者删除其中一个数据时,整个内存空间就会移动,保持住连续性,使列表的性能很受影响。列表因为是一个连续的有序的内存空间,所以在查找,索引列表中的某一个数据时会很高效。
​	链表:区别于列表,是散落排列在内存空间,优势在于增删,劣势在于查询;链表中的数据是前后相连的,前一个数据只会知道后一个数据的位置,若要知道某一个数据的位置就需要从头开始询问查找
​	queue:队列,数据遵循先进先出策略,如排队,队列前面的先进去,后面的后进去
​	stack:栈,数据遵循先进后出策略,如子弹上弹夹,第一个子弹最后一个打出

列表,链,队列,栈都是有序的数据类型,都有顺序的

列表的索引访问:
索引,也叫做下标
正索引:从左往右,从0开始,为列表中的每一个元素编号
负索引:从右往左,从-1开始
正负索引不可以超界,否则引发异常IndexError
为了方便理解,可以认为列表是从左至右排列的,左边是头部,右边是尾部,左边是下界,右边是上界

​ 列表是可变的额数据类型

列表中查找的函数:
index(): 返回指定数据所在位置的下标
  语法:列表序列.index(数据,开始位置下标,结束位置下标)
count(): 统计指定数据在当前列表中出现的次数
  语法与上相同
len(): 访问列表长度,即得到列表中的数据的个数
  列表list在存放数据时,自带有长度属性,加元素+1,减元素-1,因此获取列表的长度信息会很快
  
  时间复杂度:
  	index和count方法都是O(n)
    随着列表数据规模的增大,而效率下降,这两个函数尽量少用

7.2 列表中判断是否存在的函数:

in: 判断指定数据是否在某个列表序列,如果在返回 True,否则返回 False
not in : 判断指定数据不在某个列表序列,如果不在返回 True,否则返回 False
示例1:
mystr = ("hello world and hello python")
print('hello' in mystr)

示例2 需求:注册邮箱时,用户输入一个用户名,判断此用户名是否已经存在,存在则告知不能注册,不存在则可以注册
name_list = ['tom', 'ian', 'sam']
name = input("请输入你的用户名:")
if name in name_list:
    print("该用户名已存在,不能注册!")
else:
    print(f"用户名{name}可以注册!")

7.3 增加

作用:增加指定数据到列表中

'append(): 列表结尾追加数据,一次添加一个'
语法:列表序列.append(数据)
示例:
name_list = ['tom', 'ian', 'sam']
name = input("请输入你的用户名:")
if name in name_list:
    print("该用户名已存在,不能注册!")
else:
    print(f"用户名{name}可以注册!")
    name_list.append(name)           ##添加用户输入到列表
    print(name_list)
    
注:append()追加的数据是一个序列,则追加整个序列到列表中
extend(): 列表结尾追加数据,如果数据是一个列表,则将这个列表的数据逐一添加到目标列表中,若要追加的数据是个字符串,则将字符串逐个拆开进行追加
 语法:列表序列.extend(数据)
a = ['aa', 'bb', 'cc']
a.append(['a','b','c'])
print(a)   #结果是:['aa', 'bb', 'cc', ['a', 'b', 'c']]

a.extend(['a','bbbbb'])
print(a)    #结果是:['aa', 'bb', 'cc', ['a', 'b', 'c'], 'a', 'bbbbb'] 
'insert(): 指定位置新增数据,插队(插队在列表中效率最低,因为会影响数据在内存中的整体位置
语法:列表序列.insert(位置下标,数据)
	超越上界,尾部追加,超越下界,头部追加 
示例:
a = ['aa', 'bb', 'cc']
a.insert(1,'sorry')
print(a)
结果:['aa', 'sorry', 'bb', 'cc']


7.4 删除

del
语法: del 目标
# 删除整个列表
a = ['aa', 'bb', 'cc']
del a
print(a)

#删除列表中指定数据
a = ['aa', 'bb', 'cc']
del a[0]    #删除下标为0的数据
print(a)
pop()
'根据索引查找数据,也会挪动数据,除非是最后一个数据,也是一个低效的操作,找数据很快'
删除指定下标的数据,如果不指定下标,默认删除最后一个数据,无论是按照下标还是删除最后一个,pop函数都会返回这个被删除后的数据
示例:
a = ['aa', 'bb', 'cc']
new_a = a.pop()
print(a)
# 结果为 ['aa', 'bb']

remove(数据)
'删除列表中的某个数据的第一个匹配项,时间复杂度O(n),较低效,会引起数据的挪动,除非是最后一个数据'
语法:列表序列.remove(数据)
示例:
a = ['aa', 'bb', 'cc']
a.remove('aa')
print(a)
	clear()   -- 效率较高
	'引用计数为0时,内存的垃圾回收机制会自动清除,clear()只是会将引用计数减1,剩下一个空列表,而清除数据的操作只会是垃圾回收机制来做,而非这个函数来做'
	清空数据,返回空列表

7.5 修改

修改指定下标的数据
a = ['aa', 'bb', 'cc']
a[0] = 'ff'
print(a)
逆置排序(反转):reverset()  
>>> s3 = [1, 100, 3]
>>> s3.reverse()
>>> s3
[3, 100, 1]
升序/降序 排序: sort()  -- 低效操作
语法: 列表序列.sort( key=None, reverse=False)
注:reverse 表示排序规则,reverse = True 降序,reverse = False 升序(默认), key 是设置在排序时按照什么数据类型来排,只用于排序过程中,不改变数据本质类型
如:
>>> s5 = [1, 2, 3, 4, 8]
>>> s5.append('10')
>>> s5
[1, 2, 3, 4, 8, '10']    # 添加字符串10
>>> s5.sort()							# 列表中数据类型不同无法排序
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'str' and 'int'
>>> s5.sort(key=int, reverse=True)  # key=int 设置用整数类型做降序
>>> s5
['10', 8, 4, 3, 2, 1]

>>> s5.sort(key=str, reverse=True)		# 转换成字符串类型后降序排列,默认是按 ASCII 码比较
>>> s5
[8, 4, 3, 2, '10', 1]

7.6 复制

函数:copy()
复制列表中的数据到另一个变量进行赋值存储
经常在删除数据前先备份一份数据
语法:变量2 = 变量1.copy()
# copy()直接拷贝是浅拷贝,既只拷贝了原数据的引用指针,数据在内存中还是只有一份,如果改变了原数据,拷贝后的变量所指的数据也会改变
"""
影子拷贝:也叫做浅拷贝,遇到引用类型,只是复制了一个引用而已
		* 星号,和 copy 会出现浅拷贝的问题
深拷贝:借助 copy 模块 提供的 deepcopy
"""

例如:
'列表进行拷贝时要注意浅拷贝和深拷贝的问题,因为列表中存的是内存地址(指向)而非数据本身'
'浅拷贝-指向同一个内存地址'
>>> s1 = [1,2,3,[4,5],6]
>>> s2 = s1.copy()
>>> s2
[1, 2, 3, [4, 5], 6]
>>> s1[3][0] = 100				#改变原数据
>>> s1
[1, 2, 3, [100, 5], 6]		# s1变量改变
>>> s2
[1, 2, 3, [100, 5], 6]		# s2 变量也跟着改变

>>> s4 = [100,200]
>>> s5 = [s4] * 3
>>> s5
[[100, 300], [100, 300], [100, 300]]
>>> s4[0] = 500
>>> s5
[[500, 300], [500, 300], [500, 300]]

'深拷贝-将内存中的数据全部拷贝一份并且赋值给新变量'
需要引用:copy模块中的deepcopy
>>> s3 = [1, 2, 3]
>>> import copy
>>> s6 = copy.deepcopy(s3)
>>> s3[1] = 100
>>> s3
[1, 100, 3]
>>> s6
[1, 2, 3]

"""
在列表中使用copy时,要考虑列表中的数据类型是简单数据类型还是复杂数据类型。
简单 :list1=[1,2,3]
复杂:list2=[1,2,3,[4,5,6]]
只有list2复杂数据类型在做copy时会有浅拷贝的影响,而list1简单数据类型在做拷贝时不受影响
"""
例:
>>> f3 = [1,2,3,[00,11],5]
>>> f4 = f3.copy()
>>> f4
[1, 2, 3, [0, 11], 5]
>>> f4[2] = 99
>>> f4
[1, 2, 99, [0, 11], 5]
>>> f3
[1, 2, 3, [0, 11], 5]  # 修改f3中的简单数据类型原数据不改变
>>> f4[3][0] = 88			# 修改f4中的复杂数据类型就有浅拷贝的影响
>>> f4
[1, 2, 99, [88, 11], 5] #f3列表中的[00,11]拷贝到f4是浅拷贝,增加了一次引用,实际上只是一份数据
>>> f3
[1, 2, 3, [88, 11], 5]

7.7 列表的循环遍历

需求:依次打印列表中的数据
示例:
a = ['aa', 'bb', 'cc', 'dd']
i = 0
while i < len(a):
    print(a[i])
    i += 1

for 循环实现循环遍历

if...in... : 判断某个元素是否在列表中,如果在则返回 True
for...in... : 从头到尾依次从列表中取出每一个元素
示例:
a = ['zhangsan','lisi','wangwu']
for i in a:
  	print(i)   

7.8 列表嵌套

所谓嵌套就是指在一个列表中包含了其他的子列表。

示例:
a = [['zhangsan', 'lisi', 'wangwu'], ['a', 'b', 'c'], ['1', '2', '3']]
print(a[0][0])
## 结果为:zhangsan

7.9 列表排序-冒泡法排序

# 排序算法-冒泡法
		冒泡法属于交换排序,就地排序,直接改变列表
  	两两比较大小,交换位置。如同水泡一个一个往上冒
    结果分为升序和降序
    
    升序:
    n个数从左至右,编号从0开始到n-1,索引0和1的值比较,如果索引0大,则交换两者的位置,如果索引1大,则不交换。继续比较索引1和2的值,将大值放在右侧。直到n-2和n-1比较完,第一轮比较完成。第二轮从索引0比较到n-2,因为最右侧n-1位置上已经是最大值了,依次类推,每一轮都会减少最右侧的不参与比较,直至剩下最后2个数比较
    降序:
    和升序相反
    
    
# 冒泡法代码:
nums_list = [[1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 9, 8, 5, 6, 7, 4, 3, 2], [9, 8, 7, 6, 5, 4, 3, 2, 1]]
nums = nums_list[2]		#多个列表测试
lenges = len(nums)
count = 0
swap_count = 0
# print(lenges)
for i in range(lenges):  # 最外层循环,一共多少个数循环比较多少次
    for j in range(lenges -1 - i): #内层移动比较的数n-1个循环
      count += 1						# 列表循环的次数
        if nums[j+0] > nums[j+1]:		# 升序排列
            temp = nums[j]					# 临时变量,做数据交换
            nums[j] = nums[j+1]
            nums[j+1] = temp
            swap_count += 1		#数据交换的次数

print(nums)
print(count)  #列表循环次数
print(swap_count) #列表中数据交换次数

---------------------------------------------------------------

# 冒泡法代码优化,加入检查机制,减少列表循环次数;即只有当前一个数大于后一个数,并且进入数据位置交换程序后改变了标计为Flase;如果前一个数就是小于后一个数不用进入位置交换程序,也就不用进行列表循环了
nums_list = [[1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 9, 8, 5, 6, 7, 4, 3, 2], [9, 8, 7, 6, 5, 4, 3, 2, 1]]
nums = nums_list[2]
lenges = len(nums)
count = 0
swap_count = 0
for i in range(lenges):
    flag = True				#初始检查标志
    for j in range(lenges -1 - i):
        count += 1
        if nums[j+0] < nums[j+1]:#如果第一个数据小于第二个数据就进入数据交换程序
            temp = nums[j]
            nums[j] = nums[j+1]
            nums[j+1] = temp
            swap_count += 1
            flag =False			#发生了一次数据交换就改变标志
        if flag: #flag为True就跳出,表示上一轮数据比较没有进行数据交换
            break
print(nums)
print(count)
print(swap_count)

----------------------------------------------------------------

冒泡法总结:
(1. 冒泡法需要数据一轮轮比较
(2. 可以设定一个标记判断此轮是否有数据交换发生,如果没有发生交换,可以结束排序,如果发生交换,继续下一轮排序
(3. 最差的排序情况是,初始顺序与目标顺序完全相反,遍历次数1,...,n-1之和n(n-1)/2
(4. 最好的排序情况是,初始顺序与目标顺序完全相同,遍历次数n-1
(5. 时间复杂度O(n2)(n的平方)(两层循环,都随着n的增加而增加)

'写代码时,最多2个循环,要考虑时间复杂度,大于2个循环的代码非常低效,循环越少越高效'

8. 元组 -- 小括号定义

一个元组可以存储多个数据,元组内的数据是不能修改的;例如存储身份证号这种不能被修改的数据时,可以用元组来存储

元组中可以存放多种类型的数据,但尽量只存放一种类型的数据,避免后期取数时繁琐

元组特点:定义元组使用小括号,且逗号隔开各个数据,数据可以是不同的数据类型
# 元 组 中 的 数 据 不 能 被 修 改 #
 
#注意:一个元组中只有一个数据时一定要带上逗号,否则该单个数据是什么类型元组就会成为什么类型

元组的常见操作

#元组数据不支持修改,只支持查找 
(1) 按下标查找数据
(2) index():查找某个数据,如果数据存在返回对应的下标,否则报错,语法和列表,字符串的 index 方法相同
(3) count():统计某个数据在当前元组出线的次数
(4) len(): 统计元组中数据的个数;在元组定义时就已经有个数信息了,直接查询,性能很快
  
 时间复杂度:
	index和count方法都是O(n)
  随着列表数据规模的增大,而效率下降

示例:
# 如果元组中的数据包括列表,那就可以修改元组中列表的值,但谨慎修改
a = ('a', 'b', 'c', ['11', '22'])
print(a[3][1])    #元组中的列表可以更改,元组本身没有改变 
a[3][1] = 'tom'
print(a)
补充:
命名元组 namedtuple   是个类
namedtuple(typename,field_names,verbose=False,rename=False)
	命名元组,返回一个元组的子类,并定义了字段
  field_names可以是空白符或逗号分割的字段的字符串,可以是字段的列表
 

9. 函数

9.1 函数的基本使用

#定义函数:
	def 函数名():
   	函数体中的代码块

9.2 函数执行过程,文档注释

# 函数体内第一行多行注释即为函数的文档注释,主要写函数的描述信息
# 使用 连续的三引号编写帮助信息
def fun():
  '''
  帮助信息:计算1+1的和的函数
  '''
  a = 1 + 1
  print(a)
  
# 调用函数
fun()

# 使用光标移动到函数名上,使用 ctrl+q 可以查看自己写的函数帮助信息
ctrl+q 

9.3 函数参数1: 普通参数

作用域:变量起作用的范围

形参的作用域:只在定义函数的代码块中

函数参数的作用:可以传递数据给函数内部,增加函数的通用性
# 定义格式
	def 函数名(形参1,形参2,...)
  		函数体代码块
# 带参数的函数调用,带上参数
	函数名(实参1,实参2,...)
  
  
例:
	def func(a,b):
 	   """
  	  求和函数
  	  """
   	 c = a + b
   	 print(c)
# 函数调用并传参
func(2987,45348)

 # 形参的作用域 只在定义函数的代码块中 ,形同的形参名在不同的函数中不影响

9.4 函数返回值

return 可以设置返回值并且中断函数,中断函数后返回一个结果,return后的代码不会执行

# 通过return给函数设置返回值
# return
1. 函数内部没有任何return语句,默认返回None,表示没有任何数据
2. return不设置返回值,默认有返回None

9.5 4种函数类型

# 无参数无返回
	定义格式:
  	def 函数名():
      函数体
  
  函数调用:
  	函数名()
      
# 无参数有返回
	定义格式:
  	def 函数名(形参1,从参2,...):
    		函数体
      	return 返回结果
  
  函数调用:
  	返回值变量 = 函数名字(实参1,实参2)
  
# 有参数无返回
	定义格式:
  	def 函数名(形参1,形参2,...):
    		函数体
  
  函数调用:
  		函数名(实参1,实参2,....)

# 有参数有返回
	定义格式:
			def 函数名(形参1,形参2,...)
    			函数体
      		return 返回结果
  
  函数调用:
      返回变量 = 函数名(实参1,实参2,...)
   
  # 例:有参有返回
def func_sum(n):
    '''
    实现1-n的累加
    :param n: 累加数据的范围
    :return: 返回累加结果
    '''
    i = 1
    sum =0
    while i <= n:
        sum = sum + i
        i += 1
    return sum

ret = func_sum(100)
print(ret)
    

9.6 函数的嵌套调用

'''
函数的嵌套调用:函数里面调用其他函数
'''
# 定义func01函数
def func01():
  print("函数开始调用")
# 定义func02函数,在代码中调用func01
	def func02():
    print("函数2开始调用")
    func01()
# 调用函数
func02

示例:求3个数的平均值
# 定义函数1,求三个数的和
def number(a, b, c):
    return a + b + c
# 定义函数2,调用函数1的值,求平均
def avg_number(a,b,c):
    sum = number(a,b,c)
    avg = sum /3
    return avg

ret = avg_number(11,22,33)
print(ret)

9.7 局部变量和全局变量

# 局部变量:
1. 定义在函数内部的变量,仅在函数内调用
2. 局部变量中函数定义的形参,是一个局部变量,函数内部定义的变量也是局部变量
3. 局部变量的作用域只在函数内部

# 全局变量
1. 在函数外部定义的变量叫做 全局变量
2. 全局变量能够在所有的函数中进行访问(不修改)
3. 全局变量在第一个函数中被修改后,其他函数再去调用该变量时返回的都是修改后的结果

# 通过 global 声明修改全局变量
函数内修改全局变量:先 global 声明全局变量,再修改
# 关 键 字: global
例:
# 定义一个全局变量 num
num = 10
def doo():
    """
    修改一个全局变量
    :return: 无返回值
    """
    global num  
    # ⬆️ 先声明要修改全局变量,关键字 global
    num = 250   
    # ⬆️ 修改了全局变量
    print(num)

# 函数调用    
doo()

# 打印这个全局变量,发现已经在函数中修改了
print(num)   

9.8函数参数2: 参数详解

'''
位置参数:按形参的位置,从左往右,一一匹配传递参数
关键字参数: 通过 形参=值 方式为函数形参传值,无需和形参位置意义对应
'''
#1. 位置参数:实参的位置顺序必须和形参 位 置 一一对应
#2. 位置参数:必须保证形参和实参的 个 数 保持一致
#3. 位置参数:在以位置给函数传参时 字符串类型 一定要和函数中定义的类型相同
例:
def foo(name, age, sex):
    print("姓名:%s 年龄: %d 性别: %s" % (name, age, sex))

foo("小六",12,"男")


# 关键字参数:
#1. 函数调用时,通过形参=值方式为函数的形参传值
#2. 不用按照位置为函数形参传值,这用方式叫做关键字传参
# 注:形参不能重复赋值,关键字参数必须在位置参数的右边
关键字参数:
	关键字=值
  形参=值
  
例:
def foo(name, age, sex):
    print("姓名:%s 年龄: %d 性别: %s" % (name, age, sex))

# 关键字传参
foo(age=20, name="小李", sex="女")

# 位置和关键字混合传参,位置参数在关键自左边
foo("小王", sex="男", age=21)

# 混合传参的错误示范
foo(name="小肖",18 ,sex="男")
SyntaxError: positional argument follows keyword argument


9.9 高阶函数:map reduce filter

1. map 用法
map() 会根据提供的函数对指定序列做映射
第一个参数 function 以参数序列中的每一个元素调用 function 函数,返回包含每次 function 返回值的一个新列表

示例:
# map 函数,对列表中的数做幂次方
my_list = [1,2,3,4]
def f(x):
    return x ** 2
ret = map(f, my_list)
print(ret, list(ret))

# 对列表中首字母大写转换
m_list = ["rose", "tom", "mike", "jody"]
def foo(x):
    return x[0].upper() + x[1:]
res = map(foo, m_list)
print(res, list(res))
2.reduce 用法
reduce() 函数会对参数序列中元素进行累计
函数将一个数据集中的所有数据进行下列操作:
	1). 用传给 reduce 中的函数 function (有两个参数)先对集合中的第1,2个元素进行操作
  2). 得到的结果再与第三个数据用 function 函数运算,最后得到一个结果
  
  
示例:
# 对集合进行累计操作
import functools

mm_list = [1,2,3,4,5]
def fo(x1, x2):
    return x1 + x2
resu = functools.reduce(fo, mm_list)
# 匿名函数方式
resu = reduce(lambda x1, x2 : x1 + x2 , mm_list)


3. filter用法
filter() 函数用于过滤序列,过滤掉不符合条件的元素,返回一个 filter 对象,如果要转换为列表,可以使用 list() 来转换
该接收两个参数,第一个为函数,第二个为序列,序列的每个元素作为参数传递给函数进行判断,然后返回True或False,最后将返回 True 的元素放到新的列表中

示例:
ms_list = [1,2,3,4,5,6,7,8]
def ff(x):
    return x % 2 == 0
result = filter(ff, ms_list)
print(list(result))

10.【重点】默认形参(缺省形参)

# 默认参数(缺省形参)
' 形参设置默认值则为缺省参数,也叫做默认参数
' 调用函数时,如果没有传入默认参数对应的实参,则使用默认值
' 默认参数必须在普通参数的后边,就是定义形参时,第一个形参必须是普通参数,不能给值
例: def fun_(a,b=10,c=20)   #固定格式
				print(a, b, c)
  
  ###「重点」 ###
  #  在函数调用传实参时,实参都是从第一个形参开始赋值

11.【重点】*agrs 元组型不定长参数

形参变量名前面加上一个 * (星号),这个参数则为元 组型不定长 位置 参数

【 重 点 】
1.# 函数可以定义 不定长参数 , 用于接收 任 意 数 量 的 位 置 实 参
2.# 不定长参数本质时,将传递的 参 数 包 装 成 元 组
3.# 函数定义 形参 前加上一个 * ,这个参数就是不定长参数



例:
def foo(*agrs):
  print(args,type(args))

# 函数调用
foo()
foo(1,2,3)   # (1,2,3)  <class 'tuple'>
# 类型是元组类型


12.【重点】**agrs 字典型不定长参数

定义参数时需要在形参名前添加** (两个星号),则为字典型不定长 关键字 参数

字典型可变形参必须在形参列表的后面

# 函数形参变量,前面有2个 * ,字典型不定长参数,也叫做 关 键 字 型 不定长参数
# 函数内部使用,无需加 *
# **kwargs,这个参数一定是放在最右边(一般这种形参名起名为: kwargs)
# 不定长参数,就是将传递的参数 包装成 字 典

例:
def fooo(name, **gel):
    print(gel)
    print(name)
    print(type(gel))
    
# 实参传递是要用字典格式传参,使用的是关键字传参,与位置无关

fooo(name='xiaoming',age=18, city="beijing", sex="男")

返回结果:
{'age': 18, 'city': 'beijing', 'sex': '男'}
xiaoming
<class 'dict'>

13. 强化和进阶

13.1 组包和拆包

1. 组包  -- 只能组包成 元组
	"组包,多变一"
	" 等号 = 右边有多个数据时,会自动包装为元组
  示例:
			a = 1, 2.2, "猪"
			print(a, type(a))
  #返回结果:(1, 2.2, '猪') <class 'tuple'>
      

2. 拆包 --- 列表,字符串,字典,元组都支持拆包,字典拆包结果是key
	#   如果 变量数量 = 容器长度 ,容器中的元素会一一对应赋值给变量
  ##  拆包时注意,需要拆的数据的个数要与变量的个数相同,否则程序会异常
  ### 除了对元组拆包之外,还可以对列表,字典等拆包
  "拆包,一变多"
  
  # 交换变量
  
  # 函数同时返回多个数
  例:
				def foo():
   				 return 1, 2, 3				# 1,2,3组包赋值给函数foo()

      	# 返回值直接做拆包
				s1, s2, s3 = foo()			# 这里的foo()是一个组包后的元组
				print(s1, s2, s3)   # 1 2 3
        
        
3. 字典元素拆包
	# 注:取的值是字典的key
_codes = {'a': 1, 'b': 2, 'c': 3}
for name in _codes.items():						#先依次取出字典的key赋值给变量name
    print(name)
    key, value = name								#将变量中的元组拆包,再分别赋值给变量
    print(key)
    print(value)
    

    
# 元素交换    
a=10
b=20
a,b = b, a     # 第一步:b,a组包成(b,a)
					# 第二步:然后将(b,a)拆包分别赋值个a,b;此时b赋值给了a,a赋值给了b
print(a,b)

    

13.2 引用

# 引用:是一个变量或者值的另一个名字,又称别名
		赋值本质:给右边的变量或值,起一个别名
# 可以使用 id函数 查看变量的引用地址,引用地址相等,说明指向同一个内存空间
		每一次运行程序,每次地址都可能不一样
  例如:
		a = 10
		print(id(a), id(10))  # 地址id一样,指向同一个内存空间
    # 返回  4359929360 4359929360
    说明:a是10的别名,10是a的引用,操作a就是操作10
    
    

13.3 可变和不可变类型

可变和不可变,指的是数据的内存地址的改变

1. 可变类型:列表,字典,集合
	特性:在地址不变的情况下,可以修改内容    #(修改内容,内存指向不变)
2. 不可变类型:数字类型(int, bool, float), 字符串,元组
	特性:在地址不变的情况下,不可修改内容;  #(修改内容,内存地址指向改变)

' 不 可 变 类 型'
'修改数据,内存地址不变'
# 示例:集合和列表,集合
my_list = {1, 2, 3, 4}
print("my_list: ", id(my_list))
print(my_list)
my_list.add(5)   # 集合添加数据
#my_list.append(5)   # 列表添加数据
print("my_list: ", id(my_list))
print(my_list)
# 结果输出,修改数据内存地址不变
my_list:  4509703072     # 修改前数据的内存地址
{1, 2, 3, 4}
my_list:  4509703072     # 修改数据后的内存地址
{1, 2, 3, 4, 5}



'可 变 类 型'
'修改数据,内存地址会改变'

# 示例:字符串,元组,数据类型(int,bool,float)
a = 10
print("a:", id(a))
a = 20
print("a:", id(a))
# 输出,地址改变
a: 4484587024
a: 4484587344

a = (1, 2, 3)
print(a, id(a))
a = (4, 5, 6)
print(a, id(a))
# 输出,地址改变
(1, 2, 3) 4379709184
(4, 5, 6) 4379709376


13.4 range 函数

# range 函数    左闭右开区间
1. range 语法: for 变量 in range(开始位置,结束位置,步长)
2. range(起始,结束,步长):起始默认是0,步长默认是1
3. range() 方法可以生成一段左闭右开的 '整数' 范围
4. 特点1: 它接受的参数必须是整数,可以是负数,但不能是浮点数和其他类型
   特点2:它是不可变的序列类型,可以进行判断元素,查找元素,切片等操作,但不能修改元素
    特点3: 它是可迭代对象,却不是迭代器

# 示例1:
for 变量 in range(5): , range(5)序列范围,使用和切片一样,但是以逗号,隔开,打印结果为 0,1,2,3,4 (前闭后开区间的值)

# 示例2:
_sum = 0
for i in range(0,101):
    _sum = _sum + i
print("_sum: ", _sum)


13.5 列表推导式

一行代码,快速生成列表的作用

能够使用列表推导式创建包含 1- 100 之间元素的列表

​ 格式:[计算公式 for 循环体 if 判断]

# 通过列表推导式,实现上面的效果 [计算公式 for循环体]
1. for i in range(1, 101), 取出1,放在i变量中,i追加到列表
2. 循环下一步,取出2, 放在i变量中, i追加到列表
# 重复,直到退出循环
# 列表推导式:
list = [i for i in range(1,101)]
print(list)

示例2:取出100内的偶数添加到列表中
list = [ i for i in range(101) if i % 2 == 0 ]
print(list)
# 注:中括号里第一个 i 会接受 i % 2 == 0 的结果,是一个赋值过程
list = [i * 2 for i in range(11) if i % 2 == 0]
print(list)
# 结果:[0, 4, 8, 12, 16, 20]


示例:
# 一个for循环
my_ls = [x for x in range(4)]
# 两个for循环
my_ls = [(x,y) for x in range(1,3) for y in range(2,4)]
# 三个for循环
my_ls = [(x,y,z) for x in range(1,2) for y in range(1,3) for z in range(1,4)]

面试题:
将100以内的数字列表每3个数放进一个小列表中,小列表组成一个大列表输出
a = [x for x in range(1,101)]
b = [a[x:x+3] for x in range(0,len(a),3)]
打印 b 

13.6 匿名函数

通过匿名函数编写简单的函数

# 关 键 字 : lambda
##. 格式: 
			 'lambda 形参1,形参2,... : 单行表达式 或者 函数调用'
																		'⬆️只能写一行表达式'
特点
1. 匿名函数是简单普通函数的简洁写法
2. 匿名函数没有函数名字
3. 匿名函数是有返回值的,默认带了return做返回,函数名()就是调用函数
4. 一种用法是,给这个匿名函数设置一个接受值 ret ,ret = (lambda: 1+1)()
5. 另一种用法是给这个接受值赋值给一个变量,func = ret() , print(func)

匿名函数的优点:
1. 不用取名称,因为给函数取名是比较头疼的一件事,特别是函数比较多的时候
2. 可以直接在使用的地方定义,如果需要修改,直接找到修改即可,方便以后代码的维护工作
3. 语法结构简单,不用使用def 函数名(参数名):这种方式定义,直接使用lambda 参数:返回值 定义即可
  
# 无参无返回值
ret = (lambda: 1 + 2)()
print(ret)  
  
'有参有返回 示例'  
ret = (lambda a, b : a + b )(20, 30)
		#	(关键字 形参1,形参2 : 函数内代码块)(实参1,实参2)		
print(ret())

# 等价于下面的写法
	def ret(a,b):
    	return a + b
  foo = ret(20, 30)
  print(foo)
  
  
  
# 示例1:
1.# 给匿名函数起一个变量名,变量名() 就是调用函数
func = lambda: 1 + 1     # 给匿名函数起一个函数名字叫做 func 
												 # 函数体就是返回值的内容,无需return
ret = func()						 # 返回值变量名 = 变量名()
												 # 匿名函数调用,变量名() 就是调用函数
  

 # 示例2:
def foo(fn):
    ret = fn()			# 函数里面的形参是一个匿名函数
    print(ret)

foo(lambda: 1 + 1)    # 在调用foo()函数是,实参传的是一个匿名函数


13.7 递归函数

# 递归函数的特点
		'函数自己调用自己'
  		'一定要有出口'
1. 函数递归:函数调用自己,了解即可,尽量通过画图,理解流程
2. 递归函数一般会在特定情况下, 不再调用函数本身

'示例 用递归函数实现阶乘:
 5! = 5*4*3*2*1
 n! = n*(n-1)*(n-2)...1

def foo(n):
    if n == 1:					# 递归函数的出口
        return 1
    else:
        ret = n * foo(n - 1)		# 函数嵌套,嵌套的函数是自己本身
        return ret

_ret = foo(5)
print(_ret)

'注:若没有递归函数的出口,就会超过python解释器的递归最大深度996,解释器就会终止递归'


13.8 enumerate 函数和 del 函数

1. 通过 for 配合 enumerate 遍历容器同时获取元素索引位置和元素
		语法: for i, value in enumerate(容器):
'总结:相当于遍历查询列表中元素的 索引位置,和元素内容 '

例如:
my_list = [{'name': "xiaoming", 'age': 20, 'sex': "男"},
           {'name': "xiaozhang", 'age': 30, 'sex': "男"}]
for i, item_dict in enumerate(my_list):
    print(i, item_dict)
# 返回结果 
# 元素的索引位置    元素内容
0 {'name': 'xiaoming', 'age': 20, 'sex': '男'}
1 {'name': 'xiaozhang', 'age': 30, 'sex': '男'}


2. 通过del删除列表元素: 
		语法: del 列表[索引]
' 总结: 通过索引删除列表中对应的元素'
  del my_list[0]
  或者写法:
  del(my_list[0])

13.9 学生名片管理系统

14. 文件操作

14.1 文件介绍

​ 文件的作用:把一些长期存放起来,可以让程序下一次执行的时候直接使用,而不必重新制作一份

'数据持久化存储'

14.2 文件的打开和关闭

# 文件的操作流程
1. 打开或者新建一个文件
2. 读/写 数据
3. 关闭文件

# 打开文件
在python中,使用open函数,可以打开一个已经存在的文件,或者创建一个不存在的新文件
格式:
	文件变量 = open(路径+文件名称,访问模式) # 文件名称和访问模式都是字符串格式,不写路径就是当前目录
 
'''
  访问模式:
 r 只读模式,文件不存在会报错,默认打开方式
 w 只写模式,文件存在会先清空,不存在会创建新文件
 '''

文件中包含中文时,打开文件时设置文件编码,windows的python默认打开文件的编码是GBK:
f = open('xxx.txt'.'r',encoding = "utf-8")
'关键字 encoding'  
  
# 关闭文件   写完文件一定要关闭,否则内容不会保存
	注: 如果程序结束也会自动关闭文件的,但一定要程序执行结束,否则不会保存 
1.手动关闭
关闭文件的作用是,为了释放资源
'语法格式: 文件变量.close()

2.自动关闭
with open("文件名", "文件操作") as 文件别名(文件变量):
		# 文件操作
  	pass

  
  
  示例:
with open("test.txt", "w") as f:  # 打开的结果赋值给f变量,是文件变量的别名
	pass			# 文件操作完后会自动关闭
执行完缩进代码,会自动关闭文件

14.3 文件的读写

# 写数据
使用 write() 可以完成向文件写入数据
'语法格式:文件变量.write(编写文件内容)
				注:写文件前要先用open打开文件
示例:
f = open("sss.txt", 'w')
f.write('hello python')
...
f.close()

示例:
a = open("a.txt", "w")		# 创建一个新文件
a.write("hello ljjjjj")		# 向新文件中写内容
a.close()
a = open("a.txt", 'r')		# 只读方式打开新文件
b = a.read()							# 读新文件并赋值给b
a.close()
print(b)									# 打印变量b



# 读数据
使用read() 可以读取文件
'语法格式:内容变量 = 文件.read(n)
			注:n 为读取多少个字符数,不设置则全部读取

## readlines   文件中如果有回车行也会计算为行,换行符作分隔符
	readlines() 函数可以一次全部读出,读取所有的行,'按行作分隔条件',返回列表(每行内容是一个元素)
  '语法格式:内容列表变量 = 文件变量.readlines()
  
  # 缺点总结:在使用readlines 读取一个多行文件时,返回的结果会是一个组包后的一个列表  ['我\n', '在\n', '学\n', 'ptthon\n', '啊'] ,而不是原格式的多行内容

with open("xxx.txt", "w") as f:
    f.write("我\n在\n学\nptthon\n啊")
  
 # for 循环遍历读取多行内容
				f = open("123.txt",'r')
				b = f.readlines()
				print(b)

				for row in b :
   				 # print(row)
   				 print(row, end="")
    		f.close()
    

    
## readline		文件中有回车会包含,换行符作分隔符,结果是一个列表
readline()  每次读取一行数据,如果文件是多行内容,需要重复执行此函数
'语法格式:内容变量 = 文件变量.readline()

# 缺点总结:在使用readline读取文件时,如果文件中的内容是多行的,readline却只会读取第一行,所以就要用下述 while 循环来读取整个文件的内容,并且按照换行输出

示例:循环逐行读取文件内容
f = open("123.txt",'r')
while True:
  ret = f.readline()
  # if ret == "":		# 读到空时执行break
  if not ret:				# true 取反判断,读到文件为空时跳出
    break
  print(ret)
  f.close()
  

14.4 访问文件 r, w, a 的区别

# 文件访问模式
	r		只读,默认模式,文件不存在会报错
  w		只写,文件存在先清空,不存在先创建
  a		追加写,文件存在则追加内容,不存在会创建新文件

# 绝对路径和相对路径
open 第一个参数说明:
open 第一个参数,实际上是 (路径 + 文件名)
路径分为:绝对路径和相对路径

'绝对路径:是只文件在硬盘上真实存在的路径,是电脑完整的路径
注意:写代码的时候,windows下路径的 \ 需要改为 \\ 或者改为 /
一般而言,写程序很少使用到绝对路径,因为程序在本机运行可以找到这个绝对路径,但是,把程序拷贝给别人运行,别人电脑不一定有这个路径

'相对路径:相对于自己的目标文件位置
通常使用相对路径
注意:
	#	../1.txt: 上一级路径下的1.txt
	#	1.txt:等价于./1.txt,当前路径下的1.txt

14.5 应用:用python实现文件备份案例

版本一:

版本二:读取用户输入的文件名对以存在的文件进行备份

# 对文件名进行切片拼接
old_file_name = input("输入需要拷贝的文件名: ")
pos = old_file_name.rfind(".")

l_file_name = old_file_name[:pos]
r_file_name = old_file_name[pos:]
new_file_name = l_file_name + "[备份]" + r_file_name

# 开始备份文件
old_file = open(old_file_name, 'r')
new_file = open(new_file_name, 'w')

while True:
    ret = old_file.read(1024)   # 一次读1024个字节
    if ret:
        new_file.write(ret)
    else:
        break

old_file.close()
new_file.close()

版本三:利用python备份二进制格式文件

示例 对 .mp4结尾的文件进行备份

# 对文件名进行切片拼接
old_file_name = input("输入需要拷贝的文件名: ")
pos = old_file_name.rfind(".")

l_file_name = old_file_name[:pos]
r_file_name = old_file_name[pos:]
new_file_name = l_file_name + "[备份]" + r_file_name

# 开始备份文件,为了处理任何格式文件,
# 就以二进制格式读文件 rw ,和 二进制格式写 wb 文件
old_file = open(old_file_name, 'rb')
new_file = open(new_file_name, 'wb')

while True:
    ret = old_file.read(1024)   # 一次读1024个字节
    new_file.write(ret)
    if not ret:
      break

old_file.close()
new_file.close()

14.6 文件相关操作



文件的重命名,删除等一系列操作,是使用python中 os 模块中的功能
1)导入模块,只需要导入一次即可
	import os
2)使用os中的方法,完成功能

# 功能
1. 文件重命名
	os模块中的 rename() 进行重命名操作
  语法格式: os.rename(旧的文件名,新的文件名)
  
2. 删除文件
	os模块中的 remove() 进行文件的删除,但不能删除文件夹
  语法格式: os.remove(待删除的文件名)
  
3. 创建空文件
	创建文件夹,只能创建文件夹,不能创建普通文件
  语法格式:os.mkdir(文件夹的名字)

4. 删除空文件夹
	删除文件夹,只能删除空的文件夹
  语法格式: os.redir(待删除文件夹的名字)

5. 获取当前目录
	获取当前工作的路径
  语法格式: 路径变量 = os.getcwd
  
6. 改变默认目录
	改变默认目录,切换指定的路径
  语法格式:os.chdir(改变的路径)

7. 获取目录列表
	获取某个目录的文件信息,获取文件夹或文件的名字
  语法格式: 目录列表变量 = os.listdir(指定某个目录)
  		如果不指定目录,默认当前路径

8. 判断文件是否存在
	语法格式:os.path.exists(需要判断的文件)
  		如果文件存在返回 True , 如果文件不存在返回 False
    
	

14.7 文件版学生名片管理系统

### 字符转换 ###
# str(容器变量)  将容器变量转换成字符串类型'
user_list = [{'name': 'tom', 'age': 20, 'tel': '139'}]
my_list = str(user_list)

# eval(字符串内容)   类型转换,括号中看着像什么数据类型就转换成什么数据类型
user_list = "[{'name': 'tom', 'age': 20, 'tel': '139'}]"
new_list = eval(user_list)
print(new_list, type(new_list), new_list[0], type(new_list[0]))

输出结果:
[{'name': 'tom', 'age': 20, 'tel': '139'}] <class 'list'> {'name': 'tom', 'age': 20, 'tel': '139'} <class 'dict'>


# 将学生列表写入文件
with open("stu_info.txt", "w") as file:
  file.write(str(user_list))
  

14.8 字符串,容器类型相互转换

str(容器变量)			将容器变量转换为一个字符串
eval(字符串内容)  返回传入字符串内容的结果,字符串里面是什么类型就转换成什么类型

os 模块的一般操作

文件重命名:os.rename(旧的文件名,新的文件名)
改变默认目录:os.chdir(改变的路径)
获取目录列表:目录列表变量 = os.listdir(指定某个目录)
判断文件是否存在:os.path.exists(需要判断的文件)

15. 面向对象1 : 类和对象,魔法方法

15.1 理解面向对象

python 中一切皆对象,是一门面向对象的语言 !

# 面向对象和面向过程的区别
	面向过程:把编程的任务划分为一个一个的步骤,然后按照步骤分别去执行
	面向过程的思想:需要实现一个功能时,都需要开发按步骤和过程来进行开发,没一个步骤都亲力亲为

# 理解面向对象
	总结:面向对象就是将编程当成是一个事物,对外界来说,事物是直接使用的,不用去管他内部的情况。而编程就是设置事物能够做什么事

化简代码的作用,面向对象是一种抽象化的编程思想,很多编程语言中都有的一种思想

15.2 类和对象

类和对象的关系:
在面向对象编程过程中,有两个重要组成部分:'类' 和 '对象'
'类和对象的关系: 用类去创建一个(实例化)对象'
面向对象就要先创建类,再用类去创建对象,对象就可以实现一些功能

15.2.1 类 -- 类是由方法(函数)和属性(变量)构成
类是对一系列具有共同'特征'和'行为'的事物的统称,是一个'抽象的概念',不是真实存在的事物。
		'特征'即是属性--变量
  	'行为'既是方法--函数
    对象的公共属性定义为类,对象的抽象化是类
 
某些事物的抽象化特征,就是类,例如所有的汽车,而自己每天开的车就是类,因为是具体的'实物'
15.2.1 对象
对象是'类创建出来的真实存在的事物',例如:洗衣机
注意:开发中,先有类,再有对象
用类来创建对象
# 具体某个东西就是对象,而对象的抽象就是类
例如:'所有电脑,所有联想电脑,所有苹果电脑都是类',而具体到我自己手上的电脑实物就是对象,'实例化的东西就是对象'

​ 类是对象的模板(不占内存空间),对象是类的实例(占内存空间)

​ 类相当于图纸,对象相当于根据图纸制造的实物

15.3 面向对象的实现方法

15.3.1 定义类
python2 中类分为:经典类和新式类
''' 语法
		class 类名(object):
  			def 方法名(self):
  			pass
    	.....
 '''

# 注意: 类名要满足标识符命名规则,同时遵循 大 驼 峰 命 名 习 惯

# object 是所有类的祖先

新式类语法:
	class 类名(object):
    	def 方法名(self):
        	pass
          
 经典类语法:
		class 类名:
    		代码
      	....

15.3.2 创建对象

​ 对象又名 实例

# 语法:
对象名 = 类名()
15.3.3 self

​ # self 指的是调用该函数的对象;虽然定义方法时设置第一个参数 self,但是 调用方法时不要传递对应self的参数,解释器自动处理

​ # 在python类中规定,实现方法的第一个参数就是实例对象本身,并且约定俗成,把其名字写为self

​ # 某个对象调用其方法时,Python解释器会自动把这个对象作为第一个参数传递给方法

  • 通俗理解:哪个对象调用该方法,该方法中self就是这个对象

    【self 的作用】:

    ​ 在方法中使用 self,可以获取到调用当前方法的对象,进而获取到该对象的属性和方法

    ​ self作用:为了区分不同对象的属性(变量)和方法(函数)

'self是什么:哪个对象调用方法,方法中的self就是这个对象本身
'self作用:区分不同对象的属性和方法

# 示例:
class Washer():
    def wash(self):   # 定义Washer类中的方法,self是默认属性
        print('洗衣服')
        print(id(self)


a = Washer()			# 将类赋值给对象a
a.wash()					# 调用对象中的属性和方法
b = Washer()			# 将类再一次赋值给对象b
b.wash()					# 调用对象中的属性和方法
返回结果中,两个对象调用一个类的内存地址是不同的
说明:一个类可以创建多个对象,多个对象调用函数时,self地址是不同的
              
print(id(a))
# 此时会发现,id(self)的返回地址和id(a)返回的地址相同,说明是一个东西

类作为对象的模具,根据类可以创建多个对象

15.4 添加和获取对象属性

属性即是特征,比如:洗衣机的长,宽,高

对象属性既可以在类外面添加和获取,也能在类里面添加和获取

15.4.1 类外面添加对象属性
# 语法
对象名.属性名 = 值			# 第一次赋值是定义,第二次赋值就是修改

例如:
a.height = 800
a.width = 500
15.4.2 类的外面获取对象属性
# 语法
对象名.属性名

例如:调用对象属性
print(f'a洗衣机的宽度是{a.width}')
print(f'a洗衣机的高度是{a.height}')


15.4.3 类里面获取对象属性
# 语法:  self.属性名

示例:
class Washer():				# 创建类
    def print_info(self):			# 创建类的属性和方法
        print(f'a 洗衣机的宽度是{self.width}') #类里面获取对象的属性
        print(f'a 洗衣机的高度是{self.height}')

a = Washer()			# 创建对象(调用类)

a.width = 500			# 添加属性
a.height = 800

a.print_info()		# 对象调用方法

15.5 魔法方法

	'在Python中,所有以 __ 双下划线包起来的方法,都统称为 Magic Method ,中文称 魔法方法
	'魔法方法是系统提供好的方法名字,用户需重新实现它
  '魔法方法一般情况下无需手动调用,在合适的时候自动会调用
	
  在Python中,__xx__() 的函数叫做魔法方法,指的是具有特殊功能的函数。
# 魔法方法就是 特 殊 的 函 数,	名字一般是固定的,不被对象调用,而被系统自己调用


15.5.1 魔法方法: _init_()
__init__() 方法的作用:初始化对象;对象创建时被系统自动调用,是给对象添加属性用的

class Washer():
    def __init__(self):					# 添加初始化属性
        self.width = 500
        self.height = 800
        print('init方法调用了')

    def print_info(self):				# 添加方法
        print(f'a 洗衣机的宽度是{self.width}') #调用初始属性
        print(f'a 洗衣机的高度是{self.height}')


a = Washer()
a.print_info()


# 使用类创建对象会经历两个步骤:是由系统自动完成的
'1. 开辟空间:系统会自动调用__new__魔法方法(另一种魔法方法)
'2. 对象初始化:系统会自动调用__init__魔法方法



'注意:'
	__init__() 方法,在创建一个对象时默认被调用,不需要手动调用
  __init__(self) 中的 self 参数,不需要开发者传递,python解释器会自动把当前的对象引用传递过去
  

15.5.2 带参数的 _init_()

作用是:一个类可以创建多个对象,针对多个对象设置不同的初始化属性就用带参数的 init 定义。

  • __init__(self)除了默认参数self,还可以设置任意个数的自定义参数,例如:__init__(self,x,y,z)
  • init方法 设置的自定义参数必须和创建对象时传递的参数保持一致,例如:对象变量名 = 类名(x,y,z)
  • 开发者可以 设置自定义参数,为对象的默认属性提供 不同的初始值
class Washer():						# ⬇️带参数的init
    def __init__(self, width, height):   # 添加自定义对象形参,来接收数据
        # 初始化属性,添加实例属性⬇️
        self.width = width         # 形参赋值给变量
        self.height = height

    def print_info(self):		# 定义方法
        print(f'a 洗衣机的宽度是{self.width}')    # 调用变量
        print(f'a 洗衣机的高度是{self.height}')


#  1 个类创建多个对象并且属性值不同,再给对象传值
# 创建对象1
a = Washer(500, 800)     # 给类的形参传值
a.print_info()

步骤分解:系统使用对象a调用__init__()方法,并且传递参数a给self,500参数给width,800给height

# 创建对象2
b = Washer(300,400)    
b.print_info()

15.5.3 魔法方法 : _str_()

​ 当使用print输出对象的数据时,默认打印对象的内存地址。如果类定义了_str_ 方法,那么就会打印在这个方法中 return 的数据

​ _str_( ) 方法的返回值必须是字符串类型

​ _str_( ) 方法作用主要返回对象属性信息,print(对象变量名) 输出对象时,直接输出_str_( )方法返回的描述信息

"""
__str__方法:
    1. 返回值必须是字符串类型
    2. print(对象变量名)  对象变量名的位置替换为__str__()方法返回值的内容
    3. __str__方法大部分情况下是不需要定义除了self之外的形参
"""
'出发点:类创建对象后,打印该对象一般返回系统的内存地址信息,而设置了__str__()这个特殊函数后,打印对象就返回的是特殊函数中的return设定值'

# 一般 str中存放的是一些解释说明的文字,并且返回这些文字,而不是返回内存地址信息

class Washer():
    def __init__(self, width, height):
        # 添加实例属性
        self.width = width
        self.height = height

    def __str__(self):			# 定义str魔法方法
        return '这是a 洗衣机的说明书,高%d,宽%d' % (self.width, self.height)
        return f"高{self.height},宽{self.width}"


a = Washer(300, 400)
print(a)    # 这是a 洗衣机的说明书

15.5.4 魔法方法:_del_()

​ 当删除对象(生命周期结束)时,python解释器也会自动调用 _del_() 函数方法,并执行该函数内的代码块,做一些清理工作

# 删除对象时的返回值
class Washer():
    def __init__(self, width, height):
        # 添加实例属性
        self.width = width
        self.height = height

    def __del__(self):
        #print(f'{self}对象已经删除')
        print('对象已经删除')


a = Washer(300, 400)		# 调用类创建对象 a
del a										# 删除对象 a
# 返回: 对象已经删除

# 或者下种方式:在函数里面定义对象调用类

当下列函数调用完毕后,里面创建的对象会销毁,生命周期结束,会自动调用__del__()方法

def foo():
  dog = Washer()
  
  
# 以下三种情况会调用此魔法方法
  1. 函数调用结束,函数中调用的对象会被销毁
  2. del 对象,销毁对象
  3. 程序结束后,所有对象会被销毁
总结:
__init__: 创建对象后自动调用方法,实现给对象添加属性和属性初始化
__str__ : print(对象)时,提交替换对象__str__的返回值
__del__ :销毁对象时,自动调用,做清理动作

主逻辑:创建对象,调用方法
需求分析 ---> 找对象 ———> 抽象类属性和类方法 --—>代码实现类
15.5.5 魔法方法:_call_()
  • 包含此方法的类创建出来的实例化对象可以当做函数来调用
15.5.5.6 魔法方法:_new_()
  • new 方法负责将类实例化为对象,init方法负责将实例化出来的对象进行初始化

16 . 面向对象2: 继承,属性,方法

16.1 私有权限

封装就是定义类的属性和方法

面向对象的三大特性:封装,继承,多态

# 面向对象的三大特性:封装,继承,多态
'面向对象的封装特性:
		1. 将属性和方法放到一起封装成一个整体(就是类),然后通过实例化对象来处理(访问类中的方法)
		2. 对类的属性和方法增加访问权限控制
# 私有属性:只能在 类 内 部 访 问,类的外部无法访问
# 私有属性的定义方法:在属性(变量)名前面加2个下划线'__',则表明该属性是私有属性,否则是共有属性


# 私有方法:只能在本类的内部访问,在类外面无法直接访问
# 私有方法(函数)的定义:跟私有属性类似,在方法名前面加上2个下划线'__',则表明该方法是私有方法
# 在类内部调用实例方法的语法格式:self.方法名()


示例:
class Dog(object):
    def __init__(self):
        self.__baby_count = 0			# 私有属性(变量)
        self.age = 1							# 共有属性(变量)

    def __level(self): 						 # 私有方法
        print("ViP")

    def prin_info(self):
        print(self.__baby_count)		# 共有方法中调用私有属性
        self.__level()							# 共有方法中调用私有方法


dog = Dog()
print(dog.__baby_count)           # 私有属性不能方法,会报错找不到变量
print(dog.age)  									# 共有属性可以访问
dog.prin_info()  									# 通过共有方法调用私有属性,和私有方法

16.2 继承 (py特点继承、封装、多态)

16.2.1 继承介绍
# 概念:
		在程序中指的是'类与类'之间的关系
# 形容:
		站在'父类'的角度来看,'父类'派生出'子类'
    站在'子类'的角度来看,'子类'继承于'父类'
    '父类'也叫做'基类','子类'也叫做'派生类'

# 继承的作用:
		继承:子类直接具有父类的属性和方法
 	 	作用:解决代码重用问题,提高开发效率
  
# 继承的语法:
		class 子类名(父类名):
  				pass
	
# 注意:
		子类对象调用方法有一个就近原则:
  			如果本类能找到方法,直接调用本类的方法即可
    		如果本类找不到,则调用父类继承过来的方法
16.2.2 单继承和多层继承
# 单继承:子类只继承一个父类(子类中指调用了一个父类)

# 多层继承:继承关系为多层传递 (孙子类中调用子类,子类调用父类,每层只传递一个类,层层传递)
示例:多层继承

class Animal(object):
    def eat(self):
        print('吃')


class Dog(Animal):
    def drink(self):
        print("喝")


class Bosi_dog(Dog):
    def bandian(self):
        print("wangwnagwang")

a = Bosi_dog()
a.bandian()
a.eat()
a.drink()

16.2.3 多继承
# 多继承:所谓多继承,即一个子类继承有多个父类,并且具有相同的特征

# 多继承的语法格式:
		class 子类名(父类1,父类2,....)
  				pass
  
# 类的继承顺序:自己不用定义此方法,解释器自己定义的,可以在调用时直接调用
	查看类的继承顺序:类名.__mro__
  
  示例:查看继承顺序
class SmallDog(object):
    def eat(self):
        print("小狗吃小东西")
class BigDog(object):
    def eat(self):
        print("大狗吃肉")

class SuperDog(SmallDog, BigDog):
    pass

print(SuperDog.__mro__)
#返回一个列表,即继承顺序
(<class '__main__.SuperDog'>, <class '__main__.SmallDog'>, <class '__main__.BigDog'>, <class 'object'>)

# 子调用父类同名方法
1. 默认调用情况:如果继承过来的2个父类的方法同名,默认调用先继承父类的同名方法'(先继承先调用)'

2. 子类调用父类同名方法
		子类调用父类同名方法:
  		1) 父类名.同名方法(self, 形参1, ...) : 调用指定父类
      2) super(类名,self).同名方法(形参1,形参2,...) :调用继承顺序中类名的下一个类的同名方法
      3) super().同名方法(形参1,...) : 调用先继承父类的同名方法
        
' 当一个子类中有多个继承父类时,调用同名方法时,就需要考虑继承顺序来调用 '
用__mro__获得继承循序,按照继承进行调用同名方法
16.2.4 私有和继承
# 私有和继承
	父类中的私有方法(2个下划线),属性不能直接继承使用,只能在父类中被调用,如果这个调用私有属性或方法的是共有方法,就可以被外界调用私有属性
  可以通过调用继承的父类的共有方法,'间接'的访问父类的私有方法和属性
# 私有属性和方法的继承访问示例:
class SmallDog(object):
    def __init__(self):
        self.__la = "小狗拉"				# 父类的私有属性

    def __eat(self):							# 父类的私有方法
        print("小狗吃小东西")

    def drink(self):							# 父类的公有方法
        self.__eat()							# 调用了父类的私有方法和属性
        print(self.__la)


class SuperDog(SmallDog):					# 子类继承父类
    pass


dog1 = SuperDog()
print(dog1)
dog1.drink()					# 子类对象访问父类的公有方法,调用父类的私有属性和方法

16.3 重写父类方法

# 父类的方法不能满足子类的需要,可以对父类的方法进行重写,重写父类的目的是为了给他扩展功能
# 在子类中定义了一个和父类同名的方法(参数也一样),即为对父类的方法重写
# 子类调用同名方法,默认只会调用子类的


# 子类调用父类的同名方法	,在类内部
	子类中调用父类同名方法:(三种方法各有优缺点)
  	1. 父类名.同名方法(self,形参1,...)
    2. super (子类名,self).同名方法(形参1,...)
    3. super().同名方法(形参1,...): 是方法2的简写,'推荐写法‘
# 重写示例:子类重写父类的属性和子类的同名方法中调用父类的方法

# 父类
class Animal(object):
    def __init__(self):
        self.type = "动物"            # 父类的属性
        print("父类的init")						# 父类的打印

    def eat(self):										# 父类的eat方法1
        print("父类的逛吃逛吃逛吃")

    def print_info(self):							# 父类的方法2
        print("父类的方法")

# 子类        
class Dog(Animal):
    def __init__(self):
        self.type = "狗"             # 可以在子类重写了父类同名的属性
        print("子类的调用")

    def eat(self):									# 与父类方法同名的子类方法
        print('子类小狗吃吃吃')
        super().eat()								# 在同名子类中调用父类的方法

    def print_info(self):						# 与父类方法同名的子类方法
      	super().__init__()					# 在同名方法中调用父类的方法
        super().eat()								# 在同名方法中调用父类的方法


a = Dog()
a.eat()
a.print_info()			# 子类中调用子类的方法
# 结果:
子类的调用
子类小狗吃吃吃
父类的逛吃逛吃逛吃		# 父类方法结果
父类的init					# 父类方法结果
父类的逛吃逛吃逛吃

16.4 多态:同一个 函数 的不同表现,

python多态是伪多态

# 多态;python的多态是伪多态
	多态:多种形态,调用同一个'函数',传递不同参数实现不同表现
  因为python是动态语言,站在用户的角度,本身就是多态,不存在非多态的情况
  实现多态的步骤:'先继承,后重写父类的方法,再调用'
  	1)实现继承关系
    2)子类'重写父类'方法
    3)通过对象调用该方法
# 多态:同一个函数,根据传参不同实现不同的结果,就是多态
class Animal(object):						# 定义父类
    def eat(self):
        print("吃东西")

class Dog(Animal):						# 定义子类1,继承父类
    def eat(self):
        print("吃骨头")

class Cat(Animal):						# 定义子类2,继承父类
    def eat(self):
        print("吃鱼")

def func(temp):								# 定义一个函数,并自定义形参
    temp.eat()

a = Dog()
b = Cat()
func(a)											# 给函数传入新参,子类1
func(b)											# 给函数传入新参,子类2
# 同一个函数根据传入的实参(实参是2个不同的对象),而结果不同,这就叫做多态

16.5 实例属性,类属性

python 是一门纯面向对象的语言,一切皆对象

# 1. 专业名词说明
		在python中"万物皆对象"
  	通过类创建的对象 又称为'实例对象,对象属性 又称为 实例属性'
   	类本身也是一个对象,执行class语句是会被创建,称为 '类对象' 为了和实例对象区分开来,我们习惯叫类
      

 # 2. 实例属性
		通过 __init__ 方法里面给实例对象添加的属性
  	在类的外面,直接通过实例对象添加的属性
   	'实例属性'必须通过'实例对象' 才能访问
    
    
 # 3. 类属性
		类属性就是 '类对象' 所拥有的属性,它被 '该类的所有实例对象共同所有'(相当于这个类里面的全局变量)
  	定义在'类里面,类方法外面'的变量就是'类属性'
    类属性可以使用'类名' 或'实例对象' 访问,'推荐使用类名访问'
    
# 4 类属性和实例属性的区别
		类属性就是'类对象'所拥有的属性,它被 '该类的所有实例对象所共有'
  	'实例属性'要求'每个对象'为其'单独开辟一份内存空间',只属于某个实例对象的
    
# 5 *注意点:修改类属性时
		类属性 只 能 通 过 类 对 象 修 改,不 能 通 过 实 例 对 象 修 改;    
  例:dog1 = Dog()		# 对象变量名 = 类名
  Dog.name = “狗”    # 通过类名修改
	dog1.name = “小狗” # (不能)通过实例对象名修改
  '因为对象名dog1来修改name属性,其实不是修改,而是添加了一个同名的属性,跟类里面同名的name属性,其实不是同一个'

# 6 类属性和实例属性同名
		如果类属性和实例属性同名,实例对象名只能操作实例属性
  	结论:'操作类属性建议使用类名',避免不必要的麻烦
    '类名操作类属性,实例名操作实例属性'
    示例:
					class Dog(object):
   				 # 定义类属性: 统计使用Dog类创建类多少对象,是所有对象共同的
   				 count = 666

    				def __init__(self):
        				# 实例属性
        				self.count = 250

			d1 = Dog()  
			print(d1.count, Dog.count)		#(实例属性,类属性)各不同
    	结果:250 666


    

# 7 私有类属性
		类属性也可以设置为'私有',前面添加两个下划线__
# 类属性和实例属性的示例
class Dog(object):
   	count = 0 	# 定义类属性: 统计使用Dog类创建类多少对象,是所有对象共同的
		def __init__(self, _name):
     		self.name = _name 	# 定义示例属性:每个实例对象特有的
   			Dog.count += 1		# 每次调用__init__时,count计数加1

print(Dog.count)
dog1 = Dog("狗1")
print(dog1.name, Dog.count)

dog2 = Dog("狗2")
print(dog1.name, Dog.count)

dog3 = Dog("狗3")
print(dog1.name, Dog.count)
# 结果
0
狗1 1
狗1 2
狗1 3

# 总结:狗1,狗2,狗3 是实例属性,属于每个实例(对象)的,count是类属性是所有实例(对象)共有的


16.6 类方法,静态方法

​ 类方法目的:在不定义对象实例的情况下,调用类里面的方法

# 1.类方法
	'类对象所拥有的方法',主要为了在没有创建实例对象前提下,处理类属性
  需要用装饰器 @classmethod 来标识其为类方法
  对于类方法,'第一个参数必须是类对象(代表类)',一般以 cls 作为第一个参数,这个参数不用人为传参,解释器会自动处理
  
 '''
 类方法:为了方便处理类属性
 	1. 用装饰器 @classmethod 来标识其为类方法
 	2. 一般以 cls 作为第一个参数,代表当前这个类,这个参数不用人为传参,解释器自动执行
 	3. 类方法调用:
 		3.1 类名.类方法()			推荐用法
 		3.2 实例对象名.类方法()
 '''

示例:在创建对象的情况下,调用类里面的方法
方法一:推荐用法
class Dog(object):
    count = 1           # 类属性
    @classmethod
    def print_count(cls):       # 实例方法:创建实例对象后才能调用的方法
        print(Dog.count)

Dog.print_count()

方法二:不推荐用法
定义了对象d1来调用类中的方法,其中cls会把d1对象替换
class Dog(object):
    count = 1  # 类属性

    @classmethod
    def print_count(cls):  # 实例方法:创建实例对象后才能调用的方法
       print(cls.count)

d1 = Dog()
d1.print_count()

# 2 静态方法
				#类中定义函数时,不设置任何形参,就是静态方法#
	需要通过解释器 @staticmthod 来进行修饰,'静态方法默认情况下,既不传递类对象也不传递实例对象(形参没有self/cls)
  当方法中 既不需要使用实例对象,也不需要使用类对象 时,定义静态方法
  取消不需要的参数传递,有利于 '减少不必要的内存占用和性能消耗'
  静态方法 也能够通过 实例对象 和类对象(类名)去访问
  '''
  静态方法:
  	1. 需要通过解释器@staticmethod 来进行修饰默认情况下
  	2. 既不传递类对象也不传递实例对象(形参没有self/cls)
  	3. 静态方法调用
  		3.1 类名.静态方法()				推荐用法
  		3.2 实例对象名.静态方法()
  '''
 示例:
class Dog(object):
    # 需要通过装饰器进行修饰
    @staticmethod

    def foo():			# 函数括号内没有任何形参
        # 实例属性:self.属性
        # 类属性: cls.属性
        print("一个与示例属性和类属性无关的函数")
Dog.foo()

16.7 总结

类方法,示例方法,静态方法的区别
(就是类中的函数定义方法的区别,根据是否需要形参或者被对象还是类调用而做区别)

#定义区别
class 类名(object):
  def 实例方法名(self):			#定义实例方法,可以通过对象所执行
    pass
  
  @classmethod		# 装饰器
  def 类方法名(cls):				#类方法,不定义对象,在类外面直接用类名来调用
    pass
  
  @staticmethod		# 装饰器
  def 静态方法名():				#静态方法,没有形参,也不需要形参时的定义方法,简化
    pass

17. 异常 模块

17.1 异常介绍

# 异常的定义:
		程序在运行期间,当Python检测到一个错误时,解释器就无法执行(俗成:程序崩溃)了,反而出现了一些错误的提示,这就是所谓的"异常"
# 注意:
		异常不是语法错误,语法错误,是程序写错了,异常是指程序已经运行后的非语法错误

17.2 异常处理

处理异常的目的:

​ 1. 只要解释器检查到异常错误,默认执行的动作是终止程序

​ 2. 处理异常目的:防止程序退出,保证程序正常执行

捕获异常:防止程序结束

# 1. 语法:
			try...except
  格式:
  		try:
      		可能发生异常的代码
      except:
        	# 处理异常的代码
        1. 如果try里面发生异常
        2. 自动跳转到 except 里面
  """
  	把可能出现问题的代码,放在try中
  	把处理异常的代码,放在 except 中
  	except 后面没有指定异常类型,可以捕获任意类型的异常
  """    
  
  
  
  # 2. 捕获指定异常类型
  	语法格式:
    			try:
        			可能发生异常的代码
          except 异常类型:
            	处理异常的代码
        
  
  # 3. except 捕获多个异常
  	语法格式:
    		try:
        		可能发生异常的代码
        except(异常类型1,异常类型2):
          	处理异常的代码
            
   
  
  
  # 4. 获取异常的信息描述
  	语法格式:
    		"""
    			try:
    					可能发生异常的代码
    			except 异常类型 as 异常对象名:
    					print(异常对象名) 即可获取异常的信息描述
    		"""
      
      
      
   # 4. 捕获任意类型的异常
  	语法格式:
    		"""
    		try:
    				可能发生异常的代码
    		except Exception as 异常对象名:
    				Exception 为异常类的父类
    		"""
      
      
捕获异常示例:
# 捕获多个异常示例:
try:
    f = open("ssss.txt", 'r')
    print(10/0)

except (FileNotFoundError, ZeroDivisionError):
    print("捕获到文件不存字异常,和被除数为0的异常")
    
    
# 捕获异常并且获取异常报错信息
try:
    f = open("ssss.txt", 'r')
    # print(10/0)

except FileNotFoundError as e:
    print("捕获到文件不存字异常", e)
返回:捕获到文件不存字异常 [Errno 2] No such file or directory: 'ssss.txt'
  
 

# 捕获异常时,获取任意类型的异常
这里的任意异常就是在捕获异常时不用再设置异常的类型,而任意类型都可以被捕获
其实是一种多态,捕获的任意异常存在一个变量 Exception 中并且 给别名 e 存储起来
try:
    f = open("ssss.txt", 'r')
    # print(10/0)

except Exception as e:
    print("捕获到文件不存字异常", e)

异常中 else

在if中,它的作用是当条件不满足是执行的实行
同样在 try...except... 中也是如此,即如果没有捕获到异常,那么就执行else中的事情

# 语法格式:
"""
try:
		可能发生异常的代码
except:
		处理异常的代码
else:
		没有发生异常,except不满足执行else
"""

try...finally... (完整格式)

语法格式:
'''
try:
		可能发生异常的代码
except:
		处理异常的代码
else:
		没有发生异常,except 不满足执行else
finally:
		不管有没有异常,最终都要执行
'''

示例:

try:
    print('=' * 20)
    # num = 333
    # print(num)
    open("a", 'r')
    
except Exception as e:
    print("捕获到异常:" , e )

else:
    print("没有异常")

finally:
    print("不管有没有异常,都打印")


异常小结:
1. 处理异常的目的:
		只要解释器检查到异常错误,默认执行的动作是终止程序,为了防止程序退出,保证程序正常执行,需要认为处理异常
2. 捕获处理异常
'''
try:
		可能发生异常的代码
except:
		处理异常的代码
else:
		没有发生异常,except 不满足执行else
finally:
		不管有没有异常,最终都要执行
'''

17.3 异常传递

1. 异常传递特点
		如果异常在内部产生,如果内部不捕获处理,这个异常会向外部传递
  
2. 异常嵌套
	try 嵌套时,如果内存try没有捕获处理该异常,就会向外层try进行传递
  
3. 函数嵌套
	函数嵌套时,如果内层函数没有捕获处理该异常,就会向外层函数进行传递
  
  
  

17.4 自定义异常

1. 抛出自定义的异常
		用户用 raise 语句 来人为抛出一个异常
  	异常/错误对象必须有一个名字,且他们应是 Exception 类的子类
    
 # 语法格式:先写类,再继承该类,再调用报错
		1). 自定义异常类
  	class 自定义异常类名字(Exception):
    		1.1 重写 __init__(self, 形参1,形参2,...)
      				# 建议调用父类的init,先做父类的初始化工作
        		super().__init__()
               	自己写的代码
            
        1.2 重新写 __str__(), 返回提示信息
        
        
    2)抛出异常类
    raise 自定义异常类名字(实参1,实参2,...)
    
    
    
  示例:
class NumberError(Exception):	# 定义自定义异常的类,父类一定是Exception
    def __init__(self, _user_len, _match_len):
        super().__init__()		# 调用父类的同名初始化函数
        self.user_len = _user_len
        self.match_len = _match_len

    def __str__(self):		# 重写定义异常信息
        return f"用户输入的手机号长度:{self.user_len},但要求的长度是{self.match_len}"


try:				# 调用上述类
    iphone_num = input("请输入手机号码:")
    if len(iphone_num) != 11:
        raise NumberError(len(iphone_num), 11)	# 调用自定义异常判断类
except NumberError as e:
    print("异常信息为:", e)

    

17.5 模块介绍

介绍:
1. 模块是一个由Python代码组成的文件,就是一个以.py 结尾的文件
2. 模块包含函数,类和变量,还可以包括可运行的代码
3. 模块的主要作用:
		提高了代码的可维护性
  	一个模块编写完毕后,其他模块直接调用,不用再从零开始写代码了
    避免名字冲突
    
   
# 模块导入 import
import 导入模块,把整个模块都加载进来
语法格式:
	"""
		导入格式:	import 模块名
		使用格式:	模块名.函数		模块名.类名		模块名.变量名
	"""
  
  
  
# 模块导入 from...import导入模块中需要的内容
语法格式:
		"""
		导入格式:from 模块名 import 需使用的函数,类,变量
		使用格式:函数,类,变量			无需通过模块名引用
		"""
缺点是:有可能引起变量名冲突(就是导入的模块下的变量名与本身代码中的变量冲突)
  

  
# 模块导入 from...import * 导入模块所有的内容(也不是所有,而是__all__变量所包含的才会被导入)
语法格式:
	"""
		导入格式:from 模块名 import *
		使用格式:函数,类,变量			无需通过模块名引用
	"""
  
  
  # import 和 from...import...导入模块的区别
  import 导入模块,把整个模块都加载进来
  from...import... 导入模块时,是把需要的模块中的函数,类,变量导入进来,但容易造成名字冲突
  
  
  
# import...as...给导入的模块取别名
	把复杂名字改写简单些
  把已经同名的名字改一个不同的名字
  
语法格式:
	"""
	模块起别名
	导入格式: import 模块 as 模块别名
	使用格式: 模块别名.工具(工具指函数,类,变量)
	
	模块工具起别名
	导入格式:from 模块 import 工具 as 工具别名
	使用格式:工具别名					(无需通过模块名引用)
	"""
  
  
  
  # 模块的搜索路径
  当导入一个模块,Python解析器对模块位置的搜索顺序是:
  1. 当前路径
  2. 如果不在当前目录,python则搜索系统路径
  3. 模块搜索路径存储在system模块的sys.path变量中
  
  示例:
  	import sys
    print(sys.path)
    
    
   

17.6 模块制作

# 定义自己的模块
	在python中,每个python文件都可以作为一个模块,模块的名字就是文件的名字。
  
# 调用自己定义的模块
	import 导入模块
  
  
# 测试模块
在实际开发中,当一个开发人员编写完一个模块后,为了让模块能够在项目中达到想要的效果,这个开发人员会自行在模块文件中添加一些测试信息

注意:
		模块文件,应该是单独执行时,才执行其测试代码
  	导入模块文件时,不应该执行测试代码
    python 中变量 __name__ 能解决上述问题
    
# 模块中的 __name__
直接运行此文件,__name__的结果为 __main__
此文件被当作模块文件导入时, __name__ 的结果不为 __main__

如果不想导包把模块的测试代码也运行,把模块的测试代码放在 if __name__ == '__main__': 条件语句里面
  
  
# 模块中的 __all__   
模块中 __all__ 变量,只对 from xxx import * 这种导入方式有效
模块中 __all__ 变量包含的元素,才能会被 from xxx import * 导入
__all__ 格式:是一个列表
						__all__ = ['变量名', '类名', '函数名',...]
  
  '在模块中,定义名子为 __all__ 的一个列表,这个列表中包含的内容,才会调用模块时被调用,模块中这个变量所包含的内容会在被调用此模块时被导入,其他的不会被导入'

17.7 总结

18. 包

# 1. 创建包
有两个模块功能有些联系,可以将其放在同一个文件夹里
要组成包,还需要在该文件夹中创建 __init__.py 文件
总结:
	把有联系的多个模块文件,放在同一个文件夹下,并且在这个文件夹创建一个名字为 __init__.py 文件,那么这个文件就称之为包
  包的本质就是一个文件夹,包的作用是将模块文件组织起来
  包能有效的避免模块名称冲突问题,提高程序的结构性和可维护性
  
  
  
# 2. 导入包中模块
	使用 import 包名.模块名 能够导入包中的模块
  使用 from 包名.模块名 import...能够导入模块中的符号
  
  """
  方式1:
  导入格式:import 包名.模块名
  				包名就是文件名		模块名就是文件名字
  使用格式:包名.模块名.工具		(类名,函数,变量)
  """
  
  """
  方式2:
  导入格式:	from 包名.模块名  import 所需的工具
  使用格式: 工具		(类名,函数,变量)
  """
  
  
# 3. __init__.py 文件的作用
	包被导入时,会执行 __init__.py 文件的内容
  __init__.py 的作用:控制包的导入行为,管理模块文件
  
  包被导入时,会执行 __init__.py 文件的内容
  
  
# 方式4 init使用

__init__.py 文件
'''
print("init要执行了")
from msg import recvmsg
from msg import sendmsg
'''
# 直接导入包名,就执行了包中的init直接导入了包中的模块
import msg
#包名.文件名.函数名
msg.sendmsg.send_msg()
msg.recvmsg.recv_msg()
posted @ 2025-07-10 10:25  星河霓虹  阅读(29)  评论(0)    收藏  举报