Python函数&面向对象编程-总结-03

本文详细介绍了Python编程中的函数使用,包括定义、参数、全局与局部变量、模块管理以及高阶函数。此外,还探讨了面向对象编程的基本概念,如类、对象、继承和多态,并通过实例展示了如何创建和使用类。最后,提到了Python的魔术方法、静态方法和类方法,以及继承和多态的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 函数

1.1 定义函数

 上图中的参数即为自变量,结果为因变量,也就是说输入因变量,返回自变量。

写程序的终极原则:高内聚低耦合---> high cohesion loe coupling

设计函数最为重要的原则:单一职责原则(一个函数只要干好一件事)--->高度内聚

1.2 函数的参数

1.2.1 默认参数

def add(a=0, b=0, c=0):
    """三个数相加求和"""
    return a + b + c

# 调用add函数,没有传入参数,那么a、b、c都使用默认值0
print(add())         # 0
# 调用add函数,传入三个参数,分别赋值给a、b、c三个变量
print(add(1, 2, 3))  # 6

若函数中没有return语句,那么函数默认返回代表空值的None。 需要注意的是带默认值的参数必须放在不带默认值的参数之后,否则将产生SyntaxError错误。

1.2.2 可变参数

# 用星号表达式来表示args可以接收0个或任意多个参数
def add(*args):
    total = 0
    # 可变参数可以放在for循环中取出每个参数的值
    for val in args:
        total += val
    return total

# 在调用add函数时可以传入0个或任意多个参数
print(add())               # 0     
print(add(1))              # 1
print(add(1, 3, 5, 7 ))    # 16

1.3 全局变量和局部变量

Python程序中搜索一个变量是按照 LEGB 顺序进行搜索的

Local(局部作用域) ---> Embeded(嵌套作用域) ---> Global(全局作用域) ---> Built-in(内置作用域) ---> 如果内置作用域都没有发现会报错:NameError: name ... not defined

global ---> 声明使用全局变量或者定义一个局部变量将其放到全局作用域

nonlocal ---> 声明使用嵌套作用域的变量(不使用局部变量)

x = 100

def foo():
    # 如果我不想在函数foo中定义局部变量x,想直接使用全局变量x
    # global x
    x = 200

    def bar():
        # 如果我不想在函数bar中定义局部变量x,想直接使用嵌套作用域中的x
        # nonlocal x
        x = 300
        print(x)

    bar()
    print(x)

foo()
print(x)

1.4 用模块管理函数 ( 解决命名冲突 )

做工程化项目开发时,如果项目中的代码文件非常多,我们可以使用"包"(package)来管理"模块"(module), 再通过模块来管理函数,包其实就是一个文件夹,而模块就是一个Python文件,通过这种方式就可以很好的解决 大型项目团队开发中经常遇到的命名冲突的问题。 Python中的from、import、as关键字就是专门用来处理包和模块导入的操作的。

方法一:完全限定名(qualified name),通过 import 导入模块,然后再通过 " 模块名.函数名 " 的方式调用函数;

方法二:直接从模块中导入函数 ---> "from 模块 import 函数" ---> 直接通过函数名调用函数

                import 导入函数、模块时,可以使用as关键字(alias)进行别名

# module1.py
def foo():
    print('hello, world!')

# module2.py
def bar():
    print('goodbye, world!')

方法一:

# test.py
import module1
import module2

# 用“模块名.函数名”的方式(完全限定名)调用函数,
module1.foo()    # hello, world!
module2.bar()    # goodbye, world!

方法二:

# test.py
import module1 as f1
import module2 as f2

f1.foo()    # hello, world!
f2.bar()    # goodbye, world!

1.5 关键字参数

在没有特殊处理的情况下,函数的参数都是位置参数 。

# 定义三角形
def is_triangle(a, b, c):
    print(f'a = {a}, b = {b}, c = {c}')
    return a + b > c and b + c > a and a + c > b


