目录
五、函数
1、函数的定义和调用
1、函数的定义
2、函数的调用
2、函数参数
1、参数传递
2、参数类型
3、参数传递的序列解包
3、特殊函数
1、匿名函数
2、嵌套函数
3、递归函数
4、变量作用域
1、变量类型:
2、全局变量与局部变量
3、关键字global,nonlocal
六、面向对象程序设计
1、类与对象
2、成员
1、公有成员
2、私有成员
3、方法
1、方法的类型
2、特殊方法
4、封装、继承与多态
1、封装
2、继承
3、多态
5、抽象类与抽象方法
1、抽象类
2、抽象方法
(1)函数关键字:关键字必须以def开头,后接函数名与()
(2)函数名:遵守标识符命名规则。
(3)返回值:使用“return[表达式]”结束函数,返回一个值给调用方。如果没有返回值,默认返回None。
(4)函数体:函数体可以实现函数功能,也可以为pass语句,表示什么工作都不做,比如抽象类中的抽象函数。
(5)参数:参数是可选的,可以有,可以没有,可以有多个参数。
def sayhello():
print("Hello,Python")
sayhello()
# Hello,Python
def none():
pass
# 无函数体
def hi(name):
print("hi,",name)
hi("李华")
# hi, 李华
(1)函数定义完成之后,可以在程序中调用。函数调用时需要指出函数名称,并传入相应的参数。
(2)函数调用时传入的参数为实际参数,简称为实参。
(3)在默认情况下,函数调用时传入的实参个数,顺序等必须和函数定义时形参个数,顺序一致。
def sayhello():
print("Hello,Python")
sayhello() # 该行调用了函数 sayhello()
# Hello,Python
Python中的对象可分为不可变对象(数字、字符串、元组等)和可变对象(列表、字典、集合等)
(1)当实参为不可变对象时。函数调用是将实参的值复制一份给形参,在函数调用中修改形参时,不会改变函数外的实参。
a = 1
b = 2
def swap(m,n):
print(f"交换前m = {m},n = {n}")
x = m
m = n
n = x
print(f"交换后m = {m},n = {n}")
swap(a,b)
print(f"函数调用后:a = {a},b = {b}")
# 交换前m = 1,n = 2
# 交换后m = 2,n = 1
# 函数调用后:a = 1,b = 2
(2)当实参为可变对象时。函数调用是将实参引用给形参,形参改变时,函数外实参也会跟着改变。
a = [1,2]
def swap(m):
print(f"添加元素前:m = {m}")
m.append(4)
print(f"添加元素后:m = {m}")
swap(a)
print(f"函数调用后:a = {a}")
# 添加元素前:m = [1, 2]
# 添加元素后:m = [1, 2, 4]
# 函数调用后:a = [1, 2, 4]
(1)位置参数:调用函数时根据函数定义的参数位置来传递参数
def test(name,age,gender):
print(f"名字{name},年龄{age},性别{gender}")
test("TOM",20,"男")
输出:
名字TOM,年龄20,性别男
注意:传递参数和定义参数的顺序与个数必须一致。
(2)关键字参数:函数调用时通过”键=值“形式传递参数
作用:可以让函数更加清晰、容易使用,同时也清除了参数的顺序需求
def test(name,age,gender):
print(f"姓名{name},年龄{age},性别{gender}")
# 关键字传递参数
test(name = "小明",age = 20,gender = "男")
# 可以不按固定顺序
test(gender = "男",age = 20,name = "小明")
# 可以和位置参数混用,位置参数必须在前,且匹配参数顺序
test("小明",gender = "男",age = 20)
输出:
姓名小明,年龄20,性别男
姓名小明,年龄20,性别男
姓名小明,年龄20,性别男
(3)缺省参数:缺省参数也叫默认参数,用于定义函数,为参数提供默认值,调用函数时课不传该默认参数的值
(注意:所有位置参数,必须出现在默认参数前,包括函数定义和调用)
作用:当调用函数没有调用参数,就会使用默认是缺省参数对应的值
def test(name,age,gender = "男"):
print(f"姓名{name},年龄{age},性别{gender}")
test("TOM",20)
test("Rose",20,"女")
输出:
姓名TOM,年龄20,性别男
姓名Rose,年龄20,性别女
注意:函数调用时,如果为缺省参数传值,则修改默认参数值,否则就是用这个默认值
(4)不定长参数:不定长参数也叫可变参数,用于不确定调用的时候会传递多少个参数(不传参也可以)的场景
作用:当调用函数时不确定参数个数时,可以使用不定长参数
不定长参数类型:
1.位置传递
def test(*args):
print(f"args = {args}")
test("TOM")
test("TOM",18)
输出:
args = ('TOM',)
args = ('TOM', 18)
2.关键字传递
def test(**kwargs):
print(f"kwargs = {kwargs}")
test(name = "TOM",age = 18,id = 110)
# kwargs = {'name': 'TOM', 'age': 18, 'id': 110}
注意:参数是“键=值”形式的情况下,所有“键=值”都会被kwargs接收,同时会根据“键=值”组成字典
参数传递的序列解包针对的是实参,有*和**两种形式。实参前加入了*或**后会将列表、元组、字典等迭代对象中的元素分别传递给形参中的多个变量。
def test(a,b,c):
print(f"a = {a},b = {b},c = {c}")
test(*[1,2,3])
# a = 1,b = 2,c = 3
test(**{'a' : 1,'b' : 2,'c' : 3 } )
# a = 1,b = 2,c = 3
test(**{'c' : 1, 'b' : 2, 'a' : 3})
# a = 3,b = 2,c = 1
lambda匿名函数:
有名函数,可以基于名称重复使用。
无名称的匿名函数,只能临时使用一次。
语法:
lambda 传入参数:函数体(一行代码)
lambda是关键字,表示定义匿名函数。
传入参数表示匿名函数的形式参数,如x,y表示接收两个形式参数
def test1(t2):
result = t2(1,2)
print(result)
test1(lambda x,y : x + y)
# 3
使用def和使用lanbda,定义函数功能完全一致,只是lambda关键字定义的函数是匿名的,无法二次使用
函数作参数传递:
def test1(t2):
result = t2(1,2)
print(result)
def test2(x,y):
return x + y
test1(test2)
# 3
嵌套函数是指在一个函数(称为外函数)内部调用另一个函数(称为内函数)。嵌套函数中的内函数只能在外函数中调用,不能在外函数外面直接调用。
def outer():
def inner():
print("我是内函数")
print("我是外函数")
inner()
outer()
# 我是外函数
# 我是内函数
inner()
# 无法在外函数外部调用内函数,会报错
如果一个函数在函数体中直接或间接调用自身,那么这个函数就被称为递归函数。
例题:使用递归函数,求斐波那契函数前20项之和。
def fib(n):
if n == 1 or n == 2 :
return 1
else:
return fib(n - 1) + fib(n - 2)
sum = 0
for i in range(1,21):
sum = sum + fib(i)
print("斐波那契函数前20项之和:",sum)
# 斐波那契函数前20项之和: 17710
(1)局部变量与局部作用域:局部变量是定义在函数内部的变量,作用域是从函数定义的位置知道该函数结束。当函数被调用时变量被定义,当函数别表用结束时,局部变量消失。
(2)全局变量与全局作用域:全局变量是定义在模块函数外的变量。全局作用域仅限于单个模块文件内。
(3)闭包变量与闭包作用域:闭包变量被定义在嵌套函数的内函数外部并且在外函数内部。闭包作用域为定义该变量的位置开始的整个函数内。
(4)内建变量与内建作用域*:系统内固定模块里定义的变量,一般为预定义在内建模块内的变量。
局部变量只能在其声明的函数内部使用,全局变量可以在整个模块中使用。
a = 100
def func():
b = 1000
print("a = ",a)
print("b = ",b)
func()
# a = 100
# b = 1000
print("a = ",a)
# a = 100
print("b = ",b)
# 局部变量不能在定义该变量的外部使用,会报错
global:可以将变量修改为全局变量。
def func():
global a
a = "我变为了全局变量"
print(f"a = {a}")
func()
# a = 我变为了全局变量
print(f"a = {a}")
# a = 我变为了全局变量
nonlocal:可以将变量修改为闭包变量。
def outter():
a = "111"
def inner():
nonlocal a
print(f"a = {a}")
a = "222"
print(f"a = {a}")
print(f"a = {a}")
inner()
outter()
# a = 111
# a = 111
# a = 222
class:是关键字,表示定义类
类的属性:定义在类中的变量(成员变量)
类的行为:定义在类中的方法(成员方法)
类中,不仅可以定义属性来记录数据,也可以定义函数来记录行为
def(self,形参1,形参2......):
方法体
self:关键字是成员方法定义的时候,必须填写的。
它用来表示类对象自身的意思
当我们使用类对象调用方法的时候,self会自动别python传入
在方法内部,想要访问成员变量,必须使用self
class student:
name = None
sex = None
# 可以省略
def __init__(self , name , sex):
self.name = name
self.sex = sex
print("我是构造方法")
def say_hi(self,h):
print(f"我的名字是{self.name},{h}")
# stu_1 = student()
# stu_1.name = "李华"
# stu_1.sex = "男"
# stu_1.say_hi("hi")
# 在未定义构造方法时,可以正常运行
stu_2 = student("小明","男")
# 创建对象
stu_2.say_hi("你好啊")
# 我是构造方法
# 我的名字是小明,你好啊
公有成员:不以下划线开头,在类的内部、外部都可以访问。
私有成员变量:变量名以__开头(两个下划线)
私有成员方法:方法名以__开头(两个下划线)
使用私有成员:
私有成员无法被类对象使用,但是可以被其他的成员使用。
class student:
__name = None
__age = None
def __str__(self):
return f"student类,name:{self.__name},age:{self.__age}"
def __init__(self,name,age):
self.__age = age
self.__name = name
def __self(self):
print("我是私有方法")
def print(self):
print("我是公有有方法")
self.__self();
stu1 = student("李华",19)
stu1.print()
print(stu1)
# 我是公有有方法
# 我是私有方法
# student类,name:李华,age:19
公有方法:不以下划线开头,在类的外部通过类名或对象名调用。
私有方法:方法名以__开头(两个下划线或更多下划线),在类的内部可以使用self调用,在类外不能直接调用。
__init__:构造方法
__str__:字符串方法
__lt__:小于,大于符号比较
__le__:小于等于,大于等于符号比较
__eq__:==符号比较
class student:
name = None
age = None
# 可以省略
def __init__(self , name , age):
self.name = name
self.age = age
print("我是构造方法")
def __str__(self):
return f"student类对象,name:{self.name},age:{self.age}"
def __lt__(self, other):
return self.age < other.age
def __le__(self,other):
return self.age <= other.age
def __eq__(self,other):
return self.age == other.age
stu1 = student("李华",18)
print(stu1)
print(str(stu1))
stu2 = student("小明",18)
print(f"李华年龄大于小明吗?{stu1 > stu2}")
print(f"李华年龄大于等于小明吗?{stu1 >= stu2}")
print(f"李华年龄等于小明吗?{stu1 == stu2}")
# 我是构造方法
# student类对象,name:李华,age:18
# student类对象,name:李华,age:18
# 我是构造方法
# 李华年龄大于小明吗?False
# 李华年龄大于等于小明吗?True
# 李华年龄等于小明吗?True
封装表示的是,将现实世界事物的:
属性
行为
封装到类中,描述为:
成员变量
成员方法
从而完成程序对现实世界事物的描述
例如:私有成员,私有方法等
单继承:一个子类继承一个父类
多继承:一个子类继承多个父类
多继承中,如果有重名的属性或者方法,先继承的优先级高于后继承的
class a:
pass
class c:
pass
class b(a,c):
pass
# 定义的b类继承a,c类。
pass是占位语句,用来保证函数(方法)或类定义的完整性,表示无内容,空的意思。
一旦复写父类成员,那么类对象调用成员的时候,就会调用复写后的新成员
如果需要使用被复写的父类成员,需要用特殊的调用方式:
方式1:
使用父类成员变量:父类名.成员变量
使用父类成员方法:父类名.成员方法(self)
方式2:
使用父类成员变量:super().成员变量
使用父类成员方法:super().成员方法()
class a:
name = "我是父类成员"
class b(a):
name = "我是子类成员"
print(f"子类name = {name}")
print(f"父类name = {a.name}")
# 子类name = 我是子类成员
# 父类name = 我是父类成员
注意:
只可以在子类内部调用父类的同名成员,子类的实体类对象调用默认是调用子类复写的相关内容。
什么是多态:
多态是指,同一行为,使用不同的对象获得不同状态。
如:定义函数(方法),通过类型注解声明需要父类对象,实际传入子类对象进行工作,从而获得不同工作状态。
class Animal:
def speak(self):
pass
class Dog(Animal):
def speak(self):
print("汪汪汪")
class Cat(Animal):
def speak(self):
print("喵喵喵")
def make_noise(animal:Animal):
animal.speak()
dog = Dog()
cat = Cat()
make_noise(cat)
# 喵喵喵
make_noise(dog)
# 汪汪汪
多态常作用在继承关系上,比如:
(1)函数(方法)形参声名接受父类对象
(2)实际传入父类的子类对象进行工作
即:
(1)以父类做定义声明
(2)以子类做实际工作
(3)用以获得同一行为,不同状态
抽象类的作用:
多用于顶层设计(设计标准),以便子类做具体实现。
也是对子类的一种软性约束,要求子类必须复写(实现)父类的一些方法,并配合多态使用,获得不同的工作状态。
抽象类(接口)
class Animal:
def speak(self):
pass
class Dog(Animal):
def speak(self):
print("汪汪汪")
class Cat(Animal):
def speak(self):
print("喵喵喵")
父类Animal的speak方法,是空实现
这种设计的含义是:
(1)父类来确定有哪些方法
(2)具体的方法实现,由子类自行决定
这种写法就叫做抽象类(也可以称之为接口)
抽象类:含有抽象方法的类,称为抽象类
抽象方法:方法体是空实现的(pass)称之为抽象方法。