------删除之前------
加勒比海盗
骇客帝国
第一滴血
指环王
霍比特人
速度与激情
------删除之后------
加勒比海盗
骇客帝国
指环王
霍比特人
速度与激情
例:(pop)
movieName = [‘加勒比海盗’,‘骇客帝国’,‘第一滴血’,‘指环王’,‘霍比特人’,‘速度与激情’]
print(‘------删除之前------’)
for tempName in movieName:
print(tempName)
movieName.pop()
print(‘------删除之后------’)
for tempName in movieName:
print(tempName)
结果:
------删除之前------
加勒比海盗
骇客帝国
第一滴血
指环王
霍比特人
速度与激情
------删除之后------
加勒比海盗
骇客帝国
第一滴血
指环王
霍比特人
demo:(remove)
movieName = [‘加勒比海盗’,‘骇客帝国’,‘第一滴血’,‘指环王’,‘霍比特人’,‘速度与激情’]
print(‘------删除之前------’)
for tempName in movieName:
print(tempName)
movieName.remove(‘指环王’)
print(‘------删除之后------’)
for tempName in movieName:
print(tempName)
结果:
------删除之前------
加勒比海盗
骇客帝国
第一滴血
指环王
霍比特人
速度与激情
------删除之后------
加勒比海盗
骇客帝国
第一滴血
霍比特人
速度与激情
9.4.5 排序(sort, reverse)
sort方法是将list按特定顺序重新排列,默认为由小到大,参数reverse=True可改为倒序,由大到小。
reverse方法是将list逆置。
a = [1, 4, 2, 3]
a
[1, 4, 2, 3]
a.reverse()
a
[3, 2, 4, 1]
a.sort()
a
[1, 2, 3, 4]
a.sort(reverse=True)
a
[4, 3, 2, 1]
9.4.6 列表的嵌套
类似while循环的嵌套,列表也是支持嵌套的
一个列表中的元素又是一个列表,那么这就是列表的嵌套
schoolNames = [[‘北京大学’,‘清华大学’],
[‘南开大学’,‘天津大学’,‘天津师范大学’],
[‘山东大学’,‘中国海洋大学’]]
案例:一个学校,有3个办公室,现在有8位老师等待工位的分配,请编写程序,完成随机的分配
#encoding=utf-8
import random
定义一个列表用来保存3个办公室
offices = [[],[],[]]
定义一个列表用来存储8位老师的名字
names = [‘A’,‘B’,‘C’,‘D’,‘E’,‘F’,‘G’,‘H’]
i = 0
for name in names:
index = random.randint(0,2)
offices[index].append(name)
i = 1
for tempNames in offices:
print(‘办公室%d的人数为:%d’%(i,len(tempNames)))
i+=1
for name in tempNames:
print(“%s”%name,end=‘’)
print(“\n”)
print(“-”*20)
运行结果如下:
办公室1的人数为:3
BFG
办公室2的人数为:5
ACDEH
办公室3的人数为:0
Python的元组与列表类似,不同之处在于元组的元素不能修改。元组使用小括号(),列表使用方括号[],例:
aTuple = (‘et’,77,99.9)
print(aTuple)
运行结果:(‘et’, 77, 99.9)
9.5.1 访问元组
aTuple = (‘et’,77,99.9)
print(aTuple[0])
print(aTuple[1])
运行结果:
et
77
9.5.2 修改元组
aTuple = (‘et’,77,99.9)
aTuple[0]=111
运行结果:
Traceback (most recent call last):
File “C:/Users/WH/Desktop/Python基础/第一个Python程序.py”, line 2, in
aTuple[0]=111
TypeError: ‘tuple’ object does not support item assignment
从上面的运行结果可以得出: python中不允许修改元组的数据,包括不能删除其中的元素。
9.5.3 元组的内置函数count, index
index和count与字符串和列表中的用法相同
a = (‘a’, ‘b’, ‘c’, ‘a’, ‘b’)
a.index(‘a’, 1, 3) # 注意是左闭右开区间
Traceback (most recent call last):
File “”, line 1, in
ValueError: tuple.index(x): x not in tuple
a.index(‘a’, 1, 4)
3
a.count(‘b’)
2
a.count(‘d’)
0
字典是一种可变容器模型,且可存储任意类型对象。
字典的每个键值 key=>value 对用冒号 : 分割,每个对之间用逗号(,)分割,整个字典包括在花括号 {} 中 ,格式如下所示:
d = {key1 : value1, key2 : value2, key3 : value3 }
例:
info = {‘name’:‘AI浩’, ‘id’:100, ‘sex’:‘f’, ‘address’:‘中国北京’}
9.6.1 根据键访问值
info = {‘name’:‘AI浩’, ‘id’:100, ‘sex’:‘f’, ‘address’:‘中国北京’}
print(info[‘name’])
print(info[‘address’])
结果:
AI浩
中国北京
若访问不存在的键,则会报错:
info = {‘name’: ‘AI浩’, ‘id’: 100, ‘sex’: ‘f’, ‘address’: ‘中国北京’}
print(info[‘age’])
运行结果:
Traceback (most recent call last):
File “C:/Users/WH/Desktop/Python基础/第一个Python程序.py”, line 2, in
print(info[‘age’])
KeyError: ‘age’
在我们不确定字典中是否存在某个键而又想获取其值时,可以使用get方法,还可以设置默认值:
age = info.get(‘age’)
age #'age’键不存在,所以age为None
type(age)
<type ‘NoneType’>
age = info.get(‘age’, 18) # 若info中不存在’age’这个键,就返回默认值18
age
18
9.7.1 修改元素
字典的每个元素中的数据是可以修改的,只要通过key找到,即可修改,例:
info = {‘name’: ‘AI浩’, ‘id’: 100, ‘sex’: ‘f’, ‘address’: ‘中国北京’}
info[‘id’] = 150
print(‘修改之后的id为:%d’ % info[‘id’])
结果:修改之后的id为:150
9.7.2 添加元素
使用 变量名[‘键’] = 数据 时,这个“键”在字典中,不存在,那么就会新增这个元素,例:
info = {‘name’: ‘AI浩’, ‘sex’: ‘f’, ‘address’: ‘中国北京’}
newId = input(‘请输入新的ID’)
info[‘id’] = int(newId)
print(‘添加之后的id为:%d’ % info[‘id’])
结果:
请输入新的ID123
添加之后的id为:123
9.7.3 删除元素
对字典进行删除操作,有一下几种:
-
del
-
clear()
例1:del删除指定的元素
info = {‘name’: ‘AI浩’, ‘sex’: ‘f’, ‘address’: ‘中国北京’}
print(‘删除前,%s’ % info[‘name’])
del info[‘name’]
print(‘删除后,%s’ % info[‘name’])
结果:
删除前,AI浩
Traceback (most recent call last):
File “C:/Users/WH/Desktop/Python基础/第一个Python程序.py”, line 4, in
print(‘删除后,%s’ % info[‘name’])
KeyError: ‘name’
例2:del删除整个字典
info = {‘name’: ‘AI浩’, ‘sex’: ‘f’, ‘address’: ‘中国北京’}
print(‘删除前,%s’ % info)
del info
print(‘删除后,%s’ % info)
运行结果:
Traceback (most recent call last):
File “C:/Users/WH/Desktop/Python基础/第一个Python程序.py”, line 4, in
print(‘删除后,%s’ % info)
NameError: name ‘info’ is not defined
删除前,{‘name’: ‘AI浩’, ‘sex’: ‘f’, ‘address’: ‘中国北京’}
例3:clear清空整个字典
info = {‘name’: ‘AI浩’, ‘sex’: ‘f’, ‘address’: ‘中国北京’}
print(‘清空前,%s’ % info)
info.clear()
print(‘清空后,%s’ % info)
运行结果:
清空前,{‘name’: ‘AI浩’, ‘sex’: ‘f’, ‘address’: ‘中国北京’}
清空后,{}
9.7.4 字典的长度
使用len()方法,求字典的长度,例:
info = {‘name’: ‘AI浩’, ‘sex’: ‘f’, ‘address’: ‘中国北京’}
print(‘字典的长度:%d’ % len(info))
运行结果:字典的长度:3
9.7.5 找出字典中的所有key
keys返回一个包含字典所有KEY的视图对象,例:
info = {‘name’: ‘AI浩’, ‘sex’: ‘f’, ‘address’: ‘中国北京’}
dicKeys=info.keys()
print(dicKeys)
print(dicKeys[0])
运行结果:
Traceback (most recent call last):
File “C:/Users/WH/Desktop/Python基础/第一个Python程序.py”, line 4, in
print(dicKeys[0])
TypeError: ‘dict_keys’ object is not subscriptable
dict_keys([‘name’, ‘sex’, ‘address’])
注:返回的是个视图对象,不是list,很多资料说list是不对的,但是可以转为list,例:
info = {‘name’: ‘AI浩’, ‘sex’: ‘f’, ‘address’: ‘中国北京’}
dicKeys=list(info.keys())
print(dicKeys)
print(dicKeys[0])
运行结果:
[‘name’, ‘sex’, ‘address’]
name
9.7.6 找出字典所有的value
属性values返回一个包含字典所有value的视图列表,用法同上,例:
info = {‘name’: ‘AI浩’, ‘sex’: ‘f’, ‘address’: ‘中国北京’}
dicvalues=list(info.values())
print(dicvalues)
print(dicvalues[0])
运行结果:
[‘AI浩’, ‘f’, ‘中国北京’]
AI浩
9.7.7 找出字典的(键,值)
属性items,返回一个包含所有(键,值)元祖的列表
info = {‘name’: ‘AI浩’, ‘sex’: ‘f’, ‘address’: ‘中国北京’}
dicItems=info.items()
print(dicItems)
运行结果:
dict_items([(‘name’, ‘AI浩’), (‘sex’, ‘f’), (‘address’, ‘中国北京’)])
9.7.8 判断key是否存在
“key in dict"如果键在字典dict里返回true,否则返回false,例:
info = {‘name’: ‘AI浩’, ‘sex’: ‘f’, ‘address’: ‘中国北京’}
ss=‘name’ in info
print(ss)
运行结果:True
9.7.9 遍历字典的几种方式
1) 遍历字典的key(键)
info = {‘name’: ‘AI浩’, ‘sex’: ‘f’, ‘address’: ‘中国北京’}
for key in info.keys():
print(key)
运行结果:
name
sex
address
2) 遍历字典的value(值)
info = {‘name’: ‘AI浩’, ‘sex’: ‘f’, ‘address’: ‘中国北京’}
for key in info.values():
print(key)
运行结果:
AI浩
f
中国北京
3) 遍历字典的项(元素)
info = {‘name’: ‘AI浩’, ‘sex’: ‘f’, ‘address’: ‘中国北京’}
for item in info.items():
print(item)
运行结果:
(‘name’, ‘AI浩’)
(‘sex’, ‘f’)
(‘address’, ‘中国北京’)
4) 遍历字典的key-value(键值对)
info = {‘name’: ‘AI浩’, ‘sex’: ‘f’, ‘address’: ‘中国北京’}
for key, value in info.items():
print(“key=%s,value=%s” % (key, value))
运行结果:
key=name,value=AI浩
key=sex,value=f
key=address,value=中国北京
================================================================
函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。
函数能提高应用的模块性,和代码的重复利用率。你已经知道Python提供了许多内建函数,比如print()。但你也可以自己创建函数,这被叫做用户自定义函数。
语法:
def 函数名(参数列表):
函数体
例:
定义一个函数,能够完成打印信息的功能
def printInfo():
print (‘------------------------------------’)
print (’ 人生苦短,我用Python’)
print (‘------------------------------------’)
定义函数的规则:
-
函数代码块以 def 关键词开头,后接函数标识符名称和圆括号 ()。
-
任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。
-
函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
-
函数内容以冒号 : 起始,并且缩进。
-
return [表达式] 结束函数,选择性地返回一个值给调用方,不带表达式的 return 相当于返回 None。
定义了函数之后,就相当于有了一个具有某些功能的代码,想要让这些代码能够执行,需要调用它
调用函数很简单的,通过 函数名() 即可完成调用,例:
定义完函数后,函数是不会自动执行的,需要调用它才可以
printInfo()
在函数定义下面使用'''
进行注释,使用help函数可以查看函数的注释。
def test(a1, a2):
‘’’
用来完成对2个数求和"
:param a1:第一个参数
:param a2:第二个参数
:return:无
‘’’
print(“%d” % (a1 + a2))
print(help(test))
运行结果:
test(a1, a2)
用来完成对2个数求和"
:param a1:第一个参数
:param a2:第二个参数
:return:无
None
10.4.1 参数的定义
参数分形参、实参
形参:函数定义时括号内的参数
实参:函数调用时括号内的参数
形参相当于变量,实参相当于变量的值。
def add2num(a, b):
c = a + b
print©
add2num(11, 22) # 调用带有参数的函数时,需要在小括号中,传递数据
a,b为形参;11,12为实参
形参:
只在被调用时,才分配内存单元。调用结束,立刻释放所分配的内存。
只在函数内部有效。
实参:
可以是:常量、变量、表达式、函数。
进行函数调用时,实参必须是确定的值。
10.4.2 参数的分类
Python的函数除了正常定义的必选参数外,还可以使用默认参数、可变参数和关键字参数,使得函数定义出来的接口,不但能处理复杂的参数,还可以简化调用者的代码。
1)位置参数
位置形参:函数定义时,从左往右写的参数,比如上面的 a, b
位置实参:函数调用时,从左往右写的参数, 比如上面的 11,12
位置形参定义多少个,调用时位置实参必须写上多少个,多一个少一个都不行。
2)关键参数:
正常情况下,给函数传参数,要按顺序。如果不想按顺序,就用关键参数。
指定参数名的参数,就叫做关键参数。
函数调用时:func(a=1, b=2), 这种指定了参数名的参数,就是关键参数。
调用函数时,关键参数可以和位置参数一起用,但是关键参数必须在位置参数的后面。不然会报错。
例:
def add2num(a, b):
c = a + b
print©
#正确的调用方式
add2num(11, b=22) # 调用带有参数的函数时,需要在小括号中,传递数据
add2num(a=11, b=22)
add2num(b=11, a=22)
错误的调用方式。
#add2num(22, a=22)
#add2num(b=11,22)
3)默认参数
函数定义时,默认参数必须在位置形参的后面。
函数调用时,指定参数名的参数,叫关键参数。
而在函数定义时,给参数名指定值的时候,这个参数叫做默认参数。
关键参数,和默认参数两个参数写法一样,区别在于:
关键参数是在函数调用时,指定实参的参数名,也可以说指定值的参数名。
默认参数是在函数定义时,指定参数名的值。
定义时,有默认参数的话,调用时,这个实参可以不写。如果实参不写的话,这个形参的参数值是他的默认值。
例:
def add2num(a, b=100):
c = a + b
print©
add2num(11, b=22) # 调用带有参数的函数时,需要在小括号中,传递数据
add2num(11)
运行结果:
33
111
4 ) 动态参数:*args **kwargs
*args
针对函数定义时的*:
def func(a, b, *args):
pass
*args会接收函数调用时,传入的多余的位置实参。
*args 是一个元组
例子:
func(1, 2, 3, 4, 5, 6) 函数调用,因为函数定义时,*args前面有形参a, 形参b, *args就接收调用时多余的位置实参
a为1, b为2, *args 为: (3, 4, 5, 6),是一个元组。
针对函数调用时的 *:
func(1, 2, *[1, 2, 3, 4]) == func(1, 2, 1, 2, 3, 4)
函数调用时有*, 就应该立马将*后面的列表,元组,字符串之类的迭代器,打散成位置参数来看。
注意,如果 *后面是一个字典的话,那么打散成的位置参数就是字典的key
*可以看做是for循环。
形参中 *args 只能接收多余的位置实参,成为一个元组。不能接收关键实参。
例:
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
nums = [1, 2, 3]
调用方式1
print(calc(nums[0], nums[1], nums[2]))
#调用方式2
print(calc(*nums))
运行结果:
14
14
**kwargs:
针对函数定义时,站在形参的角度看 **:
接收多余的关键实参,成为一个字典dict。
字典的key是关键实参的变量名,字典的value是关键实参的值。
将字典交给**后面的变量名,这里是kwargs
站在实参的角度看 ** :
d = {‘x’:1, ‘y’:2, ‘z’:333}
func(**d) # 等价于func(x=1,y=2,z=333)
函数调用时,后面可以接一个字典,然后会把字典打散成关键参数的形式,也就是key=value的形式。
例:
def person(name, age, **kw):
print(‘name:’, name, ‘age:’, age, ‘other:’, kw)
#调用方式1
print(person(‘Michael’, 30))
#调用方式2
print(person(‘Bob’, 35, city=‘Beijing’))
#调用方式3
print(person(‘Adam’, 45, gender=‘M’, job=‘Engineer’))
运行结果:
name: Michael age: 30 other: {}
None
name: Bob age: 35 other: {‘city’: ‘Beijing’}
None
name: Adam age: 45 other: {‘gender’: ‘M’, ‘job’: ‘Engineer’}
None
5)混合参数时,参数顺序
函数定义时:
从左往右:位置形参 > *args > 默认参数 > **kwargs
位置形参 > 默认参数 > *args > **kwargs 也可以。
因为函数调用时给的实参满足了位置形参、默认参数之后,会把多余的位置实参给args。这样并不会报错。
但是 **kwargs 必须在 *args后面
默认形参必须在位置形参后面
函数调用时:
从左到右:位置实参 > *args > 关键参数 > **kwargs
因为 * args 在函数调用时,会被打散成位置实参,而关键参数必须在位置实参的后面,否则会报错。SyntaxError: positional argument follows keyword argument
*args 必须在 **kwargs后面, 否则会报语法错误:SyntaxError: iterable argument unpacking follows keyword argument unpacking
总结
Python的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。
默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误!
要注意定义可变参数和关键字参数的语法:
*args
是可变参数,args接收的是一个tuple;
**kw
是关键字参数,kw接收的是一个dict。
以及调用函数时如何传入可变参数和关键字参数的语法:
可变参数既可以直接传入:func(1, 2, 3)
,又可以先组装list或tuple,再通过*args
传入:func(*(1, 2, 3))
;
关键字参数既可以直接传入:func(a=1, b=2)
,又可以先组装dict,再通过**kw
传入:func(**{'a': 1, 'b': 2})
。
使用*args
和**kw
是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。
命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。
定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符*
,否则定义的将是位置参数。
10.4.3 参数的传递
Python参数传递采用的肯定是“传对象引用”的方式。这种方式相当于传值和传引用的一种综合。如果函数收到的是一个可变对象(比如字典或者列表)的引用,就能修改对象的原始值--相当于通过“传引用”来传递对象。如果函数收到的是一个不可变对象(比如数字、字符或者元组)的引用,就不能直接修改原始对象--相当于通过“传值’来传递对象。
例:
attrList = [0, 1, 2, 3, 4, 5]
print(“改变前————————————————————————————————”)
print(attrList)
def func(i, attrList):
attrList[i] = attrList[i] * 10
print(“改变后————————————————————————————————”)
for i in range(3):
func(i, attrList)
print(attrList)
运行结果:
改变前————————————————————————————————
[0, 1, 2, 3, 4, 5]
改变后————————————————————————————————
[0, 10, 20, 3, 4, 5]
可以看到,List发生了改变,如果传入不可变的元素呢?
a = 10
print(“改变前————————————————————————————————”)
print(a)
def func©:
print(id©)
c += 2
print(id©)
print(“func函数的c值:”, c)
print(id(a))
func(a)
print(“改变后————————————————————————————————”)
print(a)
print(id(a))
运行结果:
改变前————————————————————————————————
10
140724141828160
140724141828160
140724141828224
func函数的c值: 12
改变后————————————————————————————————
10
140724141828160
将a变量作为参数传递给了func函数,传递了a的一个引用,把a的地址传递过去了,所以在函数内获取的变量c的地址跟变量a的地址是一样的,但是在函数内,对c进行赋值运算,c的值从10变成了12,实际上10和12所占的内存空间都还是存在的,赋值运算后,c指向12所在的内存。而a仍然指向10所在的内存,所以后面打印a,其值还是10.
想要在函数中把结果返回给调用者,需要在函数中使用return,例:
def add2num(a, b):
c = a + b
return c
print(add2num(1, 2))
可以返回多个返回值,例:
def divid(a, b):
shang = a // b
yushu = a % b
return shang, yushu
sh, yu = divid(5, 2)
-
局部变量就是定义在一个函数体内部的变量
-
全局变量是定义在外面的变量
例:
a = 1
def f():
b = 2
其中a就是全局变量,而b是局部变量。局部变量只在函数体内部有效,出了函数体,外面是访问不到的,而全局变量则对下面的代码都有效。
全局变量可以直接在函数体内容部使用的,你可以直接访问,但是注意的是,如果对于不可变类型的数据,如果在函数里面进行了赋值操作,则对外面的全局变量不产生影响,因为相当于新建了一个局部变量,只是名字和全局一样,而对于可变类型,如果使用赋值语句,同样对外部不产生影响,但是使用方法的话就会对外部产生影响。
g_b = 3
g_l1 = [1, 2]
g_l2 = [1, 2, 3]
def t1():
g_b = 2
g_l1 = []
g_l2.append(7)
t1()
print(g_b, g_l1, g_l2)
运行结果:3 [1, 2] [1, 2, 3, 7]
global关键字
上面说到,如果使用的是赋值语句,在函数内部相当于新建了一个变量,并且重新给了指向,但是有时候我们想把这个变量就是外部的那个全局变量,在赋值操作的时候,就是对全局变量给了重新的指向,这个时候可以通过global关键字表示我在函数里面的这个变量是使用的全局那个。使用方法如下:
g_b = 3
def t1():
global g_b
g_b = 2
t1()
print(g_b)
运行结果:2
这个时候你会发现全局变量g_b也重新指向了,这是因为global g_b表示指定了函数中的g_b就是外面的那个。
如果一个函数在内部调用自身本身,这个函数就是递归函数。例:
def fact(n):
if n == 1:
return 1
return n * fact(n - 1)
print(fact(5))
运行结果:120
可以根据函数定义看到计算过程如下:
===> fact(5)
===> 5 * fact(4)
===> 5 * (4 * fact(3))
===> 5 * (4 * (3 * fact(2)))
===> 5 * (4 * (3 * (2 * fact(1))))
===> 5 * (4 * (3 * (2 * 1)))
===> 5 * (4 * (3 * 2))
===> 5 * (4 * 6)
===> 5 * 24
===> 120
递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。
使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。可以试试fact(1000)。
解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。
尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
上面的fact(n)函数由于return n * fact(n - 1)引入了乘法表达式,所以就不是尾递归了。要改成尾递归方式,需要多一点代码,主要是要把每一步的乘积传入到递归函数中,例:
def fact(n):
return fact_iter(n, 1)
def fact_iter(num, product):
if num == 1:
return product
return fact_iter(num - 1, num * product)
可以看到,return fact_iter(num - 1, num * product)仅返回递归函数本身,num - 1和num * product在函数调用前就会被计算,不影响函数调用。
fact(5)对应的fact_iter(5, 1)的调用如下:
===> fact_iter(5, 1)
===> fact_iter(4, 5)
===> fact_iter(3, 20)
===> fact_iter(2, 60)
===> fact_iter(1, 120)
===> 120
尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。
遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使把上面的fact(n)函数改成尾递归方式,也会导致栈溢出。
小结
使用递归函数的优点是逻辑简单清晰,缺点是过深的调用会导致栈溢出。
针对尾递归优化的语言可以通过尾递归防止栈溢出。尾递归事实上和循环是等价的,没有循环语句的编程语言只能通过尾递归实现循环。
Python标准的解释器没有针对尾递归做优化,任何递归函数都存在栈溢出的问题。
10.8.1 定义
用lambda关键词能创建小型匿名函数。这种函数得名于省略了用def声明函数的标准步骤。
lambda函数的语法只包含一个语句,如下:
lambda [arg1 [,arg2,…argn]]:expression
如下实例:
sum = lambda a1, a2: a1 * a2
调用sum函数
print("Value of total : ", sum(10, 20))
print("Value of total : ", sum(20, 20))
以上实例输出结果:
Value of total : 200
Value of total : 400
Lambda函数能接收任何数量的参数但只能返回一个表达式的值
匿名函数不能直接调用print,因为lambda需要一个表达式
10.8.2 应用场合
例1:自己定义函数作为参数传递
def fun(a, b, opt):
print(“a =”, a)
print(“b =”, b)
print(“result =”, opt(a, b))
fun(1, 2, lambda x, y: x + y)
运行结果:
a = 1
b = 2
result = 3
例2:作为内置函数的参数
想一想,下面的数据如何指定按age或name排序?
stus = [
{“name”:“zhangsan”, “age”:18},
{“name”:“lisi”, “age”:19},
{“name”:“wangwu”, “age”:17}
]
按name排序:
stus.sort(key=lambda x: x[‘name’])
print(stus)
运行结果:
[{‘name’: ‘lisi’, ‘age’: 19}, {‘name’: ‘wangwu’, ‘age’: 17}, {‘name’: ‘zhangsan’, ‘age’: 18}]
按age排序:
stus.sort(key=lambda x: x[‘age’])
print(stus)
运行结果:
[{‘name’: ‘wangwu’, ‘age’: 17}, {‘name’: ‘zhangsan’, ‘age’: 18}, {‘name’: ‘lisi’, ‘age’: 19}]
==================================================================
11.1.1 打开文件
在python,使用open函数,可以打开一个已经存在的文件,或者创建一个新文件
open(文件名,访问模式)
示例如下:
f = open(‘test.txt’, ‘w’)
说明:
| 访问模式 | 说明 |
| :-: | :-- |
| r | 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。 |
| w | 打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。 |
| a | 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
| rb | 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。 |
| wb | 以二进制格式打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。 |
| ab | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
| r+ | 打开一个文件用于读写。文件指针将会放在文件的开头。 |
| w+ | 打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。 |
| a+ | 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。 |
| rb+ | 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。 |
| wb+ | 以二进制格式打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。 |
| ab+ | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。 |
11.1.2 关闭文件
close( )
示例如下:
新建一个文件,文件名为:test.txt
f = open(‘test.txt’, ‘w’)
关闭这个文件
f.close()
11.2.1 写数据(write)
使用write()可以完成向文件写入数据,例:
f = open(‘test.txt’, ‘w’)
f.write(‘hello 大家好, 我是AI浩’)
f.close()
运行现象:
注意:
- 如果文件不存在那么创建,如果存在那么就先清空,然后写入数据
11.2.2 读数据(read)
使用read(num)可以从文件中读取数据,num表示要从文件中读取的数据的长度(单位是字节),如果没有传入num,那么就表示读取文件中所有的数据,例:
f = open(‘test.txt’, ‘r’)
content = f.read(5)
print(content)
print(“-”*30)
content = f.read()
print(content)
f.close()
运行结果:
hello
大家好, 我是AI浩
注意:
-
如果open是打开一个文件,那么可以不用谢打开的模式,即只写
open('test.txt')
-
如果使用读了多次,那么后面读取的数据是从上次读完后的位置开始的
11.2.3 读数据(readlines)
就像read没有参数时一样,readlines可以按照行的方式把整个文件中的内容进行一次性读取,并且返回的是一个列表,其中每一行的数据为一个元素
#coding=utf-8
f = open(‘test.txt’, ‘r’)
content = f.readlines()
print(type(content))
i=1
for temp in content:
print(“%d:%s”%(i, temp))
i+=1
f.close()
运行结果:
<class ‘list’>
1:hello 大家好, 我是AI浩
11.2.4 读数据(readline)
#coding=utf-8
f = open(‘test.txt’, ‘r’)
content = f.readline()
print(“1:%s”%content)
content = f.readline()
print(“2:%s”%content)
f.close()
运行结果:
1:hello 大家好, 我是AI浩
2:asfsifhiudh
11.3.1 获取当前读写的位置
在读写文件的过程中,如果想知道当前的位置,可以使用tell()来获取,例:
打开一个已经存在的文件
f = open(“test.txt”, “r”)
str = f.read(3)
print("读取的数据是 : ", str)
查找当前位置
position = f.tell()
print("当前文件位置 : ", position)
str = f.read(3)
print("读取的数据是 : ", str)
查找当前位置
position = f.tell()
print("当前文件位置 : ", position)
f.close()
运行结果:
读取的数据是 : hel
当前文件位置 : 3
读取的数据是 : lo
当前文件位置 : 6
11.3.2 定位到某个位置
如果在读写文件的过程中,需要从另外一个位置进行操作的话,可以使用seek()
seek(offset, from)有2个参数
-
offset:偏移量
-
from:方向
-
0:表示文件开头
-
1:表示当前位置
-
2:表示文件末尾
例1:把位置设置为:从文件开头,偏移5个字节
打开一个已经存在的文件
f = open(“test.txt”, “r”)
str = f.read(30)
print("读取的数据是 : ", str)
查找当前位置
position = f.tell()
print("当前文件位置 : ", position)
重新设置位置
f.seek(5, 0)
查找当前位置
position = f.tell()
print("当前文件位置 : ", position)
f.close()
例2:把位置设置为:离文件末尾,3字节处
打开一个已经存在的文件
f = open(“test.txt”, “rb”)
print("读取的数据是 : ", str)
position = f.tell()
print("当前文件位置 : ", position)
重新设置位置
f.seek(-3, 2)
读取到的数据为:文件最后3个字节数据
str = f.read()
print("读取的数据是 : ", str)
f.close()
运行结果:
读取的数据是 : <class ‘str’>
当前文件位置 : 0
读取的数据是 : b’ddf’
11.3.3 文件重命名
os模块中的rename()可以完成对文件的重命名操作
rename(需要修改的文件名, 新的文件名)
import os
os.rename(“test.txt”, “test-1.txt”)
11.3.4 删除文件
os模块中的remove()可以完成对文件的删除操作
remove(待删除的文件名)
import os
os.remove(“test.txt”)
实际开发中,有时需要用程序的方式对文件夹进行一定的操作,比如创建、删除等
就像对文件操作需要os模块一样,如果要操作文件夹,同样需要os模块
11.4.1 创建文件夹
import os
os.mkdir(“aa”)
11.4.2 获取当前目录
import os
os.getcwd()
11.4.3 改变默认目录
import os
os.chdir(“…/”)
11.4.4 获取目录列表
import os
os.listdir(“./”)
11.4.5 删除文件夹
import os
os.rmdir(“张三”)
11.4.6 检测文件夹是否存在
import os
if not os.path.exists(path):
os.makedirs(path)
11.4.7 创建多级文件夹
import os
os.makedirs(opDir)
====================================================================
面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。
而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。
面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。
12.1.1 类
人以类聚 物以群分。
具有相似内部状态和运动规律的实体的集合(或统称为抽象)。
具有相同属性和行为事物的统称
类是抽象的,在使用的时候通常会找到这个类的一个具体的存在,使用这个具体的存在。一个类可以找到多个对象
12.2.2 对象
某一个具体事物的存在 ,在现实世界中可以是看得见摸得着的。
可以是直接使用的
12.2.3 类和对象的关系
类是创建实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响;
12.2.4 定义类
定义一个类,格式如下:
class 类名:
方法列表
例:定义一个Car类
定义类
class Car:
方法
def getCarInfo(self):
print(‘车轮子个数:%d, 颜色%s’%(self.wheelNum, self.color))
def move(self):
print(“车正在移动…”)
说明:
-
定义类时有2种:新式类和经典类,上面的Car为经典类,如果是Car(object)则为新式类
-
类名 的命名规则按照"大驼峰"
12.2.5 创建对象
通过上一节,定义了一个Car类;就好比有车一个张图纸,那么接下来就应该把图纸交给生成工人们去生成了
python中,可以根据已经定义的类去创建出一个个对象
创建对象的格式为:
对象名 = 类名()
创建对象demo:
定义类
class Car:
移动
def move(self):
print(‘车在奔跑…’)
鸣笛
def toot(self):
print(“车在鸣笛…嘟嘟…”)
创建一个对象,并用变量BMW来保存它的引用
BMW = Car()
BMW.color = ‘黑色’
BMW.wheelNum = 4 #轮子数量
BMW.move()
BMW.toot()
print(BMW.color)
print(BMW.wheelNum)
运行结果:
车在奔跑…
车在鸣笛…嘟嘟…
黑色
4
12.2.6 __init__()
方法
1)使用方式
def 类名:
#初始化函数,用来完成一些默认的设定
def init():
pass
2)__init__()
方法的调用
定义汽车类
class Car:
def init(self):
self.wheelNum = 4
self.color = ‘蓝色’
def move(self):
print(‘车在跑,目标:夏威夷’)
创建对象
BMW = Car()
print(‘车的颜色为:%s’%BMW.color)
print(‘车轮胎数量为:%d’%BMW.wheelNum)
运行结果:
车的颜色为:蓝色
车轮胎数量为:4
12.2.7 魔法方法
1)打印id()
如果把BMW使用print进行输出的话,会看到如下的信息,例:
定义汽车类
class Car:
def init(self):
self.wheelNum = 4
self.color = ‘蓝色’
def move(self):
print(‘车在跑,目标:夏威夷’)
创建对象
BMW = Car()
print(BMW)
运行结果:
<main.Car object at 0x0000014F596F8400>
即看到的是创建出来的BMW对象在内存中的地址
2) 定义__str__()
方法
class Car:
def init(self, newWheelNum, newColor):
self.wheelNum = newWheelNum
self.color = newColor
def str(self):
msg = “嘿。。。我的颜色是” + self.color + “我有” + str(self.wheelNum) + “个轮胎…”
return msg
def move(self):
print(‘车在跑,目标:夏威夷’)
BMW = Car(4, “白色”)
print(BMW)
运行结果:
嘿。。。我的颜色是白色我有4个轮胎…
3)定义__del__()
方法
创建对象后,python解释器默认调用__init__()
方法;
当删除一个对象时,python解释器也会默认调用一个方法,这个方法为__del__()
方法
import time
class Animal(object):
初始化方法
创建完对象后会自动被调用
def init(self, name):
print(‘__init__方法被调用’)
self.__name = name
析构方法
当对象被删除时,会自动被调用
def del(self):
print(“__del__方法被调用”)
print(“%s对象马上被干掉了…” % self.__name)
创建对象
dog = Animal(“哈皮狗”)
删除对象
del dog
cat = Animal(“波斯猫”)
cat2 = cat
cat3 = cat
print(“—马上 删除cat对象”)
del cat
print(“—马上 删除cat2对象”)
del cat2
print(“—马上 删除cat3对象”)
del cat3
print(“程序2秒钟后结束”)
time.sleep(2)
运行结果:
__init__方法被调用
__del__方法被调用
哈皮狗对象马上被干掉了…
__init__方法被调用
—马上 删除cat对象
—马上 删除cat2对象
—马上 删除cat3对象
__del__方法被调用
波斯猫对象马上被干掉了…
程序2秒钟后结束
-
当有1个变量保存了对象的引用时,此对象的引用计数就会加1
-
当使用del删除变量指向的对象时,如果对象的引用计数不会1,比如3,那么此时只会让这个引用计数减1,即变为2,当再次调用del时,变为1,如果再调用1次del,此时会真的把对象进行删除
总结
-
在python中方法名如果是
__xxxx__()
的,那么就有特殊的功能,因此叫做“魔法”方法 -
当使用print输出对象的时候,只要自己定义了
__str__(self)
方法,那么就会打印从在这个方法中return的数据
常用的魔法方法列表
| 魔法方法 | 含义 |
| — | — |
| | 基本的魔法方法 |
| _new_(cls[, …]) | 1. new 是在一个对象实例化的时候所调用的第一个方法 2. 它的第一个参数是这个类,其他的参数是用来直接传递给 init 方法 3. new 决定是否要使用该 init 方法,因为 new 可以调用其他类的构造方法或者直接返回别的实例对象来作为本类的实例,如果 new 没有返回实例对象,则 init 不会被调用 4. new 主要是用于继承一个不可变的类型比如一个 tuple 或者 string |
| _init_(self[, …]) | 构造器,当一个实例被创建的时候调用的初始化方法 |
| _del_(self) | 析构器,当一个实例被销毁的时候调用的方法 |
| _call_(self[, args…]) | 允许一个类的实例像函数一样被调用:x(a, b) 调用 x.call(a, b) |
| _len_(self) | 定义当被 len() 调用时的行为 |
| _repr_(self) | 定义当被 repr() 调用时的行为 |
| _str_(self) | 定义当被 str() 调用时的行为 |
| _bytes_(self) | 定义当被 bytes() 调用时的行为 |
| _hash_(self) | 定义当被 hash() 调用时的行为 |
| _bool_(self) | 定义当被 bool() 调用时的行为,应该返回 True 或 False |
| _format_(self, format_spec) | 定义当被 format() 调用时的行为 |
| | 有关属性 |
| _getattr_(self, name) | 定义当用户试图获取一个不存在的属性时的行为 |
| _getattribute_(self, name) | 定义当该类的属性被访问时的行为 |
| _setattr_(self, name, value) | 定义当一个属性被设置时的行为 |
| _delattr_(self, name) | 定义当一个属性被删除时的行为 |
| _dir_(self) | 定义当 dir() 被调用时的行为 |
| _get_(self, instance, owner) | 定义当描述符的值被取得时的行为 |
| _set_(self, instance, value) | 定义当描述符的值被改变时的行为 |
| _delete_(self, instance) | 定义当描述符的值被删除时的行为 |
| | 比较操作符 |
| _lt_(self, other) | 定义小于号的行为:x < y 调用 x.lt(y) |
| _le_(self, other) | 定义小于等于号的行为:x <= y 调用 x.le(y) |
| _eq_(self, other) | 定义等于号的行为:x == y 调用 x.eq(y) |
| _ne_(self, other) | 定义不等号的行为:x != y 调用 x.ne(y) |
| _gt_(self, other) | 定义大于号的行为:x > y 调用 x.gt(y) |
| _ge_(self, other) | 定义大于等于号的行为:x >= y 调用 x.ge(y) |
| | 算数运算符 |
| _add_(self, other) | 定义加法的行为:+ |
| _sub_(self, other) | 定义减法的行为:- |
| _mul_(self, other) | 定义乘法的行为:* |
| _truediv_(self, other) | 定义真除法的行为:/ |
| _floordiv_(self, other) | 定义整数除法的行为:// |
| _mod_(self, other) | 定义取模算法的行为:% |
| _divmod_(self, other) | 定义当被 divmod() 调用时的行为 |
| _pow_(self, other[, modulo]) | 定义当被 power() 调用或 ** 运算时的行为 |
| _lshift_(self, other) | 定义按位左移位的行为:<< |
| _rshift_(self, other) | 定义按位右移位的行为:>> |
| _and_(self, other) | 定义按位与操作的行为:& |
| _xor_(self, other) | 定义按位异或操作的行为:^ |
| _or_(self, other) | 定义按位或操作的行为:| |
| | 反运算 |
| _radd_(self, other) | (与上方相同,当左操作数不支持相应的操作时被调用) |
| _rsub_(self, other) | (与上方相同,当左操作数不支持相应的操作时被调用) |
| _rmul_(self, other) | (与上方相同,当左操作数不支持相应的操作时被调用) |
| _rtruediv_(self, other) | (与上方相同,当左操作数不支持相应的操作时被调用) |
| _rfloordiv_(self, other) | (与上方相同,当左操作数不支持相应的操作时被调用) |
| _rmod_(self, other) | (与上方相同,当左操作数不支持相应的操作时被调用) |
| _rdivmod_(self, other) | (与上方相同,当左操作数不支持相应的操作时被调用) |
| _rpow_(self, other) | (与上方相同,当左操作数不支持相应的操作时被调用) |
| _rlshift_(self, other) | (与上方相同,当左操作数不支持相应的操作时被调用) |
| _rrshift_(self, other) | (与上方相同,当左操作数不支持相应的操作时被调用) |
| _rxor_(self, other) | (与上方相同,当左操作数不支持相应的操作时被调用) |
| _ror_(self, other) | (与上方相同,当左操作数不支持相应的操作时被调用) |
| | 增量赋值运算 |
| _iadd_(self, other) | 定义赋值加法的行为:+= |
| _isub_(self, other) | 定义赋值减法的行为:-= |
| _imul_(self, other) | 定义赋值乘法的行为:*= |
| _itruediv_(self, other) | 定义赋值真除法的行为:/= |
| _ifloordiv_(self, other) | 定义赋值整数除法的行为://= |
| _imod_(self, other) | 定义赋值取模算法的行为:%= |
| _ipow_(self, other[, modulo]) | 定义赋值幂运算的行为:**= |
| _ilshift_(self, other) | 定义赋值按位左移位的行为:<<= |
| _irshift_(self, other) | 定义赋值按位右移位的行为:>>= |
| _iand_(self, other) | 定义赋值按位与操作的行为:&= |
| _ixor_(self, other) | 定义赋值按位异或操作的行为:^= |
| _ior_(self, other) | 定义赋值按位或操作的行为:|= |
| | 一元操作符 |
| _neg_(self) | 定义正号的行为:+x |
| _pos_(self) | 定义负号的行为:-x |
| _abs_(self) | 定义当被 abs() 调用时的行为 |
| _invert_(self) | 定义按位求反的行为:~x |
| | 类型转换 |
| _complex_(self) | 定义当被 complex() 调用时的行为(需要返回恰当的值) |
| _int_(self) | 定义当被 int() 调用时的行为(需要返回恰当的值) |
| _float_(self) | 定义当被 float() 调用时的行为(需要返回恰当的值) |
| _round_(self[, n]) | 定义当被 round() 调用时的行为(需要返回恰当的值) |
| _index_(self) | 1. 当对象是被应用在切片表达式中时,实现整形强制转换 2. 如果你定义了一个可能在切片时用到的定制的数值型,你应该定义 index 3. 如果 index 被定义,则 int 也需要被定义,且返回相同的值 |
| | 上下文管理(with 语句) |
| _enter_(self) | 1. 定义当使用 with 语句时的初始化行为 2. enter 的返回值被 with 语句的目标或者 as 后的名字绑定 |
| _exit_(self, exc_type, exc_value, traceback) | 1. 定义当一个代码块被执行或者终止后上下文管理器应该做什么 2. 一般被用来处理异常,清除工作或者做一些代码块执行完毕之后的日常工作 |
| | 容器类型 |
| _len_(self) | 定义当被 len() 调用时的行为(返回容器中元素的个数) |
| _getitem_(self, key) | 定义获取容器中指定元素的行为,相当于 self[key] |
| _setitem_(self, key, value) | 定义设置容器中指定元素的行为,相当于 self[key] = value |
| _delitem_(self, key) | 定义删除容器中指定元素的行为,相当于 del self[key] |
| _iter_(self) | 定义当迭代容器中的元素的行为 |
| _reversed_(self) | 定义当被 reversed() 调用时的行为 |
| _contains_(self, item) | 定义当使用成员测试运算符(in 或 not in)时的行为 |
12.2.8 self
self可以理解为自己,类似C#中的this,例:
定义一个类
class Animal:
方法
def init(self, name):
self.name = name
def printName(self):
print(‘名字为:%s’ % self.name)
定义一个函数
def myPrint(animal):
animal.printName()
dog1 = Animal(‘淘淘’)
myPrint(dog1)
dog2 = Animal(‘贝贝’)
myPrint(dog2)
运行结果:
名字为:淘淘
名字为:贝贝
总结
所谓的self,可以理解为自己
可以把self当做C++中类里面的this指针一样理解,就是对象自身的意思
某个对象调用其方法时,python解释器会把这个对象作为第一个参数传递给self,所以开发者只需要传递后面的参数即可
属性分为类属性和实例属性。
类属性就是属于类所有,可以直接用类名.属性名直接调用,类的属性在内存中只有一份。实例属性就是在__init__()方法中初始化的属性;
实例属性属于类的对象所有,可以用对象名.属性名的形式进行调用,但是不能用类名.属性名进行调用 。因为实例属性只有在实例创建时,才会初始化创建。
12.2.1 类属性
类属性就是类对象
所拥有的属性,它被所有类对象
的实例对象
所共有,在内存中只存在一个副本,这个和C#中类的静态成员变量有点类似。对于公有的类属性,在类外可以通过类对象
和实例对象
访问,例:
class People(object):
name = ‘Tom’ #公有的类属性
__age = 12 #私有的类属性
p = People()
print(p.name) #正确
print(People.name) #正确
print(p.__age) #错误,不能在类外通过实例对象访问私有的类属性
print(People.__age) #错误,不能在类外通过类对象访问私有的类属性
类属性的访问:
import time
class Test(object):
name = ‘scolia’
a = Test()
print(Test.name) # 通过类进行访问
print(a.name) # 通过实例进行访问
运行结果:
scolia
scolia
可以访问的,但是,试图修改这个属性的话:
class Test(object):
name = ‘scolia’
a = Test()
Test.name = ‘scolia good’ # 通过类进行修改
print(Test.name)
print(a.name)
运行结果:
scolia good
scolia good
我们发现两者都修改成功了。再尝试通过实例来修改属性的话:
class Test(object):
name = ‘scolia’
a = Test()
a.name = ‘scolia good’ # 通过实例进行修改
print(Test.name)
print(a.name)
运行结果:
scolia
scolia good
我们发现类的属性没有修改,而实例的属性则修改成功了。这究竟是为什么?
其实这里的情况非常类似于局部作用域和全局作用域。
我在函数内访问变量时,会先在函数内部查询有没有这个变量,如果没有,就到外层中找。这里的情况是我在实例中访问一个属性,但是我实例中没有,我就试图去创建我的类中寻找有没有这个属性。找到了,就有,没找到,就抛出异常。而当我试图用实例去修改一个在类中不可变的属性的时候,我实际上并没有修改,而是在我的实例中创建了这个属性。而当我再次访问这个属性的时候,我实例中有,就不用去类中寻找了。如果用一张图来表示的话:
12.2.2 实例属性(对象属性)
实例属性是在_init_(self)方法定义的属性,属于对象的本身,只能通过对象.属性来访问,不能用过类.属性来访问。例:
class People(object):
address = ‘山东’ #类属性
def init(self):
self.name = ‘xiaowang’ #实例属性
self.age = 20 #实例属性
p = People()
p.age =12 #实例属性
print(p.address) #正确
print(p.name) #正确
print(p.age) #正确
print(People.address) #正确
print(People.name) #错误
print(People.age) #错误
12.2.3 私有属性
如果有一个对象,当需要对其进行修改属性时,有2种方法
-
对象名.属性名 = 数据 ---->直接修改
-
对象名.方法名() ---->间接修改
为了更好的保存属性安全,即不能随意修改,一般的处理方式为
-
将属性定义为私有属性
-
添加一个可以调用的方法,供调用
class People(object):
def init(self, name):
self.__name = name
def getName(self):
return self.__name
def setName(self, newName):
if len(newName) >= 5:
self.__name = newName
else:
print(“error:名字长度需要大于或者等于5”)
xiaoming = People(“AI浩”)
print(xiaoming.__name)
Traceback (most recent call last):
File “C:/Users/WH/Desktop/Python基础/第一个Python程序.py”, line 16, in
print(xiaoming.__name)
AttributeError: ‘People’ object has no attribute ‘__name’
直接调用私有属性会报错,我需要定义个方法,调用方法
class People(object):
def init(self, name):
self.__name = name
def getName(self):
return self.__name
def setName(self, newName):
if len(newName) >= 5:
self.__name = newName
else:
print(“error:名字长度需要大于或者等于5”)
xiaoming = People(“张三”)
xiaoming.setName(“wanger”)
print(xiaoming.getName())
xiaoming.setName(“lisi”)
print(xiaoming.getName())
运行结果:
wanger
error:名字长度需要大于或者等于5
wanger
总结
-
如果需要在类外修改
类属性
,必须通过类对象
去引用然后进行修改。如果通过实例对象去引用,会产生一个同名的实例属性
,这种方式修改的是实例属性
,不会影响到类属性
,并且之后如果通过实例对象去引用该名称的属性,实例属性会强制屏蔽掉类属性,即引用的是实例属性
,除非删除了该实例属性
。 -
Python中没有像C++中public和private这些关键字来区别公有属性和私有属性
-
它是以属性命名方式来区分,如果在属性名前面加了2个下划线’__’,则表明该属性是私有属性,否则为公有属性(方法也是一样,方法名前面加了2个下划线的话表示该方法是私有的,否则为公有的)。
在程序中,继承描述的是事物之间的所属关系,例如猫和狗都属于动物,程序中便可以描述为猫和狗继承自动物;同理,波斯猫和巴厘猫都继承自猫,而沙皮狗和斑点狗都继承足够,如下如所示:
定义一个父类,如下:
class Cat(object):
def init(self, name, color=“白色”):
self.name = name
self.color = color
def run(self):
print(“%s–在跑” % self.name)
定义一个子类,继承Cat类如下:
class Bosi(Cat):
def setNewName(self, newName):
self.name = newName
def eat(self):
print(“%s–在吃” % self.name)
bs = Bosi(“印度猫”)
print(‘bs的名字为:%s’ % bs.name)
print(‘bs的颜色为:%s’ % bs.color)
bs.eat()
bs.setNewName(‘波斯’)
bs.run()
运行结果:
bs的名字为:印度猫
bs的颜色为:白色
印度猫–在吃
波斯–在跑
结论:
-
虽然子类没有定义
__init__
方法,但是父类有,所以在子类继承父类的时候这个方法就被继承了,所以只要创建Bosi的对象,就默认执行了那个继承过来的__init__
方法 -
子类在继承的时候,在定义类时,小括号()中为父类的名字
-
父类的属性、方法,会被继承给子类
12.3.1 私有属性和方法的继承
class Animal(object):
def init(self, name=‘动物’, color=‘白色’):
self.__name = name
self.color = color
def __test(self):
print(self.__name)
print(self.color)
def test(self):
print(self.__name)
print(self.color)
class Dog(Animal):
def dogTest1(self):
print(self.__name) #不能访问到父类的私有属性
print(self.color)
def dogTest2(self):
self.__test() #不能访问父类中的私有方法
self.test()
A = Animal()
print(A.__name) #程序出现异常,不能访问私有属性
print(A.color)
A.__test() #程序出现异常,不能访问私有方法
A.test()
print(“------分割线-----”)
D = Dog(name=“小花狗”, color=“黄色”)
D.dogTest1()
D.dogTest2()
运行结果:
白色
动物
白色
------分割线-----
黄色
小花狗
黄色
结论:
私有的属性,不能通过对象直接访问,但是可以通过方法访问
私有的方法,不能通过对象直接访问
私有的属性、方法,不会被子类继承,也不能被访问
一般情况下,私有的属性、方法都是不对外公布的,往往用来做内部的事情,起到安全的作用
12.3.2 多继承
Python中多继承的格式如下:
定义一个父类
class A:
def printA(self):
print(‘----A----’)
定义一个父类
class B:
def printB(self):
print(‘----B----’)
定义一个子类,继承自A、B
class C(A,B):
def printC(self):
print(‘----C----’)
obj_C = C()
obj_C.printA()
obj_C.printB()
运行结果:
----A----
----B----
结论:
python中是可以多继承的
父类中的方法、属性,子类会继承
如果父类A和父类B中,有一个同名的方法,那么通过子类去调用的时候,调用哪个?
coding=utf-8
class base(object):
def test(self):
print(‘----base test----’)
class A(base):
def test(self):
print(‘----A test----’)
定义一个父类
class B(base):
def test(self):
print(‘----B test----’)
定义一个子类,继承自A、B
class C(A, B):
pass
obj_C = C()
obj_C.test()
print(“搜索顺序:”,C.mro) # 可以查看C类的对象搜索方法时的先后顺序
运行结果:
----A test----
搜索顺序: (<class ‘main.C’>, <class ‘main.A’>, <class ‘main.B’>, <class ‘main.base’>, <class ‘object’>)
12.3.3 重写父类方法
所谓重写,就是子类中,有一个和父类相同名字的方法,在子类中的方法会覆盖掉父类中同名的方法
class Cat(object):
def sayHello(self):
print(“halou-----1”)
class Bosi(Cat):
def sayHello(self):
print(“halou-----2”)
bosi = Bosi()
bosi.sayHello()
运行结果:
halou-----2
12.3.4 调用父类的方法
调用父类的__init__方法
#coding=utf-8
class Cat(object):
def init(self,name):
self.name = name
self.color = ‘yellow’
class Bosi(Cat):
def init(self,name):
调用父类的__init__方法1(python2)
#Cat.init(self,name)
调用父类的__init__方法2
#super(Bosi,self).init(name)
调用父类的__init__方法3
super().init(name)
def getName(self):
return self.name
bosi = Bosi(‘xiaohua’)
print(bosi.name)
print(bosi.color)
运行结果:
xiaohua
yellow
调用父类的普通方法
父类
class Dog:
def bark(self):
print(“汪汪叫”)
子类 继承
class XiaoTianQuan(Dog):
def fly(self):
print(“我会飞”)
可以重写父类中的同名方法
def bark(self):
super().父类方法名 调用父类中的方法 (第一种方式)(推荐)
super().bark()
父类名.方法(self) 调用父类中的方法 (第二种方式,python2.x)(不推荐,父类名修改后,此处也得改)
Dog.bark(self)
注意:如果使用子类名调用方法,可能会出现递归调用 – 死循环!
XiaoTianQuan.bark(self) # 会产生死循环
针对子类特有的需求,进行扩展
print(“神一样的叫唤…”)
xtq = XiaoTianQuan()
xtq.bark()
运行结果:
汪汪叫
汪汪叫
神一样的叫唤…
多态的概念是应用于Java和C#这一类强类型语言中,而Python崇尚“鸭子类型”。
所谓多态:定义时的类型和运行时的类型不一样,此时就成为多态,例:Python “鸭子类型”
class F1(object):
def show(self):
print (‘F1.show’)
class S1(F1):
def show(self):
print (‘S1.show’)
class S2(F1):
def show(self):
print (‘S2.show’)
def Func(obj):
print (obj.show())
s1_obj = S1()
Func(s1_obj)
s2_obj = S2()
Func(s2_obj)
运行结果:
S1.show
None
S2.show
None
12.5.1 类方法
是类对象所拥有的方法,需要用修饰器@classmethod
来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以cls
作为第一个参数(当然可以用其他名称的变量作为其第一个参数,但是大部分人都习惯以’cls’作为第一个参数的名字,就最好用’cls’了),能够通过实例对象和类对象去访问。
class People(object):
country = ‘china’
类方法,用classmethod来进行修饰
@classmethod
def getCountry(cls):
return cls.country
p = People()
print(p.getCountry()) # 可以用过实例对象引用
print(People.getCountry()) # 可以通过类对象引用
类方法还有一个用途就是可以对类属性进行修改:
class People(object):
country = ‘china’
类方法,用classmethod来进行修饰
@classmethod
def getCountry(cls):
return cls.country
@classmethod
def setCountry(cls, country):
cls.country = country
p = People()
print(p.getCountry()) # 可以用过实例对象引用
print(People.getCountry()) # 可以通过类对象引用
p.setCountry(‘japan’)
print(p.getCountry())
print(People.getCountry())
运行结果:
china
china
japan
japan
结果显示在用类方法对类属性修改之后,通过类对象和实例对象访问都发生了改变
12.5.2 静态方法
需要通过修饰器@staticmethod
来进行修饰,静态方法不需要多定义参数
class People(object):
country = ‘china’
@staticmethod
#静态方法
def getCountry():
return People.country
print People.getCountry()
总结
从类方法和实例方法以及静态方法的定义形式就可以看出来,类方法的第一个参数是类对象cls,那么通过cls引用的必定是类对象的属性和方法;而实例方法的第一个参数是实例对象self,那么通过self引用的可能是类属性、也有可能是实例属性(这个需要具体分析),不过在存在相同名称的类属性和实例属性的情况下,实例属性优先级更高。静态方法中不需要额外定义参数,因此在静态方法中引用类属性的话,必须通过类对象来引用
================================================================
当Python检测到一个错误时,解释器就无法继续执行了,反而出现了一些错误的提示,这就是所谓的"异常"。
13.1.1 捕获异常 try…except…
看如下示例:
try:
print(‘-----test–1—’)
open(‘123.txt’,‘r’)
print(‘-----test–2—’)
except IOError:
pass
运行结果:
-----test–1—
说明:
-
此程序看不到任何错误,因为用except 捕获到了IOError异常,并添加了处理的方法
-
pass 表示实现了相应的实现,但什么也不做;如果把pass改为print语句,那么就会输出其他信息
小总结:
-
把可能出现问题的代码,放在try中
-
把处理异常的代码,放在except中
13.1.2 except捕获多个异常
看如下示例:
try:
print (num)
except IOError:
print(‘产生错误了’)
运行结果如下:
Traceback (most recent call last):
File “C:/Users/WH/Desktop/Python基础/第一个Python程序.py”, line 2, in
print(num)
NameError: name ‘num’ is not defined
想一想:
上例程序,已经使用except来捕获异常了,为什么还会看到错误的信息提示?
答:
except捕获的错误类型是IOError,而此时程序产生的异常为 NameError ,所以except没有生效
修改后的代码为:
try:
print (num)
except NameError:
print(‘产生错误了’)
运行结果如下:
产生错误了
实际开发中,捕获多个异常的方式,如下:
#coding=utf-8
try:
print(‘-----test–1—’)
open(‘123.txt’,‘r’) # 如果123.txt文件不存在,那么会产生 IOError 异常
print(‘-----test–2—’)
print(num)# 如果num变量没有定义,那么会产生 NameError 异常
except (IOError,NameError):
#如果想通过一次except捕获到多个异常可以用一个元组的方式
errorMsg里会保存捕获到的错误信息
print(errorMsg)
运行结果:
-----test–1—
Traceback (most recent call last):
File “C:/Users/WH/Desktop/Python基础/第一个Python程序.py”, line 4, in
open(‘123.txt’,‘r’) # 如果123.txt文件不存在,那么会产生 IOError 异常
FileNotFoundError: [Errno 2] No such file or directory: ‘123.txt’
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File “C:/Users/WH/Desktop/Python基础/第一个Python程序.py”, line 12, in
print(errorMsg)
NameError: name ‘errorMsg’ is not defined
注意:
- 当捕获多个异常时,可以把要捕获的异常的名字,放到except 后,并使用元组的方式仅进行存储
13.1.3 获取异常的信息描述
13.1.4捕获所有异常
13.1.5 else
咱们应该对else
并不陌生,在if中,它的作用是当条件不满足时执行的实行;同样在try…except…中也是如此,即如果没有捕获到异常,那么就执行else中的事情
try:
num = 100
print (num)
except NameError as errorMsg:
print(‘产生错误了:%s’%errorMsg)
else:
print(‘没有捕获到异常,真高兴’)
运行结果如下:
100
没有捕获到异常,真高兴
13.1.6 try…finally…
try…finally…语句用来表达这样的情况:
在程序中,如果一个段代码必须要执行,即无论异常是否产生都要执行,那么此时就需要使用finally。 比如文件关闭,释放锁,把数据库连接返还给连接池等
demo:
import time
try:
f = open(‘test.txt’)
try:
while True:
content = f.readline()
if len(content) == 0:
break
time.sleep(2)
print(content)
except:
#如果在读取文件的过程中,产生了异常,那么就会捕获到
#比如 按下了 ctrl+c
pass
finally:
f.close()
print(‘关闭文件’)
except:
print(“没有这个文件”)
说明:
test.txt文件中每一行数据打印,但是我有意在每打印一行之前用time.sleep方法暂停2秒钟。这样做的原因是让程序运行得慢一些。在程序运行的时候,按Ctrl+c中断(取消)程序。
我们可以观察到KeyboardInterrupt异常被触发,程序退出。但是在程序退出之前,finally从句仍然被执行,把文件关闭。
13.2.1. try嵌套中
import time
try:
f = open(‘test.txt’)
try:
while True:
content = f.readline()
if len(content) == 0:
break
time.sleep(2)
print(content)
finally:
f.close()
print(‘关闭文件’)
except:
print(“没有这个文件”)
运行结果:
没有这个文件
13.2.2 函数嵌套调用中
def test1():
print(“----test1-1----”)
print(num)
print(“----test1-2----”)
def test2():
print(“----test2-1----”)
test1()
print(“----test2-2----”)
def test3():
try:
print(“----test3-1----”)
test1()
print(“----test3-2----”)
except Exception as result:
print(“捕获到了异常,信息是:%s” % result)
print(“----test3-2----”)
test3()
print(“------华丽的分割线-----”)
test2()
运行结果:
----test3-1----
----test1-1----
捕获到了异常,信息是:name ‘num’ is not defined
----test3-2----
------华丽的分割线-----
----test2-1----
----test1-1----
Traceback (most recent call last):
File “C:/Users/WH/Desktop/Python基础/第一个Python程序.py”, line 26, in
test2()
File “C:/Users/WH/Desktop/Python基础/第一个Python程序.py”, line 9, in test2
test1()
File “C:/Users/WH/Desktop/Python基础/第一个Python程序.py”, line 3, in test1
print(num)
NameError: name ‘num’ is not defined
总结:
-
如果try嵌套,那么如果里面的try没有捕获到这个异常,那么外面的try会接收到这个异常,然后进行处理,如果外边的try依然没有捕获到,那么再进行传递。。。
-
如果一个异常是在一个函数中产生的,例如函数A---->函数B---->函数C,而异常是在函数C中产生的,那么如果函数C中没有对这个异常进行处理,那么这个异常会传递到函数B中,如果函数B有异常处理那么就会按照函数B的处理方式进行执行;如果函数B也没有异常处理,那么这个异常会继续传递,以此类推。。。如果所有的函数都没有处理,那么此时就会进行异常的默认处理,即通常见到的那样
-
注意观察上图中,当调用test3函数时,在test1函数内部产生了异常,此异常被传递到test3函数中完成了异常处理,而当异常处理完后,并没有返回到函数test1中进行执行,而是在函数test3中继续执行
你可以用raise语句来引发一个异常。异常/错误对象必须有一个名字,且它们应是Error或Exception类的子类
下面是一个引发异常的例子:
class ShortInputException(Exception):
‘’‘自定义的异常类’‘’
def init(self, length, atleast):
#super().init()
self.length = length
self.atleast = atleast
def main():
try:
s = input('请输入 --> ')
if len(s) < 3:
raise引发一个你定义的异常
raise ShortInputException(len(s), 3)
except ShortInputException as result:#x这个变量被绑定到了错误的实例
print(‘ShortInputException: 输入的长度是 %d,长度至少应是 %d’% (result.length, result.atleast))
else:
print(‘没有异常发生.’)
main()
运行结果如下:
请输入 --> 11
ShortInputException: 输入的长度是 2,长度至少应是 3
注意
- 以上程序中,关于代码#super().init()的说明
这一行代码,可以调用也可以不调用,建议调用,因为
__init__
方法往往是用来对创建完的对象进行初始化工作,如果在子类中重写了父类的__init__
方法,即意味着父类中的很多初始化工作没有做,这样就不保证程序的稳定了,所以在以后的开发中,如果重写了父类的__init__
方法,最好是先调用父类的这个方法,然后再添加自己的功能
class Test(object):
def init(self, switch):
self.switch = switch #开关
def calc(self, a, b):
try:
return a/b
except Exception as result:
if self.switch:
print(“捕获开启,已经捕获到了异常,信息如下:”)
print(result)
else:
#重新抛出这个异常,此时就不会被这个异常处理给捕获到,从而触发默认的异常处理
raise
a = Test(True)
a.calc(11,0)
print(“----------------------华丽的分割线----------------”)
a.switch = False
a.calc(11,0)
运行结果:
捕获开启,已经捕获到了异常,信息如下:
division by zero
----------------------华丽的分割线----------------
Traceback (most recent call last):
File “C:/Users/WH/Desktop/Python基础/第一个Python程序.py”, line 21, in
a.calc(11,0)
File “C:/Users/WH/Desktop/Python基础/第一个Python程序.py”, line 6, in calc
return a/b
ZeroDivisionError: division by zero
================================================================
14.1.1 Python中的模块
有过C语言编程经验的朋友都知道在C语言中如果要引用sqrt函数
,必须用语句#include <math.h>
引入math.h这个头文件,否则是无法正常进行调用的。
那么在Python中,如果要引用一些其他的函数,该怎么处理呢?
在Python中有一个概念叫做模块(module),这个和C语言中的头文件以及Java中的包很类似,比如在Python中要调用sqrt函数
,必须用import关键字引入math这个模块,下面就来了解一下Python中的模块。
说的通俗点:模块就好比是工具包,要想使用这个工具包中的工具(就好比函数),就需要导入这个模块
14.1.2 import
在Python中用关键字import
来引入某个模块,比如要引用模块math,就可以在文件最开始的地方用import math来引入。
形如:
import module1,mudule2…
当解释器遇到import语句,如果模块在当前的搜索路径就会被导入。
在调用math模块中的函数时,必须这样引用:
模块名.函数名
- 想一想:
为什么必须加上模块名调用呢?
- 答:
因为可能存在这样一种情况:在多个模块中含有相同名称的函数,此时如果只是通过函数名来调用,解释器无法知道到底要调用哪个函数。所以如果像上述这样引入模块的时候,调用函数必须加上模块名
import math
#这样会报错
print sqrt(2)
#这样才能正确输出结果
print math.sqrt(2)
有时候我们只需要用到模块中的某个函数,只需要引入该函数即可,此时可以用下面方法实现:
from 模块名 import 函数名1,函数名2…
不仅可以引入函数,还可以引入一些全局变量、类等
- 注意:
- 通过这种方式引入的时候,调用函数时只能给出函数名,不能给出模块名,但是当两个模块中含有相同名称函数的时候,后面一次引入会覆盖前一次引入。也就是说假如模块A中有函数function( ),在模块B中也有函数function( ),如果引入A中的function在先、B中的function在后,那么当调用function函数的时候,是去执行模块B中的function函数。
- 如果想一次性引入math中所有的东西,还可以通过from math import *来实现
14.1.3 from…import
Python的from语句让你从模块中导入一个指定的部分到当前命名空间中
语法如下:
from modname import name1[, name2[, … nameN]]
例如,要导入模块fib的fibonacci函数,使用如下语句:
from fib import fibonacci
注意
- 不会把整个fib模块导入到当前的命名空间中,它只会将fib里的fibonacci单个引入
14.1.4 from … import *
把一个模块的所有内容全都导入到当前的命名空间也是可行的,只需使用如下声明:
from modname import *
注意
- 这提供了一个简单的方法来导入一个模块中的所有项目。然而这种声明不该被过多地使用。
14.1.5 as
In [1]: import time as tt
In [2]: time.sleep(1)
NameError Traceback (most recent call last)
in ()
----> 1 time.sleep(1)
NameError: name ‘time’ is not defined
In [3]:
In [3]:
In [3]: tt.sleep(1)
In [4]:
In [4]:
In [4]: from time import sleep as sp
In [5]: sleep(1)
NameError Traceback (most recent call last)
in ()
----> 1 sleep(1)
NameError: name ‘sleep’ is not defined
In [6]:
In [6]:
In [6]: sp(1)
In [7]:
14.1.6 定位模块
当你导入一个模块,Python解析器对模块位置的搜索顺序是:
-
当前目录
-
如果不在当前目录,Python则搜索在shell变量PYTHONPATH下的每个目录。
-
如果都找不到,Python会察看默认路径。UNIX下,默认路径一般为/usr/local/lib/python/
-
模块搜索路径存储在system模块的sys.path变量中。变量里包含当前目录,PYTHONPATH和由安装过程决定的默认目录。
14.2.1 定义自己的模块
在Python中,每个Python文件都可以作为一个模块,模块的名字就是文件的名字。
比如有这样一个文件test.py,在test.py中定义了函数add
test.py
def add(a,b):
return a+b
14.2.2 调用自己定义的模块
那么在其他文件中就可以先import test,然后通过test.add(a,b)来调用了,当然也可以通过from test import add来引入
main.py
import test
result = test.add(11,22)
print(result)
14.2.3 测试模块
在实际开中,当一个开发人员编写完一个模块后,为了让模块能够在项目中达到想要的效果,这个开发人员会自行在py文件中添加一些测试信息,例如:
test.py
def add(a,b):
return a+b
用来进行测试
ret = add(12,22)
print(‘int test.py file,12+22=%d’%ret)
如果此时,在其他py文件中引入了此文件的话,想想看,测试的那段代码是否也会执行呢!
main.py
import test
result = test.add(11,22)
print(result)
运行现象:
int test.py file,12+22=34
33
至此,可发现test.py中的测试代码,应该是单独执行test.py文件时才应该执行的,不应该是其他的文件中引用而执行
为了解决这个问题,python在执行一个文件时有个变量__name__
直接运行此文件
def add(a, b):
return a + b
# 用来进行测试
ret = add(12,22)
print(‘int test.py file,12+22=%d’%ret)
print("in test.py file,name is %s "%name)
运行结果:
in test.py file,name is main
在其他文件中import此文件
in test.py file,name is test
33
总结:
- 可以根据__name__变量的结果能够判断出,是直接执行的python脚本还是被引入执行的,从而能够有选择性的执行测试代码
执行test.py:
def add(a, b):
return a + b
if name == “main”:
用来进行测试
ret = add(12, 22)
print(‘int test.py file,12+22=%d’ % ret)
print("in test.py file,name is %s " % name)
运行结果:
D:\ProgramData\Anaconda3\python.exe C:/Users/WH/Desktop/Python基础/test.py
int test.py file,12+22=34
in test.py file,name is main
执行main.py :
import test
result = test.add(11, 22)
print(result)
运行结果:
33
14.3.1 没有__all__
test.py
class Test(object):
def test(self):
print(“Test类中的test函数”)
def test1():
print(“test1函数”)
def test2():
print(“test2函数”)
main.py调用
from test import *
a=Test()
print(a.test())
print(test1())
print(test2())
运行结果:
Test类中的test函数
None
test1函数
None
test2函数
None
14.3.2. 模块中有__all__
all=[“Test”,“test1”]
class Test(object):
def test(self):
print(“Test类中的test函数”)
def test1():
print(“test1函数”)
def test2():
print(“test2函数”)
执行main.py
from test import *
a=Test()
print(a.test())
print(test1())
print(test2())
运行结果:
Traceback (most recent call last):
File “C:/Users/WH/Desktop/Python基础/第一个Python程序.py”, line 6, in
print(test2())
NameError: name ‘test2’ is not defined
Test类中的test函数
None
test1函数
None
文末有福利领取哦~
👉一、Python所有方向的学习路线
Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
👉二、Python必备开发工具
👉三、Python视频合集
观看零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。
👉 四、实战案例
光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。(文末领读者福利)
👉五、Python练习题
检查学习结果。
👉六、面试资料
我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
👉因篇幅有限,仅展示部分资料,这份完整版的Python全套学习资料已经上传
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!