面向对象编程vs面向过程编程
面向对象的编程,是通过对象实现某项功能;面向过程的编程,是将实现该功能分解为一个个步骤,再通过对每个步骤进行抽象进行编程,通过逐一实现每个步骤,最终实现目标功能。
通过生活中的例子来解释:目标:倒1杯白开水,倒1杯果汁。
面向对象的编程思想是将各种功能保存到对应的对象里(例如,杯子是一个对象,倒水是一个对象,果汁也是一个对象),需要实现某个目标的时候,找到并调用需要的对象即可,如果没有现成的对象,则先创造对象再调用对象。面向对象编程更容易维护,也容易复用。
面向对象的三大特性:封装、继承和多态。
每个对象都保存三种数据:
1)id由解析器生成的
2)在CPython中,id是对象的内存地址
3)用id(a)查看对象a的id
k = True
print (id('123') #140443389493616
print (id(k)) #4551017680
print (id(print)) #140443323327584
print (id(id)) #140443323326544
print (id(None)) #4550715496
注意:
a = [1,2,3] —> 这个操作是将列表对象的id赋值给变量a
a[0] = 4 ----> 这种操作是通过变量a修改列表对象的值,不修改变量a内所存储的对象id
a = [4,2,3] ----> 这种操作是修改变量a内所存储的对象id,即此时变量a已经指向了新的列表对象[4,2,3]。
原列表对象[1,2,3]仍在内存中存在,但再没有变量指向它了。这个没有变量能找到原列表[1,2,3],就像在太空中孤单游荡的失联宇宙飞船。。。
#【修改对象的值和修改变量的区别】
a = [1,2,3] #将列表对象[1,2,3]的id赋值给变量a
print (a, 'id=', id(a))
# 运行结果
# [1, 2, 3] id=2604572200832
a[0] = 4
#上述操作为通过变量a, 修改列表对象的值,使列表对象的值变为[4,2,3]。注意此时变量a仍指向原列表对象,即id(a)未发生变化,只是原列表对象的值改变
print(a, 'id=', id(a))
# 运行结果
# [4, 2, 3] id=2604572200832
a = [4,2,3]
#上述操作创建了一个新的列表对象[4,2,3],并让变量a指向了新的列表对象,注意id(a)发生了变化。
print(a, 'id=', id(a))
# 运行结果
# [4, 2, 3] id=2604572426880
当修改对象的时候,如果有其他的变量也指向了同一个对象,则其他指向该对象的变量也会发生变化。
#【修改对象的影响】
a = [1,2,3] #变量a指向对象[1,2,3]
b = a #变量a赋值给变量b,即深拷贝,所以变量b也指向对象[1,2,3]
print ('a=', a, ', a_id=', id(a))
print ('b=', b, ', b_id=', id(b))
# 运行结果 a,b的id一样,说明指向同一个对象
#a=[1, 2, 3] , a_id=2604571277760
#b=[1, 2, 3] , b_id=2604571277760
b[0] = 10
print ('a=', a, ', a_id=', id(a))
print ('b=', b, ', b_id=', id(b))
# 运行结果 a,b的值都发生了变化,但是id都没有发生变化,因为所指向的对象的值发生了改变
#a=[10, 2, 3] , a_id=2604571277760
#b=[10, 2, 3] , b_id=2604571277760
当修改变量的时候,如果有其他的变量也指向了同一个对象,则其他指向该对象的变量不会发生变化。
#【修改对象的影响】
a = [1,2,3] #变量a指向对象[1,2,3]
b = a #变量a赋值给变量b,所以变量b也指向对象[1,2,3]
b = [10,2,3] #将变量b指向了另一个对象[10,2,3]
print ('a=', a, ', a_id=', id(a))
print ('b=', b, ', b_id=', id(b))
#运行结果 变量b的值和id均发生了变化,而变量a的值和id不受影响
#a= [1, 2, 3] , a_id= 2604572370496
#b= [10, 2, 3] , b_id= 2604572370624
#【修改对象的影响--浅拷贝】
a = [1,2,3] #变量a指向对象[1,2,3]
b = a[:] #变量a的值赋值给变量b,即浅拷贝,变量b指向新的对象[1,2,3]
print ('a=', a, ', a_id=', id(a))
print ('b=', b, ', b_id=', id(b))
# 运行结果 a,b的值相同,但是id不同
# a= [1, 2, 3] , a_id= 2604572251072
# b= [1, 2, 3] , b_id= 2604572235328
# 这种情况下,修改变量b就不会对对象[1,2,3]产生影响,也不会对变量a产生影响
类(class):简单理解为创建对象的图纸,根据类来创建对象,对象是类的实例(instance), 一个类可以创建多个对象。如果多个对象都是通过一个类创建的,则称这些对象为一类对象。
python有内置类,比如int(), str()等,也允许自定义类。可以用type()函数查看对象所属的类。
如何自定义类:用class 关键字创建。语法:class 类名([父类]): 代码块
【示例-创建一个最简单的类】
#【示例-创建一个最简单的类】
class My_Class():
pass #此处暂不写任何功能,但空着会认为程序未完结,会报错,所以用pass跳过。
print (My_Class)
# 运行结果
#
在类中可以定义变量和函数。在类中定义的变量,称为属性,将成为这个类的所有实例的公共属性,所有实例都可以访问;在类中定义的函数,称为方法,该类的方法可以通过实例进行访问。
#【示例-在类里定义变量和函数】
class Person():
name = 'Eva' #定义Person类的属性name为Eva
birth_year = 1991 #定义Person类的属性birth_year为1991
age = lambda birth_year, year: year-birth_year
def say_hello():
print('hello')
def say_bye(a): #此处随便定义一个形参,占位,防止报错
print('bye')
p1 = Person() #创建一个名字为p1的Person类型
print(p1) #访问p1的值(因为p1没有值,所以返回的其实是type和id)
# 运行结果
# <__main__.Person object at 0x7ff8e5bb1b50>
调用属性和调用方法的区别:
调用方法(即类里面定义的函数)和调用一般函数的区别:
#【调用属性】
print (p1.name) #访问这个p1对象的name属性值(注意,name属性值不是p1对象赋予的,不存在p1对象的值里,而是类定义的,存在类的值里)
# 运行结果
# Eva
#【修改p1的name属性】
# 将p1的name属性值修改
# 注意,本次修改实际上是在p1这个对象的值里新增了一个name属性,这个name属性值为'Bob', 类似于对p1对象定义了专属名字。
p1.name = 'Bob'
print(p1.name)
# 运行结果
# Bob
# 上述修改的是p1对象的name属性,没有修改p1对象所属类的name属性
# 所以,创建一个新的对象p2后,由于p2本身没有定义name的值,所以调用p2的name属性值时仍调用的是类Person里定义的属性值。
p2 = Person()
print(p2.name)
# 运行结果
# Eva
#【调用方法】
p1.say_hello()
# 运行结果,报错,因为类里的函数没有定义形参,但是在调用时会默认传递一个参数
# TypeError: say_hello() takes 0 positional arguments but 1 was given
p1.say_bye() #调用时默认传递一个参数
# 运行结果
# bye
属性和方法的查找流程:
如果某个属性/方法是这个类里所有实例对象共有的,则应在类里定义这个属性/方法;如果某个属性/方法是某个或某几个实例对象特有的,则应该在对象里定义这个属性/方法。
在类的方法(函数)中,不能直接调用类中定义的属性。
【实例-类的方法不能直接调用类中定义的属性】
#【说明类的方法不能直接调用类中定义的属性】
class Person():
name = 'Bob' #在类中定义name属性
def say_hello(a): #调用函数时默认传入一个参数
print ('Hi, my name is %s' %name) #将name属性作为参数变量在函数中调用
p1 = Person()
p1.say_hello()
# 运行结果
# NameError: name 'name' is not defined
# 报错,name没有被定义。
# 将调用的参数变成某个对象的name属性,虽然实际中不会这么使用。
class NewPerson():
name = 'Bob'
def say_hello(a):
print('Hi, my name is %s' %p2.name)
p2 = NewPerson()
p3 = NewPerson()
p2.say_hello()
# 运行结果
# Hi, my name is Bob
#修改p2名字后再尝试调用say_hello函数
p2.name = 'Test'
p2.say_hello()
p3.say_hello()
# 运行结果
# Hi, my name is Test
# Hi, my name is Test
针对上文代码中,类中定义的方法可以调用某一个实例对象的属性,其实是因为类方法中,系统默认传递给该形参(即第一个形参)的实参是调用该方法的实例对象。
该默认形参的名称虽然可以随意取名,但是按照习惯通常称这个形参为"self“。
【类的方法中的self形参】
#【self形参的解析】
# 对上文代码中定义的Person类进行修改
class Person():
age = 0
def say_hello(a):
print (a) #看看参数a的id地址
print ('Hi, my name is %s' %p1.name)
def my_age(self):
print('the info of self is', self) #看一下参数self的id地址
print('Hi, I am %s years old.' %self.age)
p1 = Person()
print ('the id of p1 is', id(p1)) #打印一下p1的id信息
# 运行结果
# the id of p1 is 140376857803088
p1.name = 'Eva'
p1.say_hello() #调用类中定义的say_hello函数
# 运行结果
# the id of a is 140376857803088
# Hi, my name is Eva
#发现传入实参后a的地址p1的地址一样,说明传入的实参就是p1.
p1.my_age() #调用类中定义的my_age函数
# 运行结果
# the id of self is 140376857803088
# Hi, I am 0 years old.
# 发现传入实参后self的地址p1的地址一样,说明传入的实参就是p1,而且形参名字的改变不影响传入实参。
为什么需要类的初始化,即__init__方法?
__init__是一种特殊方法(也称为魔术方法)。
【优化Person类,添加特殊方法init】
#【优化Person类,添加特殊方法init】
class Person():
name = '这是存储在类中的姓名' #这个name属性存储在Person类对象的value里
def __init__(self): #init属于类方法,所以也需要传递形参self
print ('init方法调用啦') #测试init方法的调用
self.name = '这是存储在对象中的姓名' #这个name属性存储在创建的实例对象的value里
def my_age(self):
print('Hi, I am %s years old.' %self.age)
def say_hello(self):
print ('Hi, my name is %s' %self.name)
p1 = Person()
p1.__init__() #仅用于说明init可以手动调用,但实际编码中不要这么写,因为init会在特殊时刻自动调用
# 运行结果,发现__init__被调用了两次
# init方法调用啦
# init方法调用啦
print (p1.name)
# 运行结果
# '这是存储在对象中的姓名‘
上述代码中,p1 = Person()的实际运行流程:
【继续优化Person类,使用特殊方法init初始化对象属性】
#【优化Person类,使用特殊方法init初始化对象属性】
class Person():
name = '这是存储在类中的姓名' #这个name属性存储在Person类对象的value里
def __init__(self, name): #init方法里传递name参数
self.name = name #这个name是外部传递的参数
def my_age(self):
print('Hi, I am %s years old.' %self.age)
def say_hello(self):
print ('Hi, my name is %s' %self.name)
p1 = Person()
# 运行结果 (提示传递的参数缺失)
# TypeError: __init__() missing 1 required positional argument: 'name'
p1 = Person('Eva')
p1.say_hello()
# 运行结果
# Hi, my name is Eva
常用的类的基本结构
class 类名([父类]):
公共的属性(需对属性赋值)
#对象的初始化方法
def __init__(self, [其他形参]): ([]表示非必须参数)
self.属性名 = 形参名
#类的其他方法
def method_1(self, [其他形参]):
......
def 方法名(self, [其他形参]):
.......
封装:在定义类的时候,通过一些方式,隐藏对象中一些不希望被外部访问到的属性或方法。
注意,没有方式可以完全隐藏属性或者方法使得外部无法访问,但可以通过一些方式使得这些希望隐藏的属性或方法很难被外部访问到。
隐藏属性的方法:
【隐藏属性】
#【示例-隐藏属性方法1】
class Dog:
def __init__(self, name): #注意init两边各有两个下划线
self.hidden_name = name #在类里,属性名是hidden_name
def say_hello(self):
print(f'hello, I am {self.hidden_name}.')
d = Dog('旺财') #从外部传入狗的名字
d.say_hello()
# 运行结果
# hello, I am 旺财.
d.name = '小黑' #除非外部查看类的定义,否则一般很难猜到属性名是hidden_name,所以一旦创建了对象d,则很难从外部直接修改狗的名字
d.say_hello()
# 运行结果
# hello, I am 旺财.
d.hidden_name = '小白' #但如果真找出来了,通过属性名还是可以从外部修改对象的名字的。
d.say_hello()
# 运行结果
# hello, I am 小白.
在隐藏了属性后,有时候外部还是会需要获取属性,则通过专门预留的通道进行访问,即使用getter、setter方法。
#【在类中设置getter和setter方法】
class Person:
def __init__(self, name, age):
self.hidden_name = name
self.hidden_age = age
def get_name(self): #返回对象的name属性值
return self.hidden_name
def get_age(self): #返回对象的age属性
return self.hidden_age
def set_name(self, name): #修改对象的name属性
self.hidden_name = name
print(f'reset {self.hidden_name}\'s age to {age}.')
def set_age(self, age): #修改对象的age属性,并添加校验规则
if int(age) >= 0:
self.hidden_age = age
else:
print('age属性值需为不小于0的整数')
p1 = Person('Eva', 18)
print(p1.get_name())
print(p1.get_age())
# 运行结果
# Eva
# 18
p1.set_age(10)
print(p1.get_age())
# 运行结果
# reset Eva's age to 10.
# 10
p1.set_age(-10)
print(p1.get_age())
# 运行结果
# age属性值需为不小于0的整数
# 18
使用了封装后,增加了类的定义的复杂程度,但是增强了数据的安全性。一是,隐藏了属性,使得调用者无法从外部随意修改对象的属性;二是,增设专门用于获取和修改属性的专用通道,可以控制属性的可读性和值的范围。
类型转换四个函数:int(), float(), str(), bool()
a = '123'
b = int(a)
print (a, type(a)) #123
#变量a的值和类型都没有改变
print (b, type(b)) #123
#转换成整型后的对象返回给了变量b
m = 123.6
n = int(m)
print (n, type(n)) #123
#浮点转整数的时候,舍去小数位,只取整数位。
x = True
y = int(True)
print (y) #1
#布尔值转整型,True -> 1, False -> 0
a = 123
b = float(a)
print(b) #123.0
def 函数名([形参1,形参2,......]):
代码块
#【示例】
def test():
print ('my first function')
函数名代表函数对象,函数名()代表调用函数
print (函数名)会返回函数的地址
print (test) #
print (type(test)) #
test #程序没有任何反应
test() #my first function
在定义函数时,可以在函数名后的()里定义多个形参,形参之间用逗号隔开。
#【在创建带形参的函数,并传递实参】
#定义带形参的函数
def sum(a, b): #a, b是函数sum的形参
print (a+b) #sum函数的功能是将两个参数a,b相加然后打印出来
#调用函数时传递实参
sum(10, 20) #10, 20函数sum的实参,分别给形参a,b赋值10,20
#调用函数时传递部分实参
sum(10, )#系统报错,缺少必须的实参b,TypeError: sum() missing 1 required positional argument: 'b'
定义形参时,可以为形参指定默认值。如果在创建函数的时候指定了形参的默认值,在调用函数的时候,如果用户没有传递实参,则默认值生效,反之默认值不生效。
指定默认值时,需从后向前指定,即含有默认值的参数需要放在不含默认值的参数的后面,否则系统报错 “SyntaxError: non-default argument follows default argument”。
#【定义函数时指定形参默认值】
def display(a, b=0):
print ('a=',a)
print ('b=',b)
display(1,2) #调用函数时对两个形参都赋值,则默认值不生效
#a=1
#b=2
display(1,) #调用函数时对一个形参不赋值,则默认值生效
#a=1
#b=0
#【定义函数时,指定默认值的形参放在未指定默认值的形参之前】
def display(a = 1, b):
print ('a=',a)
print ('b=',b)
display(1,2)
#【运行结果】
# File "", line 2
# def displayer(a =1, b):
# ^
# SyntaxError: non-default argument follows default argument
实参的传递方式:位置参数,关键字参数
#练习实参的传递方式:位置参数、关键字参数
def intro(name, age, country):
print (f'Hi, my name is {name}, I\'m {age}, and I\'m from {country}.')
#采用位置参数,按位置传递实参
intro ('Anna', 17, 'China')
intro (21, 'Max', 'Hebei')
#【运行结果】
#Hi, my name is Anna, I'm 17, and I'm from China.
#Hi, my name is 21, I'm Max, and I'm from Hebei.
#采用关键字参数,按关键字传递实参
intro(age = 30, name = 'Maggie', country='Thailand')
#【运行结果】
#Hi, my name is Maggie, I'm 30, and I'm from Thailand.
#【任意类型实参都可以-示例】
def test(a):
print ('a =', a)
test(1) #整型
test(0.666) #浮点型
test('im a test') #字符串
test(True) #布尔值
test(['hello', 'readers']) #列表
test(('have', 'a', 'nice', 'day')) #元组
test(dict(name = 'Tom', type = 'cat', hobby = 'catch Jerry')) #字典
# 运行结果
# a = 1
# a = 0.666
# a = im a test
# a = True
# a = ['hello', 'readers']
# a = ('have', 'a', 'nice', 'day')
# a = {'name': 'Tom', 'type': 'cat', 'hobby': 'catch Jerry'}
#【函数本身也可以做实参】
def welcome(a):
print ('hello,',a)
def test(b):
print('b =', b)
test(welcome) #此处是将函数welcome本身作为实参,而不是调用welcome函数后的结果
# 运行结果
# b =
#【实参类型不符合函数要求时,会导致报错】
def test(a,b):
print('result =', a+b)
test(10,11)
# 运行结果 因为两个整型可以相加,所以函数正常运行
# 21
test(10, '11')
# 运行结果 因为整型和字符串不能相加,所以函数运行失败,系统报错
# TypeError: unsupported operand type(s) for +: 'int' and 'str'
函数里对形参的操作可能会影响实参传递的对象本身,继而影响所有指向该对象的变量
# 【修改函数中的形参对实参的影响】
def change(a):
a[0] = 1
print('a的id:', id(a))
print ('a =', a)
c = ['hello', 'you']
print ('c的id:', id(c))
change(c) #向函数change传入实参c
print (c)
# 运行结果
# c的id: 2855052165248 #变量c所指向的对象的内存地址
# a的id: 2855052165248 #形参a和实参c的id一样,说明两者指向了同一个对象,说明实参c传递的是对象的地址。
# a = [1, 'you'] #函数通过修改形参,修改了对象的值
# [1, 'you'] #因为对象的值被修改,所以指向该对象的c也受到影响
#【向函数传递实参副本】
def change(a):
a[0] = 1
print('a的id:', id(a))
print ('a =', a)
c = ['hello', 'you']
print ('c的id:', id(c))
change(c.copy()) #向函数change传入实参c的副本
print (c)
# 运行结果
# c的id: 2855052216000
# a的id: 2855052218304 #形参a和实参c指向了不同的对象
# a = [1, 'you']
# ['hello', 'you']
change(c[:]) #向函数change传入实参c的副本
print (c)
# 运行结果
# a的id: 2855052216000 #形参a的id又发生了变化,和实参c指向了不同的对象
# a = [1, 'you']
# ['hello', 'you']
当函数不确定要使用多少个参数时(比如,求任意个数字之和),可以设置不定长参数。
不定长参数有两种类型:1. 不定长位置参数(*形参名),2. 不定长关键字参数(**形参名)
不定长位置参数(*a)的用法
#【设置只接受位置参数的不定长位置参数*a】
def show_info(*a):
print ('a=',a)
print ('a tpye is', type(a))
show_info(123,456,'mamamia')
# 运行结果
# a= (123, 456, 'mamamia')
# a tpye is
#【定义一个函数,可以对任意个数字求和】
def sum_num(*a):
# 创建一个变量用于记录结果
result = 0
# 遍历元组,求和
for num in a:
result += num
return (result)
# 调用函数,并对(3,4,5,6,7)求和
sum_num(3,4,5,6,7)
# 运行结果
# Out: 25
# 使用不定长位置参数的时候,不传入参数也不会因参数个数不匹配而报错
sum_num()
# 运行结果
# Out: 0
# 【不定长位置参数接收关键字参数,系统报错】
def sum_num (*nums):
result = 0
for num in numes:
result += num
return (result)
sum_num (a=1, b=2, c=3)
# 运行结果 因为对不定长位置参数传递了关键字参数,所以系统报错
# TypeError: sum_num() got an unexpected keyword argument 'a'
不定长关键字参数(**b)的用法
#【设置不定长关键字参数**b】
def show_info (**b):
print ('b=', b)
print ('b tpye is', type(b))
show_info (a=5,b=6,c=7)
# 运行结果 参数b变成了字典
# b= {'a': 5, 'b': 6, 'c': 7}
# b tpye is
#【不定长关键字参数,不接收位置参数】
def show_info (**b):
print ('b=', b)
print ('b tpye is', type(b))
show_info (1,2,'hello', a=5,b=6,c=7)
# 运行结果 系统报错提示,**b不接收位置参数,但是传递的实参里有三个位置参数
# TypeError: show_info() takes 0 positional arguments but 3 were given
#【在所有形参的位置最前面加*】
def print_name(*, names, tag):
for name in names:
print(tag,':', name)
#这里的names是一个元组,是实参。
names = ('Eva', 'Bob', 'Jane')
# 所有的参数都必须是关键字参数
print_name(tag='speaker name', names = names)
# 运行结果
# speaker name : Eva
# speaker name : Bob
# speaker name : Jane
# 如果有参数不是关键字参数,系统报错
print_name('speaker name', names = names)
# 运行结果 系统提示该函数没有位置参数,但却传递了一个位置参数。
# TypeError: print_name() takes 0 positional arguments but 1 positional argument (and 1 keyword-only argument) were given
#【不定长位置参数可以与定长的参数混合使用】
# 位置参数与不定长位置参数
def print_name(tag, *names):
for name in names:
print(tag,':', name)
print_name('speaker name','Eva', 'Bob', 'Jane')
# 运行结果
# speaker name : Eva
# speaker name : Bob
# speaker name : Jane
#【将不定长位置参数*a写在定长参数之前】-- 错误写法
def print_name(*names, tag):
for name in names:
print(tag,':', name)
print_name('speaker name','Eva', 'Bob', 'Jane')
# 运行结果 因为不定长位置形参*names抢走了所有的实参,导致没有值赋给形参tag,所以系统报错
# TypeError: print_name() missing 1 required keyword-only argument: 'tag'
#【不定长位置参数与关键字参数】
#关键字参数的位置可以在不定长位置参数的位置之后
def print_name(*names, tag):
for name in names:
print(tag,':', name)
#传递参数的时候,需要按函数定义时形参的顺序传递,最后传递关键字参数
print_name('Eva', 'Bob', 'Jane',tag='speaker name',)
# 运行结果 传递进来的实参依次传给不定长位置参数,直到关键字参数,由于不定长位置参数收不了,故而留了下来。
# speaker name : Eva
# speaker name : Bob
# speaker name : Jane
#如果传递参数的时候,没有按函数定义时的顺序传递
print_name(tag='speaker name','Eva', 'Bob', 'Jane')
# 运行结果 因为先传递了关键字参数,与函数定义时的顺序不符,出现系统报错,提示位置参数在关键字参数后面。
# SyntaxError: positional argument follows keyword argument
#【不定长关键字参数与位置参数混合使用】
def person_info(name, **info):
print ('name is', name)
print (name, 'info', info)
person_info ('Anna', gender = 'Female', age = '18', height = '170')
# 运行结果
# name is Anna
# Anna info {'gender': 'Female', 'age': '18', 'height': '170'}
#【不定长关键字参数与定长的关键字参数混合使用】
# 传递实参的时候,单个的关键字参数可以放在任意位置,无需与函数定义时的位置一致。
# 放在最后面
person_info (gender = 'Female', age = '18', height = '170',name = 'Anna')
# 运行结果
# name is Anna
# Anna info {'gender': 'Female', 'age': '18', 'height': '170'}
#放在最前面
person_info (name = 'Anna',gender = 'Female', age = '18', height = '170')
# 运行结果
# name is Anna
# Anna info {'gender': 'Female', 'age': '18', 'height': '170'}
#放在中间
person_info (gender = 'Female', age = '18', name = 'Anna',height = '170')
# 运行结果
# name is Anna
# Anna info {'gender': 'Female', 'age': '18', 'height': '170'}
#【不定长关键字参数后面存在其他参数】
def person_info(**info,name):
print ('name is', name)
print (name, 'info', info)
# 如果属于位置参数
person_info (gender = 'Female', age = '18', height = '170','Anna')
# 运行结果 系统报错
# SyntaxError: invalid syntax
# 如果是按关键字传参
person_info (gender = 'Female', age = '18', height = '170',name = 'Anna')
# 运行结果 系统报错
# SyntaxError: invalid syntax
#【不定长位置参数和不定长关键字参数混合使用】
def test_print (*a, **b):
print ('a=', a)
print ('b=', b)
test_print (1,2,3,4, x=5,y=6,z=7)
# 运行结果 a接收了所有的位置参数,b接收了所有的关键字参数
# a= (1, 2, 3, 4)
# b= {'x': 5, 'y': 6, 'z': 7}
#【多种不定长参数和定长参数混合使用】
def test_print (p, *a, x, **b):
print ('p=', p)
print ('a=', a)
print ('x=', x)
print ('b=', b)
test_print (1,2,3,4, x=5,y=6,z=7)
# 运行结果 p为位置参数,a为不定长位置参数,x为关键字参数,b为不定长关键字参数
# p= 1
# a= (2, 3, 4)
# x= 5
# b= {'y': 6, 'z': 7}
#【多种不定长参数和定长参数混合使用】-- 顺序错误
def test_print (p, x, *a, **b):
print ('p=', p)
print ('a=', a)
print ('x=', x)
print ('b=', b)
test_print (1,2,3,4, x=5,y=6,z=7)
# 运行结果 系统报错,因为x被当成位置参数赋值2,然后又被当做关键字参数再次赋值,导致x有多个值。
# TypeError: test_print() got multiple values for argument 'x'
#如果在传递实参时,调整一下关键字参数的位置,也是不行的
test_print (1,x=5,2,3,4, y=6,z=7)
# 运行结果 系统报错,因为关键字参数必须在位置参数之后
# SyntaxError: positional argument follows keyword argument
参数的解包:将传递进来的一串元素,逐一赋值给函数的形参。即实参是一包元素,传递实参的时候,将这包中的元素一个一个传递给函数的形参,而不是将这包元素作为一个实参传递给函数。
#【序列类型的参数解包】
def show_info (a, b, c):
print ('a =', a)
print ('b =', b)
print ('c =', c)
#未解包传递
test = (1,2,3)
show_info(test)
# 运行结果,此时test被当做一个参数传递函数,由于传递的实参个数小于形参个数,系统报错
# TypeError: show_info() missing 2 required positional arguments: 'b' and 'c'
#解包传递
test = (1,2,3)
show_info(*test)
# 运行结果
# a = 1
# b = 2
# c = 3
#【序列类型的参数解包-使用不定长位置形参】
def show_info (a, b, *c):
print ('a =', a)
print ('b =', b)
print ('c =', c)
#解包传递
test = (1,2,3,4,5,6,'End')
show_info(*test)
# 运行结果
# a= 1
# b= 2
# c= (3, 4, 5, 6, 'End')
#【字典类型的参数解包】
def show_info (name, age, gender):
print ('a =', a)
print ('b =', b)
print ('c =', c)
#解包传递,字典实参里的键名必须与函数的形参名一致
test = {
'name':'Eva', 'age':18, 'gender':'f'}
show_info(**test)
# 运行结果
# name = Eva
# age = 18
# gender = f
#解包传递,当字典实参里的键名与函数的形参名不一致时,系统报错
test = {
'name':'Eva', 'age':18, 'sex':'f'}
show_info(**test)
# 运行结果
# TypeError: show_info() got an unexpected keyword argument 'sex'
#【字典类型的解包传递--使用不定长关键字形参】
def show_info (name, age, **rest):
print ('name =', name)
print ('age =', age)
print ('rest =', rest)
# 解包传递
test = {
'name':'Eva', 'age':18, 'gender':'f', 'country': 'China'}
show_info(**test)
# 运行结果
# name = Eva
# age = 18
# rest = {'gender': 'f', 'country': 'China'}
返回值:函数执行后返回的结果,通过return指定函数的返回值。return后面可以跟任意对象,甚至是函数对象。可以直接使用返回值,也可以用一个变量接收这个返回值。
#【有和没有返回值的区别】
# 1. 创建一个将两个变量相加的函数,没有返回值,不打印结果
def sum_num (a, b):
c = a+b
sum_num (2,3)
print (sum_num (4,3))
# 运行结果 计算结果并没有传递到函数外
# None
# 2. 创建一个将两个变量相加的函数,没有返回值,打印结果
def sum_num_p (a, b):
c = a+b
#将函数执行的结果c打印,不返回
print(c)
sum_num_p (2,3)
# 运行结果
# 5
print (sum_num_p (4,3))
# 运行结果
# 7 sum_num函数执行时打印的计算结果c
# None 但计算结果c并没有传递到函数外,所以打印的结果为None
# 3. 创建一个将两个变量相加的函数,有返回值
def sum_num_r (a, b):
c = a+b
#将函数执行的结果返回。结果c将传递到函数外
return c
sum_num_r (2,3)
# 运行结果
# (空)
print (sum_num_r (4,3))
# 运行结果
# 7
#【可以直接使用返回值,也可以用变量接收返回值】
def sum_num_r (a, b):
c = a+b
#将函数执行的结果返回。结果c将传递到函数外
return c
print (sum_num_r (4,3)+1)
# 运行结果
# 8
result = sum_num_r (4,3)
print(result+2)
# 运行结果
# 9
#【返回值是函数】
def return_fx(a,b):
result = a+b
def fx(a,b): #这个fx是函数return_fx内部的函数
print (a+b)
return fx
r = return_fx(10,5)
print (r)
# 运行结果 由于r是一个函数对象,所以打印这个函数对象的信息
# .fx at 0x7fc33bcef170>
r()
# 运行结果 调用了r接收的函数,即调用了函数fx()
# 15
def sum_num (a, b):
c = a+b
return
print (sum_num (4,3))
# 运行结果
# None
def sum_num (a, b):
print (a)
return
print (b)
sum_num (5,6)
# 运行结果
# 5
# return后面的print语句不执行
def sum_num (a, b):
i = 0
while i < a:
b += 2
i += 1
return (f'结果b={b}, 执行次数i={i}')
result = sum_num (3, 5)
print (result)
# 运行结果
# 结果b=7, 执行次数i=1
# 由于return在循环中,所以在执行第一次循环时会遇到return,导致之后的循环均不再执行
print不带括号的函数名( 例如:print(fn) )和print带括号的函数名( 例如:print(fn()) ),是有区别的:
#【示例,打印函数时带括号和不带括号的区别】
def fn (a, b):
c = a+b
return c
print ('1.不带括号:', fn)
print ('2.带括号:', fn(3,2))
# 运行结果
# 1.不带括号:
# 2.带括号: 5
文档字符串doc str: 在定义函数的时候,可以在函数内部编写函数的说明文档,有助于他人或者之后使用的时候快速了解函数功能及用法。这个说明文档即文档字符串。
#【文档字符串示例】
def sum_num (a, b):
'''
这是一个文档字符串示例
本函数的作用是:求两个数值之和
本函数的参数包括:
a: 作用,类型, 默认值, blahblahblah
b: 作用,类型, 默认值, blahblahblah
'''
c = a+b
return c
help(sum_num)
# 运行结果
# Help on function sum_num in module __main__:
# sum_num(a, b)
# 这是一个文档字符串示例
# 本函数的作用是:求两个数值之和
# 本函数的参数包括:
# a: 作用,类型, 默认值, blahblahblah
# b: 作用,类型, 默认值, blahblahblah
#【在定义函数时添加描述的示例】
def sum_num (a: int, b: int) -> int:
c = a+b
return c
print (sum_num(1,2))
help(sum_num)
# 运行结果
# 3
# Help on function sum_num in module __main__:
# sum_num(a: int, b: int) -> int
# 对函数的描述不限制可传入的实参类型,不限制返回值类型
print (sum_num('a','b'))
# 运行结果
# ab
#【在函数上添加描述和默认值的示例】
def sum_num (a: int, b: int = 0) -> int:
c = a+b
return c
print (sum_num(1))
# 运行结果
# 1
作用域:变量生效的区域。
python里有两种作用域:全局作用域,函数作用域(也称为局部作用域)
全局作用域:
函数作用域:
#【全局变量vs局部变量】
b = '你好' #b属于全局变量,可以在程序中任意位置访问
def fx():
a = 'hello' #a属于局部变量,只能在函数内部访问
print ('这里是函数内部,a =', a)
print ('这里是函数内部,b =', b)
fx()
# 运行结果
# 这里是函数内部,a = hello
# 这里是函数内部,b = 你好
print ('这里是函数外部,b =', b)
print ('这里是函数外部,a =', a)
# 运行结果 在函数外部调用a时,系统报错
# 这里是函数外部,b = 你好
# NameError: name 'a' is not defined
调用变量的时候,会优先在当前作用域查找变量。如果找到,则使用;如果没找到,则向上一级作用域寻找。
#【局部变量的调用】
def fx():
a = 'hello' #定义局部变量
def fn():
print ('这里是fn内部, a =', a) #在函数fn内部访问变量a
fn() #调用函数fn
fx() #调用函数fx
# 运行结果
# 这里是fn内部, a = hello
#【优先调用当前作用域的变量】
def fx():
a = 'hello' #定义一个局部变量a
def fn():
a = '你好' #在fn内部再定义一个局部变量a
print ('这里是fn内部, a =', a) #在函数fn内部访问变量a
fn() #调用函数fn
fx() #调用函数fx
# 运行结果
# 这里是fn内部, a = 你好
函数内部对变量的赋值,默认为对局部变量的赋值,不影响函数外部的变量值。
如果想在函数内部对全局变量的值进行修改,需要使用global关键字进行声明,将函数内部的局部变量变为全局变量。
#【global的使用-局部变量变为全局变量】
# 1. 不使用global
a = 'hello' #全局变量a
def fx():
a = '你好' #局部变量a
print ('函数内部的a =', a)
fx()
print ('函数外部的a =', a)
# 运行结果 函数内部对a的修改没有影响全局变量a的值
# 函数内部的a = 你好
# 函数外部的a = hello
# 2. 使用global
a = 'hello' #全局变量a
def fx():
global a #声明函数内部的a为全局变量a
a = '你好' #对全局变量a重新赋值
print ('函数内部的a =', a)
fx()
print ('函数外部的a =', a)
# 运行结果 在函数内部对之前定义的全局变量a的值进行了修改
# 函数内部的a = 你好
# 函数外部的a = 你好
#【全局变量出现顺序】
def fx():
global a #声明函数内部的a为全局变量a
a = '你好' #给全局变量a赋值
print ('函数内部的a =', a)
fx()
a = 'hello' #给全局变量a重新赋值
print ('函数外部的a =', a)
# 运行结果 先在函数内部创建全局变量a,然后函数外部重新赋值
# 函数内部的a = 你好
# 函数外部的a = hello
闭包:函数式编程的一种语法结构,一种特殊的内嵌函数。
#【闭包作用域和变量引用】
def out_f(x):
def inner_f(y):
return x*y
return inner_f
# 调用外部函数,将内部函数作为返回值赋给i
i = out_f(6)
print (type(i))
# 调用内部函数
result = i(5)
print (result)
# 运行结果
#
# 30
如果要修改闭包作用域中的变量,需使用 nonlocal 关键字,声明该变量为外层函数的非全局变量
#【nonlocal关键字使用】
# 使用了nonlocal关键字
def outer():
num = 'hello'
print('outer num is', num,'and its id is', id(num))
def inner():
nonlocal num #声明nonlocal关键字
num = '你好'
print ('inner num is', num,'and its id is', id(num))
inner() #在外部函数调用内部函数
print ('outer num after inner is', num,'and its id is', id(num))
outer()
# 运行结果(内外部函数的num变量均指向了同一个对象)
# outer num is hello and its id is 2541908695664
# inner num is 你好 and its id is 2541909346224
# outer num after inner is 你好 and its id is 2541909346224
#【nonlocal关键字使用】
# 不使用nonlocal关键字
def outer():
num = 'hello'
print('outer num is', num,'and its id is', id(num))
def inner():
num = '你好'
print ('inner num is', num,'and its id is', id(num))
inner() #在外部函数调用内部函数
print ('outer num after inner is', num,'and its id is', id(num))
outer()
# 运行结果 (内外部函数的num变量指向了不同的对象)
# num in outer is hello and its id is 2541908695664
# num in inner is 你好 and its id is 2541909346032
# num in outer is hello and its id is 2541908695664
命名空间namespace():变量存储的位置。
locals():用于获取当前作用域的命名空间。在全局作用域调用,获取全局命名空间;在函数作用域调用,获取函数的命名空间
#【在不同作用与调用用locals】
a = 'hello'
print('当前作用域的命名空间: ', locals())
print(type(locals()))
# 运行结果
# 当前作用域的命名空间: {'__name__': '__main__', '__doc__': 'Automatically created module for IPython interactive environment', '__package__': None, '__loader__': None, '__spec__': None, '__builtin__': , ......此处省略一堆其他内置变量......, 'a': 'Strangers'}
#
def fx():
a = '你好'
print ('在fx函数获取当前作用域的命名空间', locals())
def fx_1():
a = 'hello'
print ('在fx_1函数获取当前作用域的命名空间', locals())
fx()
fx_1()
# 运行结果
# 在fx函数获取当前作用域的命名空间 {'a': '你好'}
# 在fx_1函数获取当前作用域的命名空间 {'a': 'hello'}
globals():用于在任意位置获取全局作用域的命名空间。用法与locals()类似。
递归:简单来说,即自己引用自己,就像在一个前后都是镜子的房间,可以无限看到自己。
递归函数:在函数内部调用自身函数,例如在函数fx()中调用fx。利用递归函数,将需要解决的大问题一层层分解为小问题,直到小问题不能再分解为更小的问题。
递归函数的两个要件:
#【创建递归函数】
# 创建一个函数,实现任意数的阶乘
# 将问题分解:
# 10!= 10*9!
# 9! = 9*8!
# 8! = 8*7!
# ......
# 2! = 2*1!
# 1! = 1 不能再分解,所以这就是基线条件。
def factorial(n: int):
#说明基线条件
if n == 1:
return 1
# 递归条件,不断对阶乘问题进行拆分
return n*factorial(n-1)
factorial(15)
# 运行结果
# 1307674368000
#【利用递归函数计算斐波那契数列】
def recur_fibo(n):
if n <= 1:
return n
return recur_fibo(n - 1) + recur_fibo(n - 2)
# 获得第n位斐波那契数
recur_fibo(10)
# Out: 55
#打印整个数列
lst = []
for k in range(11):
lst.appen(recur_fibo(k))
print (lst)
# 运行结果
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
高阶函数:接收至少一个函数作为参数,或者返回值是函数。
当使用函数作为参数时,其实是将指定的代码传递进目标函数。例如,将函数x作为函数y的参数,其实是将函数x的代码传递进了函数y。
#【示例】接收函数作为参数的函数
#定义一个函数sub_list,该函数从传入的列表参数lst中选取符合函数参数func要求的元素并保存到新列表中,返回新列表
def sub_list(func, lst):
new_list = []
for n in lst:
if func(n):
new_list.append(n)
return new_list
#定义一个函数fn2,该函数用于检查一个任意的数字是否为偶数
def fn2(num):
if num % 2 == 0:
return True
return False
lst = [1,2,3,4,5,6,7,8,9]
#将fn2作为参数传递进sub_list,创建了从指定列表中获取偶数的函数
print (sub_list(fn2, lst))
#运行结果
#[2,4,6,8]
上述示例中的功能可以用**python内置的函数filter()**实现。
#【filter()函数使用】
#定义一个函数fn2,该函数用于检查一个任意的数字是否为偶数
def fn2(num):
if num % 2 == 0:
return True
return False
lst = [1,2,3,4,5,6,7,8,9]
print (filter(fn2,lst))
# 运行结果 因为返回值是一个可迭代的数据结构,直接打印不出里面的元素,打印的是这个结构所在的位置。
#
print (list(filter(fn2, lst)))
# 运行结果 将返回的结果转换成list,可以打印出元素
# [2,4,6,8]
注意!!将函数作为参数时,不要在函数名后面带括号。函数名后面加括号,代表调用函数,得到的是函数返回值。
sort()方法:可以使用key关键字参数,该关键字参数需要一个函数作为参数,通过比较函数参数的返回值,进行多种排序,例如按字符串长度排序等。
#【sort()+key关键字参数-示例】
# 将以下列表中的字符串按照字符长度从短到长排序
strs = ['aaa', 'b','ccccccc','dddd']
strs.sort(key=len)
print(strs)
sorted()函数:可以对任意序列进行排序,排序原理与sort()基本一致,但是sorted()函数将排序后的结果作为一个新的对象返回,不改变被排序的原序列,此点与sort()不同。
#【sorted()使用示例】
lst = ['aaa', 'b','ccccccc','dddd']
print('排序前:', list(lst))
# 对lst按照字符串长度进行排序
new_lst = sorted(lst, key = len)
print('排序后:', list(lst))
print(new_lst)
# 运行结果
# 排序前: ['aaa', 'b', 'ccccccc', 'dddd']
# 排序后: ['aaa', 'b', 'ccccccc', 'dddd']
# ['b', 'aaa', 'dddd', 'ccccccc']
将函数作为返回值的这种高阶函数,也称为闭包。
【闭包示例】
#【闭包示例】
def fn():
a = "I'm from inside"
def inner():
print ('test:', a)
return inner
r = fn()
# r是调用fn后返回的函数inner。
# 由于inner这个函数是在函数内部定义的,不是全局函数,
# 因此通过inner函数总可以访问到fn函数内部定义的变量
r()
# 运行结果
# test: I'm from inside
【创建一个求平均值的函数】
#【创建一个求平均值的函数】
def make_averager(): #该函数为求平均函数的外套函数,用于将全局变量num变成函数内部的变量,以防程序其他部位意外访问该变量导致计算错误
nums = [] #创建一个空列表变量nums,该列表变量在函数内定义,可以有效防止函数外部意外访问或者覆盖该变量
def averager(n): #该函数为求平均的函数
nums.append(n)
return sum(nums)/len(nums)
return averager
averager = make_averager() #将内部函数averager赋值给变量averager.
print(averager(10))
# 运行结果
# 10.0
print(averager(20))
# 运行结果
# 15.0
print(averager(30))
# 运行结果
# 20.0
print (nums) #尝试调用函数内部的nums,返回错误“无法找到定义的nums变量”,说明外部是无法调用修改函数内部的变量
# 运行结果
# NameError: name 'nums' is not defined
nums = [] #外部重新定义nums,并不影响函数内部的nums。
# 但是如果去掉外层函数make_averager(),则此处定义nums会覆盖之前定义的nums)
print(averager(30))
# 运行结果
# 22.5
lambda表达式:函数创建的一种简化方法,主要用于创建一些简单的函数。
【lambda函数表达式示例】
#【lambd函数表达式与def创建函数功能一样,示例】
def sumsum(a,b):
return a+b
lambda a,b : a+b
# 运行结果
# (a, b)>
print (sumsum(1,3))
# 运行结果
# 4
x = lambda a,b :a+b
print (x(1,3))
# 运行结果
# 4
【lambda表达式的调用】
a = 1
b = 3
# 将lambda表达式赋值给一个变量(一般不这么用)
x = lambda a,b :a+b
print (x)
# 运行结果
# at 0x0000028A35917EE0>
# 因为x为函数, 打印时没有调用函数(函数x后面没有括号,没有传递参数),所以打印的是函数本身信息。
# 可以看到,打印出的函数信息里,该函数没有函数名,函数名的地方显示的是, 所以也称为匿名函数
print (x(a,b))
# 运行结果
# 4
# 如果想直接调用lambd表达式,可以在表达式后面加括号并传入参数(一般也不这么用)
# 注意,需要先将整个lambda表达式用括号包围起来,然后再在末尾加括号和参数,不能直接加括号,否则末尾的括号会直接作用在最后一个元素b上。
lambda a,b :a+b(2,10)
# 运行结果
# (a, b)>
(lambda a,b :a+b)(2,10)
# 运行结果
# 12
lambda函数式可用于作为一次性使用的函数参数,实现函数的定制化,但lambda只能用于写简单的函数,写不了for循环、while循环等
#【用lambda函数式作为函数的参数】
lst = [1,2,3,4,5,6,7,8,9]
r = filter(lambda i: i%2==0, lst)
#上式等同于之前高阶函数中的filter()函数示例,即创建了一个可从指定列表lst中获取偶数的函数。
print(list(r))
# 运行结果
# [2,4,6,8]
lambda函数式还可以和map()函数组合使用
map()函数:可对可迭代对象中的所有元素做指定的操作,然后将这些元素添加到新的对象中。
【map()和lambda表达式示例】
#【map函数与lambda表达式的结合】
#现想将特定列表lst中的所有元素都乘以2
lst = [1,2,3,4,5]
m = map(lambda i : i*2, lst)
print(list(m))
# 运行结果
# [2,4,6,8,10]
装饰器,用于在不修改原函数的基础上,对原函数功能进行扩展。通过这种方式扩展函数功能,可用于避免以下问题:
在定义函数时,通过@装饰器,来使用指定的装饰器,装饰当前函数(扩展当前函数功能)。
一个函数可以指定多个装饰器,按照从内向外的顺序被装饰
【装饰器使用实例】
#【装饰器使用实例】
# 第一个装饰器
# 将需要扩展的函数作为一个参数传入装饰器函数,返回一个扩展后的新函数
def begin_end(old):
'''
这是一个用于扩展函数功能的装饰器。该装饰器可以在执行其他函数前先打印“开始执行”,在函数执行结束后戴莹“结束执行”。
参数:old 要扩展的函数
'''
#创建一个新函数。
#由于要扩展的函数可能需要传递参数,但传递参数的个数和类型未知,因此在创建函数的时候需要对参数进行打包,使用不定长位置参数和不定长关键词参数囊括所有可能出现的参数。
def new_function(*args, **kwargs):
print("开始执行...")
#调用被扩展的函数
result = old(*args, **kwargs) #对传递进来的参数进行解包
print("结束执行...")
#返回函数的执行结果
return result
#返回扩展后的新函数
return new_function
#第二个装饰器
def fn3(old):
def new_funtion(*args, *kwargs):
print('fn3装饰器开始执行')
result = old(*args, **kwargs)
print('fn3装饰器结束执行')
return result
return new_function
x = lambda a,b: a+b
f = begin_end(x) #f是原函数x扩展后得到的新函数
r = f(1,3)
print(r)
# 运行结果
# 开始执行...
# 结束执行...
# 4
@begin_end #调用装饰器,装饰say_hello函数
def say_hello():
print('hello')
say_hello() #使用装饰器后,原函数say_hello本体直接被扩展。
# 运行结果
# 开始执行...
# hello
# 结束执行...
@begin_end
@fn3
def say_bye():
print('byebe')
say_bye()
# 运行结果
# 开始执行...
# fn3装饰器开始执行
# byebye
# fn3装饰器结束执行
# 结束执行...