Python:函数与面向对象编程总结

Posted better meˇ:)

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python:函数与面向对象编程总结相关的知识,希望对你有一定的参考价值。

函数

函数的定义

函数就是对实现某一特定功能的代码的封装。

在Python中可以使用def关键字来定义函数,函数的命名规则跟变量的命名规则是一致的。

# 定义函数:def是定义函数的关键字、fac是函数名,num是参数(自变量)
def fac(num):
    """求阶乘"""
    result = 1
    for n in range(1, num + 1):
        result *= n
    # 返回num的阶乘(因变量)
    return result

函数的参数

  • 位置参数:传入参数的时候对号入座即可;也可以通过参数名=参数值的方式传入函数所需的参数,因为指定了参数名,传入参数的顺序可以进行调整。
  • 命名关键字参数:在函数的参数列表中,写在*之后的参数,传参时,只能以参数名=参数值的方式传参。
  • 关键字参数:Python中可以通过*kwargs表达式语法传入0个或任意多个参数名=参数值形式的参数,关键字参数会将传入的带参数名的参数组装成一个字典,参数名就是字典中键值对的键,而参数值就是字典中键值对的值。
  • 可变参数:可以实现向一个函数中传0个或任意多个参数,Python中可以通过*args表达式语法来支持可变参数。(不能接收带参数名的参数

注意:不带参数名的参数(位置参数)必须出现在带参数名的参数(关键字参数)之前

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
# 传入0个参数
print(add())
# 只传入可变参数
print(add(1, 2, 3))
# 只传入关键字参数
print(add(a=1, b=2, c=3))
# 传入位置参数和关键字参数,关键字参数在*args的‘*’之后,因此也是命名关键字参数
print(add(1, 2, c=3, d=4))

用模块管理函数

Python中每个文件就代表了一个模块(module),在不同模块可以有同名的函数,在使用函数时,通过import关键字导入指定的模块,再使用完全限定名的调用方式就可以区分是哪个模块中的函数。

高阶函数

函数的参数和返回值可以是任意类型的对象,这就意味着函数本身也可以作为函数的参数或返回值,这就是所谓的高阶函数

# 中间两个参数是命名关键字参数,必须带上参数名
def calculate(*args, init_value, op, **kwargs):
    """可以做任意二元运算的函数

    :param init_value:初始值
    :param op: 一个实现二元运算(+、*)的函数
    :param args: 位置参数
    :param kwargs: 关键字参数
    :return: 运算结果
    """
    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(a, b):
    return a + b


def mul(a, b):
    return a * b


print(calculate(1, 2, 30, init_value=0, op=add))
print(calculate(1, 2, 3, init_value=0, op=mul))
print(calculate(15, 20, init_value=100, op=add, c=5))

Lambda函数

在使用高阶函数的时候,如果作为参数或者返回值的函数本身非常简单,一行代码就能够完成,那么我们可以使用Lambda函数来表示。如上面的add函数和mul函数

# 中间两个参数是命名关键字参数,必须带上参数名
def calculate(*args, init_value, op, **kwargs):
    """可以做任意二元运算的函数

    :param init_value:初始值
    :param op: 一个实现二元运算(+、-、*、/)的函数
    :param args: 位置参数
    :param kwargs: 关键字参数
    :return: 运算结果
    """
    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

# 调用calculate函数,通过lambda函数给op参数赋值
print(calculate(1, 2, 30, init_value=0, op=lambda x, y: x + y, a=5))
print(calculate(1, 2, 3, init_value=1, op=lambda x, y: x * y))
print(calculate(15, 20, init_value=100, op=lambda x, y: x - y))

递归调用

Python中允许函数嵌套定义,也允许函数之间相互调用,而且一个函数还可以直接或间接的调用自身。

递归函数的要点:
1.递归公式:(第n次与第n-1次的关系)
2.收敛条件(什么时候停止递归调用)

例题

例题1:汉诺塔问题

a柱上有n个盘子,要把所有盘子搬到b柱上,借用c柱帮助搬运,大盘不能压在小盘上面。

def move(n, a, c, b):
    if n == 1:
        print(f'{a}->{b}')
    else:
        move(n - 1, a, b, c)
        move(1, a, c, b)
        move(n - 1, c, a, b)


if __name__ == '__main__':
    n = int(input('请输入盘子数:'))
    move(n, 'a', 'c', 'b')

例题2:爬楼梯

有一个小孩爬楼梯,一次可以爬1个台阶、2个台阶或3个台阶,问爬完10个台阶,有多少种走法?

def step(n):
    if n == 1:
        return 1
    elif n == 2:
        return 2
    elif n == 3:
        return 4
    else:
        return step(n - 1) + step(n - 2) + step(n - 3)


if __name__ == '__main__':
    print(step(10))

例题3:冒泡排序

def bubble_sort(nums, ascending=True, gt=lambda x, y: x > y):
    """冒泡排序

    :param nums: 待排序的列表
    :param ascending: 是否使用升序
    :param gt: 比较两个元素大小的函数
    :return: 排序后的列表
    """
    sort_nums = nums[:]
    for i in range(1, len(sort_nums)):
        swapped = False
        for j in range(0, len(sort_nums) - i):
            if gt(sort_nums[j], sort_nums[j + 1]):
                sort_nums[j + 1], sort_nums[j] = sort_nums[j], sort_nums[j + 1]
                swapped = True
        if not swapped:
            break
    if not ascending:
        return sort_nums[::-1]
    return sort_nums


nums = [15, 20, 10, 15, 12, 30, 65]
# 使用默认的gt函数
print(bubble_sort(nums, False))
print(nums)

words = ['apple', 'watermelon', 'hello', 'zoo', 'internationalization']
# 修改gt函数根据字符串长度排序
print(bubble_sort(words, gt=lambda x, y: len(x) > len(y), ascending=False))

例题4:编写实现查找元素的函数

若列表元素无序,使用顺序查找;若列表元素有序,使用二分查找(折半查找)。

def seq_search(items, key):
    for index, i in enumerate(items):
        if i == key:
            return index
    return -1


def bin_search(items, key, cmp=lambda x, y: x - y):
    """折半查找(渐进时间复杂度:(log2 n))

    :param cmp: 比较两个元素的大小
    :param items: 一个有序列表
    :param key: 查找的元素
    :return: 找到了返回元素下标,没找到返回-1
    """
    high = len(items) - 1
    low = 0
    while low <= high:
        mid = (high + low) // 2
        if cmp(items[mid], key) > 0:
            high = mid - 1
        elif cmp(items[mid], key) < 0:
            low = mid + 1
        else:
            return mid
    return -1


if __name__ == '__main__':
    nums1 = [78, 58, 93, 42, 45, 66, 12, 15]
    nums2 = [1, 15, 25, 40, 80, 92, 98, 99]
    print(seq_search(nums1, 58))
    print(seq_search(nums1, 40))
    print(bin_search(nums2, 99))
    print(bin_search(nums2, 85))

例题5:判断快乐数

对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程,如果数字变成了1,这个数就是快乐数,如果无限循环始终变不到1,这个数就不是快乐数。

def is_happy_num(num):
    temp = []
    while num not in temp:
        temp.append(num)
        total = 0
        while num != 0:
            total += (num % 10) ** 2
            num //= 10
        if total == 1:
            return True
        num = total
    return False


for i in range(100, 1001):
    if is_happy_num(i):
        print(i, end=' ')

面向对象编程

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

面向对象编程的过程

  1. 定义类

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

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

  2. 创建对象

  3. 给对象发消息

定义类

在Python中,可以使用class关键字加上类名来定义类,通过缩进我们可以确定类的代码块,就如同定义函数那样。

class Student:

	def __init__(self, name, age):
        """初始化方法"""
        self.name = name
        self.age = age
    def study(self, course_name):
        print(f'{self.name}正在学习{course_name}.')

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

创建对象

stu1 = Student('小青', 20)
stu2 = Student('小白', 22)

给对象发消息

# 通过“类.方法”调用方法,第一个参数是接收消息的对象,第二个参数是学习的课程名称
Student.study(stu1, 'Python程序设计')
# 通过“对象.方法”调用方法,点前面的对象就是接收消息的对象,只需要传入第二个参数
stu1.study('Python程序设计') 
stu2.play('王者荣耀')

一般使用“对象.方法”调用方法

面向对象的支柱

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

  • 抽象:提取共性(定义类就是一个抽象过程,需要做数据抽象和行为抽象)。
  • 封装:把数据和操作数据的函数从逻辑上组成一个整体(对象)。隐藏实现细节,暴露简单的调用接口
  • 继承:扩展已有的类创建新类,实现对已有类的代码复用。
  • 多态:给不同的对象发出同样的消息,不同的对象执行了不同的行为。

静态方法和类方法

使用staticmethod装饰器声明某个方法是某个类的静态方法,如果要声明类方法,可以使用classmethod装饰器。可以直接使用类名.方法名的方式来调用静态方法和类方法,二者的区别在于,类方法的第一个参数是类对象本身,而静态方法则没有这个参数。简单的总结一下,对象方法、类方法、静态方法都可以通过类名.方法名的方式来调用,区别在于方法的第一个参数到底是普通对象还是类对象,还是没有接受消息的对象

继承和多态

继承的语法是在定义类的时候,在类名后的圆括号中指定当前类的父类。在子类的初始化方法中,我们可以通过super().__init__()来调用父类初始化方法,super函数是Python内置函数中专门为获取当前对象的父类对象而设计的。子类除了可以通过继承得到父类提供的属性和方法外,还可以定义自己特有的属性和方法,所以子类比父类拥有的更多的能力。
子类继承父类的方法后,还可以对方法进行重写(重新实现该方法)。
多态就是调用相同的方法不同的子类对象做不同的事情

魔术方法

魔术方法(魔法方法):有特殊用途和意义的方法。
Python中有很多魔法方法,下面是几个常用的:
__init__ —> 初始化方法,在调用构造器语法创建对象的时候会被自动调用
__str__ —> 获得对象的字符串表示,在调用print函数输出对象时会被自动调用
__repr__ —> 获得对象的字符串表示,把对象放到容器中调用print输出时会自动调用
__lt__ —> 在使用 < 运算符比较两个对象大小时会自动调用

类和类之间的关系

类和类之间的关系可以粗略的分为is-a关系(继承)has-a关系(关联)use-a关系(依赖)

  • is-a关系:继承
  • has-a关系:关联 —>把一个类的对象作为另一个类的对象的属性
    —普通关联
    —强关联:整体和部分的关联,聚合和合成
  • use-a关系:依赖 —> 一个类的对象作为另一个类的方法的参数或返回值

以上是关于Python:函数与面向对象编程总结的主要内容,如果未能解决你的问题,请参考以下文章

Python之路 - 面向对象初识

Scala的面向对象与函数编程

Python面向对象编程_类

Python 面向对象编程的一些知识点总结

Python核心编程总结(四面向对象与异常)

面向对象学习(python)