# 按位置对号入座
print(is_triangle(1, 2, 3))
# 调用函数也可按“参数名=参数值”的形式,顺序不要求
print(is_triangle(a=1, b=2, c=3))
print(is_triangle(c=3, a=1, b=2))

定义函数时,写在 * 前面的参数称为位置参数,调用函数传递参数时,只需要对号入座 ;写在 * 后面的参数称为命名关键字参数,调用函数传递参数时,必须要写成"参数名=参数值"的形式。关键字参数一定是在位置参数的后面!!!

在设计函数的时候,函数的参数个数是暂时无法确定时:

*args ---> 可变参数--->可以接收零个或任意多个位置参数 ---> 将所有位置参数打包成一个元组 

 **kwargs ---> 可以接收零个或任意多个关键字参数 ---> 将所有关键字参数打包为字典

def add(*args, **kwargs):

    total = 0
    for arg in args:
        if type(arg) in (int, float):
            total += arg
    for value in kwargs.values():
        if type(value) in (int, float):
            total += value
    return total

print(add())                                     # 0
print(add(1, ))                                  # 1
print(add(1, 2, c=3, b=2.5, a=1, d='hello'))     # 9.5
print(add(1, '2', 3))                            # 4
print(add(1, 2, 'hello', 4))                     # 7

1.6 高阶函数

Python中的函数是一等函数(一等公民):

1. 函数可以作为函数的参数

2. 函数可以作为函数的返回值

3. 函数可以赋值给变量

如果把函数作为函数的参数或者返回值,这种玩法通常称之为高阶函数。 通常使用高阶函数可以实现对原有函数的解耦合操作。

def calc(*args, op, init_value=0, **kwargs):    # op 定义的运算
    total = init_value
    for arg in args:
        if type(arg) in (int, float):
            total = op(total, arg)
    for value in kwargs.values():
        if type(value) in (int, float):
            total = op(total, value)
    return total

def add(x, y):
    return x + y

def mul(x, y):
    return x * y

print(calc(11, 22, 33, 44, op=add))                  # 110
print(calc(11, 22, 33, 44, init_value=1, op=mul))    # 351384

Lambda函数 ---> 没有名字而且一句话就能写完的函数,唯一的表达式就是函数的返回值。下面由Lambda改写后的代码:

def calc(*args, op, init_value=0, **kwargs):
    total = init_value
    for arg in args:
        if type(arg) in (int, float):
            total = op(total, arg)
    for value in kwargs.values():
        if type(value) in (int, float):
            total = op(total, value)
    return total

print(calc(11, 22, 33, 44, op=lambda x, y: x + y))                  # 110
print(calc(11, 22, 33, 44, init_value=1, op=lambda x, y: x * y))    # 351384

1.7 递归调用

函数如果直接或间接的调用了自身,这种调用称为递归调用。 不管函数是调用别的函数,还是调用自身,一定要做到快速收敛。 在比较有限的调用次数内能够结束,而不是无限制的调用函数。 如果一个通常指递归调用的函数不能够快速收敛,那么就很有可能产生下面的错误 :RecursionError: maximum recursion depth exceeded  ---->  最终导致程序的崩溃。

递归函数的两个要点

1. 递归公式(第n次跟第n-1次的关系)

2. 收敛条件(什么时候停止递归调用)

实例:利用递归求 n!

问题分析:根据阶乘的定义  n!  =  n * (n - 1) * (n - 2) * ... * 2 * 1  ---->   n! = n * (n - 1)!

def fac(num: int) -> int:
    """递归求阶乘"""
    if num == 0:
        return 1
    return num * fac(num - 1)

if __name__ == '__main__':
    # 计算思路:
    # return 5 * fac(4)
    # return 4 * fac(3)
    # return 3 * fac(2)
    # return 2 * fac(1)
    # return 1 * fac(0)
    # return 1
    print(fac(5))

2 面向对象编程

2.1 相关基本概念

所谓编程范式即程序设计的方法学 ,分为面向对象编程 、函数式编程、指令式编程等。

面向对象编程:把一组数据和处理数据的方法组成对象,把行为相同的对象归纳为,通过封装隐藏对象的内部细节,通过继承实现类的特化和泛化,通过多态实现基于对象类型的动态分派。

对象:数据 + 函数(方法)---> 对象将数据和操作数据的函数从逻辑上变成了一个整体。

~ 一切皆为对象

~ 对象都有属性和行为

~ 每个对象都是独一无二的

~ 对象一定属于某个类

:将有共同特征(静态特征和动态特征)的对象的共同特征抽取出来之后得到的一个抽象概念。

面向对象编程的四大支柱:

~ 抽象(abstraction):提取共性(定义类就是一个抽象过程,需要做数据抽象和行为抽象)。

~ 封装(encapsulation):把数据和操作数据的函数从逻辑上组装成一个整体(对象)。

             ---> 隐藏实现细节,暴露简单的调用接口。

~ 继承(inheritance):扩展已有的类创建新类,实现对已有类的代码复用。

~ 多态(polymorphism):给不同的对象发出同样的消息,不同的对象执行了不同的行为。

            ---> 方法重写:子类对父类已有的方法,重新给出自己的实现版本

在面向对象编程的世界中,一切皆为对象对象都有属性和行为每个对象都是独一无二的,而且对象一定属于某个类类是一个抽象的概念,对象是一个具体的概念。比如我们经常说的人类,这是一个抽象概念,而我们每个人就是人类的这个抽象概念下的具体的实实在在的存在,也就是一个对象。简单的说,类是对象的蓝图(模板),有了类才能够创建出这种类型的对象。

2.2 面向对象编程的步骤

1.定义类 ---> 驼峰命名法(每个单词首字母大写)

~ 数据抽象:找到和对象相关的静态特征(属性)--->找名词

~ 行为抽象:找到和对象相关的动态特征(方法)--->找动词

2.创建对象

3.给对象发消息

# 第一步:定义类
class Student:                       # class为关键字,后跟类名,需驼峰命名法
    """学生"""
    # 数据抽象(属性)
    # 通过初始化方法即 '__init__' 方法指定属性,同时完成对属性赋初始值的操作
    def __init__(self,name,age):     # self 它代表了接收这个消息的对象本身 
        self.name=name
        self.age=age

    # 行为抽象(方法)
    def eat(self):
        """吃饭"""
        print(f'{self.name}正在吃饭。')

    def study(self,course_name):
        """学习"""
        print(f'{self.name}正在学习{course_name}。')

    def play(self,game_name):
        """玩"""
        print(f'{self.name}正在玩{game_name}')

    def watch_av(self):
        """看视频"""
        if self.age<18:
            print(f'{self.name}未满18岁,只能看《熊出没》')
        else:
            print(f'{self.name}正在看电影')
# 第二步 创建对象 ---> 使用构造器语法 ---> 格式:类名(...,...)
stu1=Student('王大锤',15)
stu2=Student('骆',41)

# 第三步:给对象发出消息(调用对象的方法),有两种方式
# 方式一:“类.方法”调用,第一个参数是接收消息的对象即stu1,第二个参数是学习的课程名称
# Student.study(stu1,'Python程序设计')   # 王大锤正在学习Python程序设计
# 方式二:“对象.方法”调用,点前面的对象就是接收消息的对象,只需要传入第二个参数
stu1.study('Python程序设计')             # 王大锤正在学习Python程序设计
stu1.eat()                              # 王大锤正在吃饭

stu2.play('斗地主')                      # 骆正在玩斗地主
stu2.watch_av()                         # 骆正在看电影

2.3 魔术方法

在Python中,以两个下划线 '__' 开头和结尾的方法通常都是有特殊用途和意义的方法,一般称之为魔术方法魔法方法

2.3.1  __init__ ---> 初始化方法

 __init__ ---> 初始化方法,在调用构造器语法创建对象的时候会被自动调用,如上述例子在定义学生对象属性时所用。

2.3.2 __str__与__repr__魔术方法

~ __str__ ---> 获得对象的字符串表示,在调用print函数输出对象时会被自动调用

~ __repr__ ---> 获得对象的字符串表示,把对象放到容器中调用print输出时会自动调用

                 ---> representation

若在前述例子,第三步给对象发消息步骤时,直接打印:

print ( stu1 )    # < ex01.Student object at 0x0000029A05524AF0 >   返回的是地址,ex01为文件名

在前述定义类的行为抽象(方法)后面增加如下内容:

def __str__(self):
    return f'{self.name}:{self.age}'

print ( stu1 )                                                       #  王大锤:15

若是容器类型则把 str 改为 repr :

def __repr__(self):
    return f'{self.name}:{self.age}'

students = [ stu1,stu2 ]
print ( students )                                               # [ 王大锤: 16, 骆: 41]

2.3.3 __slots__魔法与动态属性

在Python中,我们可以动态为对象添加属性 。如果要限制一个类的对象只能拥有某些属性,可以在类中使用__slots__魔法属性

class Student:
    # __slots__ = ('name', 'age')   # 注释掉此行代码可增加grade属性,否则只能拥有name,age
    def __init__(self, name, age):
        self.name = name
        self.age = age

stu = Student('王大锤', 20)
# 为Student对象动态添加grade属性
stu.grade = '五年级'

此外还有 __lt__魔法, ---> 在使用  <  运算符比较两个对象大小时会自动调用。

2.4 静态方法和类方法

我们在类里面写的函数,通常称之为方法,它们基本上都是发给对象的消息。 但是有的时候,我们的消息并不想发给对象,而是希望发给这个类(类本身也是一个对象), 这个时候,我们可以使用静态方法或类方法。

静态方法 - 发给类的消息 ---> @staticmethod ---> 装饰器

类方法 - 发给类的消息 ---> @classmethod ---> 装饰器 ---> 第一个参数(cls)是接收消息的类

实例:定义描述三角形的类,提供计算周长和面积的方法

class Triangle:
    # 数据抽象(属性):
    def __init__(self, a, b, c):
        if not Triangle.is_valid(a, b, c):
            raise ValueError('无效的边长,无法构成三角形')
        self.a = a
        self.b = b
        self.c = c

    # 类方法(与下面类静态方法效果等价)
    # @classmethod
    # def is_valid(cls,a,b,c):
    #     return a + b > c and b + c > a and a + c > b
    # 静态方法
    @staticmethod
    def is_valid(a, b, c):
        return a + b > c and b + c > a and a + c > b

    def perimeter(self):
        return self.a + self.b + self.c

    def area(self):
        half = self.perimeter() / 2
        return (half * (half - self.a) * (half - self.c) * (half - self.b)) ** 0.5


if __name__ == '__main__':
    try:
        t = Triangle(3, 4, 5)
        print(t.perimeter())
        print(t.area())
    except ValueError as err:
        print(err)

 2.5 继承和多态

继承:对已有的类进行扩展创建出新的类,这个过程就叫继承。 提供继承信息的类叫做父类(超类、基类),得到继承信息的类称为子类(派生类),继承是实现代码复用的一种手段。

两个类之间的关系:

~ is - a关系:继承 ---> 从一个类派生出另一个类

a student is a person. a teacher is a person.

~ has - a关系:关联 ---> 把一个类的对象作为另外一个类的对象的属性

a person has an identity card.

a car has an engine.

~(普通)关联

~  强关联:整体和部分的关联,聚合和合成 ---> 如:扑克与牌

~ use-a关系:依赖 ---> 一个类的对象作为另外一个类的方法的参数或返回值

a person use a vehicle.

多态:

子类对父类已有的方法,重新给出自己的实现版本,这个过程叫做方法重写(override);在重写方法的过程中,不同的子类可以对父类的同一个方法给出不同的实现版本,那么该方法在运行时就会表现出多态行为

例如,定义一个学生类和一个老师类,会发现他们有大量的重复代码,而这些重复代码都是老师和学生作为人的公共属性和行为,所以在这种情况下,我们应该先定义人类,再通过继承,从人类派生出老师类和学生类。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值