2. 面向对象编程推导

news2024/11/30 14:53:07

1. 面向过程编程

面向过程编程(Procedure-Oriented Programming, POP): 是一种关注于解决问题步骤或过程的编程范式.

面向过程编程核心思想: 将复杂问题分解为一系列简单, 可执行的步骤(即过程或函数), 
并按照特定的顺序依次执行这些步骤, 直到问题得到解决.
每个步骤(过程或函数)接受输入数据, 执行特定操作, 并产生输出结果.
过程或函数可以接受参数, 并可以返回值供其他过程或函数使用.

面向过程编程主要特点:
* 1. 顺序执行: 程序按照预定的步骤顺序执行.
* 2. 模块化: 程序被分解为多个独立的函数或过程, 每个函数或过程执行特定的任务.
     模块化提高了代码的可读性, 可维护性和重用性.
* 3. 数据和过程分离: 数据和过程是分开的. 
* 4. 数据通过参数传递给过程, 并在过程中被处理. 过程本身不包含数据, 只包含处理数据的逻辑.
* 5. 控制流: 程序的控制流(即执行顺序)通过流程控制语句(如条件语句, 循环语句等)来管理.
     这些结构确保程序能够按照我们设计的逻辑来执行.

总结: 面向过程编程使用函数来组织代码, 并通过函数调用和参数传递来实现数据在函数之间的传递.
它强调问题的分解和步骤的执行顺序, 而忽略了数据和对象之间的关系, 只适用于解决那些可以明确划分为一系列步骤的问题.
然而, 随着问题复杂性的增加, 面向过程编程的代码可能变得难以维护和扩展, 缺乏对数据和行为的封装, 容易导致代码的重复和耦合.
在处理更复杂的项目和系统时, 面向对象编程可能是一个更好的选择, 
因为它强调将数据和对数据的操作封装在对象中, 并通过对象之间的交互来实现程序的功能.
例如, 需要设计一个注册程序员, 对这个程序进行分析, 可分解为以下三步骤:
* 1. 获取用户名和密码
* 2. 组织成固定的格式
* 3. 文件操作写入文件
# 用户注册
def register():
    # vip等级
    vip_level = {
        '1': 'LV1',
        '2': 'LV2',
        '3': 'LV3'}

    # 循环注册
    while True:
        username = input('输入用户名称>>>:').strip()  # 去除多余的空格
        password = input('输入用户密码>>>:').strip()

        # 输入不能为空
        if len(username) == 0 or len(password) == 0:
            print('账户或密码不能为空, 请重新输入!')
            break

        #
        while True:
            print('选择vip等级'.center(15, '-'))
            for k, v in vip_level.items():
                print(k, ' : ', v)
            select = input('请选择>>>:')
            if select not in vip_level:
                print('输入选项不存在, 请重新输入!')
                break

            # 返回账户, 密码, 身份
            return username, password, vip_level[select]


# 组织成固定的格式
def deal_data():
    # 获取数据, 并格式化数据
    data = '%s|%s|%s\n' % (register())
    return data


# 文件写入
def sava_data():
    # 追加模式写入数据
    with open(r'data.txt', mode='a', encoding='utf8') as f1:
        # 写入返回的数据
        f1.write(deal_data())
        print('注册成功!')


# 程序入口
sava_data()

image-20240612034454247

2. 面向对象编程

面向对象(Object-Oriented, 简称OO): 是一种软件开发方法, 它将现实世界的事物抽象为'对象', 通过类和对象等概念来设计软件结构.
面向对象的方法使软件开发更加符合人类的思维方式, 提高了软件的可重用性, 可维护性和可扩展性.

面向对象编程(Object-Oriented Programming, 简称OOP): 是面向对象方法的具体实现.
在面向对象编程中, 开发者定义类(Class)作为对象的模板, 类描述了对象的状态(属性或数据成员)和行为(方法或函数成员).
对象则是根据类创建的实例, 它拥有类的所有属性和方法.

2.1 程序演变1

学生选课系统 Version 1.0 (数据与功能是分离的)
1. 定义一个课程列表存放课程(Python, Linux).
2. 定义两个学生信息(名称, 年龄, 性别, 班级, 课程).
3. 在定义一个选课函数, 需要打印选课信息.

现在我们写的程序是由数据和功能组成: 先定义一系列的数据, 然后再定义一系列的功能(函数)来对数据进行操作.
# 定义学生1的信息
stu1_name = 'kid'
stu1_age = 18
stu1_gender = 'male'
stu1_class = '301'
stu1_courses = []  # 可以用多门课程, 先定义为一个空列表

# 定义学生2的信息
stu2_name = 'qq'
stu2_age = 19
stu2_gender = 'female'
stu2_class = '301'
stu2_courses = []  # 可以用多门课程, 先定义为一个空列表


# 选课函数
def elective(stu_name, stu_courses, course):
    stu_courses.append(course)
    print('%s成功选择%s课程!' % (stu_name, course))


# 调用函数选课
elective(stu1_name, stu1_courses, 'Python')  # kid成功选择Python课程!
elective(stu2_name, stu2_courses, 'Linux')  # qq成功选择Linux课程!

image-20240612034314663

这个例子, 用于展示如何在Python中处理学生信息和选课功能.
然而, 随着数据量的增加, 硬编码的方式(如直接定义变量stu1_name, stu2_name等)存储用户的信息, 往后会变得难以维护.

2.2 程序演变2

学生选课系统 Version 2: (将学生信息存储在结构化的容器中)
之前的数据存储存在的散乱的问题.
改为使用字典存储学生信息, 这样可以确保每个学生的信息都被组织在一个清晰, 结构化的容器中, 方便区分学生的信息.
也可以直接通过学生的唯一标识符来快速定位并访问他们的信息, 大大提高了信息检索的效率和准确性.
例如, 要查找学生kid的信息, 可以直接通过stu1['key']获取, 无需在散乱的数据中搜索.
# 定义学生1的信息
stu1 = {
    'name': 'kid',
    'age': 18,
    'gender': 'male',
    'class': '301',
    'courses': []
}

# 定义学生2的信息
stu2 = {
    'name': 'qq',
    'age': 19,
    'gender': 'female',
    'class': '301',
    'courses': []
}


# 选课函数
def elective(stu_name, stu_courses, course):
    stu_courses.append(course)
    print('%s成功选择%s课程!' % (stu_name, course))


# 调用函数选课
elective(stu1['name'], stu1['courses'], 'Python')  # kid成功选择Python课程!
elective(stu2['name'], stu2['courses'], 'Linux')  # qq成功选择Linux课程!

image-20240612034237245

2.3 程序演变3

学生选课系统 Version 3: (将选课功能与学生对象紧密绑定)
将原本可以分离的数据与功能放在一起, 字典中同时具有学生数据和选课功能 --> 对象的雏形.

在Python中, 函数是一等公民(first-class citizens), 可以像其他对象一样被赋值给变量, 作为参数传递, 甚至作为容器类型的元素.
# 定义学生1的信息
def elective(stu, course):
    stu['courses'].append(course)
    print('%s成功选择%s课程!' % (stu['name'], course))


# 定义学生1的信息
stu1 = {
    'name': 'kid',
    'age': 18,
    'gender': 'female',
    'class': '301',
    'courses': [],
    'elective': elective  # 使用函数的特性
}

# 定义学生2的信息

stu2 = {
    'name': 'qq',
    'age': 19,
    'gender': 'male',
    'class': '301',
    'courses': [],
    'elective': elective  # 使用函数的特性
}

stu1['elective'](stu1, 'Python')  # kid成功选择Python课程!
stu2['elective'](stu2, 'Linux')  # qq成功选择Linux课程!

image-20240612034207145

可以看到每个学生信息字典中都包含一些重复且不变的数据结构, 
比如: 'age': 18, 'gender': 'male', 如果多个学生具有相同的年龄和性别, 这些数据就会在每个学生的字典中重复.

2.4 程序演变4

学生选课系统 Version 4 (减少数据冗余)
为了减少数据的重复, 将共同的数据提取出来存放在一个字典容器中.
可以通过创建一个包含默认学生信息的字典, 并在创建每个学生信息时从这个默认字典中复制数据.
# 选课函数
def elective(stu, course):
    stu['courses'].append(course)
    print('%s成功选择%s课程!' % (stu['name'], course))


# # 定义默认学生信息(共同部分)
student_info = {
    'class': '301',
    'elective': elective
}

# 定义学生1的信息
stu1 = {
    'name': 'kid',
    'age': 18,
    'gender': 'male',
    'courses': [],
}

# 定义学生2的信息
stu2 = {
    'name': 'qq',
    'age': 19,
    'gender': 'male',
    'courses': [],
}

student_info['elective'](stu1, 'Python')  # kid成功选择Python课程!
student_info['elective'](stu2, 'Linux')  # qq成功选择Linux课程!

image-20240612034123491

如果我们要创建多个学生对象, 每个对象都需要一个独立的字典, 还会导致代码冗余和难以管理.
况且字典主要用于简单的数据存储和检索, 而不是用于定义具有复杂行为的对象.
Python语言提供了类来更好将数据和功能存放, 而且代码更加清晰, 易于管理和扩展.
从上述可知, 类的基本作用是解决代码冗余,

2.5 类

(Class)是面向对象编程中的一个核心概念. 
类是一个抽象的概念, 它描述了一组具有共同属性和方法的对象的蓝图.
通过定义一个类, 可以指定该类实例(即对象)应有的行为(方法==函数)和状态(属性==变量).
2.5.1 定义类
在Python中, 可以通过class关键字来定义类. 
# 类的基本语法如下:
class ClassName:  
    """类的文档字符串."""  
    # 定义对象的公有属性
    class_name = 301
    
    # 定义方法
    def func():
        ...

学生选课系统 Version 4 (解决相同数据冗余问题)
多个学生注册, 字典中会产生一些重复且不变的数据(数据-->class班级, 功能-->elective选课).
将学生对象中重复的数据提取出来, 存放在一个共用的容器中, 这个容器可以是类.
类可以存放相同的特征和技能, 将对象的属性和行为包装在一起, 作为一个独立的单元, 这个过程称为'封装'.
在类中定义的变量称为属性, 在类中定义的函数称为方法.
# 定义Student类:
class Student:
    # 定义对象的公有属性
    class_name = '301'

    # 定义对象的公有方法
    def elective(stu, course):
        stu['courses'].append(course)
        print('%s成功选择%s课程!' % (stu['name'], course))

image-20240612034052345

2.5.2 定义类时发生的事情
定义类时发生的事情:
* 1. 立刻执行类体代码: 定义一个类时, Python会立即执行类体中的代码.
     这包括任何在类体中的赋值语句, 函数定义(这些函数在类内成为方法).
     会遇到几种不同类型的代码:
     赋值语句: 这些会立即执行, 为类变量(也称为静态变量或类属性)分配值.
     函数定义: 在类体中定义的函数会成为类的方法.
     注意, 这些函数定义本身只是定义了方法的结构(即它们的名称, 参数和函数体), 它们并不会被'执行'或调用.
     当你定义方法时, Python会为它们创建函数对象, 并将这些对象存储在类的命名空间中.

* 2. 产生一个类的名称空间, 把类体内产生的名称都归档进名称空间(字典):
     当Python定义一个新类时, 它会为这个类创建一个命名空间.
     这个命名空间是一个字典, 用于存储类级别的属性(也称为类变量或静态变量)和方法.
     类体中的任何变量或函数定义都会成为这个命名空间的一部分.

* 3. 把类的名称绑定给.dict (类名.__dict__可以进行查看):
     当定义一个类时, Python会将类的名称绑定到一个类型对象上.
     这个类型对象有一个__dict__ 属性, 它是一个字典, 包含了类的所有属性和方法.
     通过类名.__dict__来查看这个字典, 从而了解类定义了哪些属性和方法.

     请注意, __dict__ 属性主要用于内部实现和调试目的.
     在大多数情况下, 不需要直接访问它.
在Python中, 对象的.()操作符通常用于访问对象的属性和方法.
这是面向对象编程中的一个核心概念, 允许我们以直观和清晰的方式与对象进行交互.
# 定义Student类:
class Student:
    # 定义对象的公有属性
    class_name = '301'

    # 定义对象的公有方法, 创建方法的时候会自动添加一个参数self, 后续会详细介绍.
    def elective(self, stu, course):
        stu['courses'].append(course)
        print('%s成功选择%s课程!' % (stu['name'], course))


print(Student.__dict__)
"""
{
'__module__': '__main__', 
'class_name': '301',
'elective': < functionStudent.electiveat0x000001C3832D8F70 >, 
'__dict__': < attribute'__dict__'of'Student'objects >, 
'__weakref__': < attribute'__weakref__'of'Student'objects >, 
'__doc__': None
}
"""

image-20240612034028625

查看一个类的__dict__属性时, 会得到一个字典, 该字典包含了类的所有公共属性和方法.
下面是对这些条目的解释:

__module__: 这是一个内置属性, 表示类定义所在的模块名称.
目前情况下, 它是 '__main__', 这通常意味着类是在主程序(即直接运行的脚本)中定义的, 而不是在导入的模块中.

class_name: 自己定义的类变量, 值为'301'.

elective: 定义的方法, <function Student.elective at 0x000001C3832D8F70>是该方法在内存中的地址.

__dict__: 这是一个内置属性描述符, 用于在类的实例上实现__dict__属性的行为.
但请注意, 在类的 __dict__ , __dict__本身并不是一个字典, 而是一个描述符对象.
当尝试访问一个实例的__dict__属性时, Python会使用这个描述符来返回实例的属性字典.

__weakref__: 是一个内置属性描述符, 用于支持对象的弱引用. 这意味着对象可以被引用, 但不会增加其引用计数.
因此, 即使存在循环引用, 只要对象的强引用计数变为零, 垃圾回收器仍然可以回收其内存.
(主要是资源回收, 避免循环引用导致的内存泄漏.)

__doc__: 这是一个内置属性, 用于存储类的文档字符串(docstring).
文档字符串是类, 方法, 模块或函数定义后的第一个未赋值的字符串.
目前的情况下, __doc__的值是None, 这意味着没有为Student类定义文档字符串.

这个字典包含了类的元信息(: __module__  __doc__), 自定义的类变量(: class_name)和方法(: elective),
以及用于实现特定功能的内置描述符(: __dict__和__weakref__).
2.5.3 实例化对象
在Python中, 类名加括号用于实例化一个类, 会返回一个新的对象, 这个对象是该类的一个实例.
实例(instance)是类的具体实现, 或者说是类的一个具体化的存在.
(类可以被看作是一个模板或蓝图, 它定义了对象应有的属性和方法.
而实例则是根据这个模板创建出来的具体对象, 它包含了类中定义的属性和方法的具体值或实现.)
实例化: 使用类作为模板创建对象的过程.
实例化对象: 根据类创建的具体实例, 具有类定义的属性和方法, 并存储在内存中.
实例化是一个动词, 描述的是创建对象的过程.
实例化对象是一个名词, 指的是创建出来的具体对象.
但在很多非正式或简化的语境中, 这两个术语可能会被混用.
# 定义Student类:
class Student:
    # 定义类属性, 类属性通常用于存储与类相关的常量值或默认配置信息.
    class_name = '301'

    # 定义对象的公有方法
    def elective(self, stu, course):
        stu['courses'].append(course)
        print('%s成功选择%s课程!' % (stu['name'], course))


# 定义学生1的信息
stu1 = {
    'name': 'kid',
    'age': 18,
    'gender': 'male',
    'courses': [],
}

# 定义学生1的信息
stu2 = {
    'name': 'qq',
    'age': 19,
    'gender': 'female',
    'courses': [],
}

# 实例化对象
stu_obj1 = Student()  # stu_obj1为一个学生对象, 不包含学生信息
stu_obj2 = Student()  # stu_obj2为一个学生对象, 不包含学生信息

# 通过对象.方法名称的方法获取方法的函数名称
stu_obj1.elective(stu1, 'Python')  # kid成功选择Python课程!
stu_obj2.elective(stu2, 'Linux')  # qq成功选择Linux课程!

现在数据又分离, 我们需要把数据放到对象中去.

image-20240612033932892

2.5.4 实例化对象独有属性
在Python中, 每个实例化对象都有一个内建的__dict__属性, 它是一个字典, 用于存储该对象的实例属性(也称为成员变量).
通过这个属性, 我们可以查看或修改对象的状态, 即它的实例属性.
(请注意, 不是所有的对象都有__dict__ 属性, 一些内置类型或使用了__slots__属性的类可能不使用__dict__来存储实例属性.)
# 定义Student类:
class Student:
    # 定义对象的公有属性
    class_name = '301'

    # 定义对象的公有方法
    def elective(self, stu, course):
        stu['courses'].append(course)
        print('%s成功选择%s课程!' % (stu['name'], course))


# 定义学生1的信息
stu1 = {
    'name': 'kid',
    'age': 18,
    'gender': 'male',
    'courses': [],
}


# 实例化对象
stu_obj1 = Student()

print(stu_obj1.__dict__)  # {}

image-20240612033842996

2.5.5 添加实例对象独有属性
在Python中, 实例的独有属性可以通过点操作符(.)来读取, 设置, 新增和删除.
(可以直接操作__dict__来添加或修改属性, 但不推荐.)
Python中, 实例对象(或称为对象实例)的独有数据(即实例属性)可以通过两种方式访问和修改:
* 1. 使用点符号(.)直接访问或修改对象的属性, 例如: 对象.attribute.
* 2. 使用__dict__字典来访问或修改对象的属性, 例如: 对象.__dict__[key].

但它们在使用方式和背后的机制上有所不同.
* 1. 对象.__dict__[key] = value: 直接操作对象的__dict__属性, 这是一个存储对象属性的字典.
     通过这种方式, 可以直接设置或修改对象的任何属性, 无论该属性是否已经在类的定义中声明.
     这种方法绕过了Python的属性访问机制, 直接对底层数据结构进行操作.
     如果对象没有__slots__属性, 那么可以通过这种方式添加或修改任意属性.
* 2. 对象.attribute = value: 通过标准的点符号(.)操作符来设置对象的属性.
     例如, obj.name = 'kid', Python会首先检查对象的__dict__中是否有name这个键.
     如果有, 就更新它的值; 如果没有, 就在__dict__中添加这个键和对应的值.
     如果name属性是通过类定义中的方法(@property装饰器)或描述符
     (如实现了__get__, __set__, 和__del__方法的对象)来控制的,
     那么设置这个属性时会调用相应的方法或描述符的__set__方法.
     
     这种方式更符合Python的面向对象编程惯例, 并且更易于理解和维护.
# 定义Student类:
class Student:
    # 定义对象的公有属性
    class_name = '301'

    # 定义对象的公有方法
    def elective(self, stu, course):
        stu['courses'].append(course)
        print('%s成功选择%s课程!' % (stu['name'], course))


# 实例化对象
stu_obj1 = Student()
print(stu_obj1.__dict__)  # {}
# 添加独有属性
stu_obj1.__dict__['name'] = 'kid'
stu_obj1.__dict__['age'] = 18
stu_obj1.__dict__['gender'] = 'male'
print(stu_obj1.__dict__)  # {'name': 'kid', 'age': 18, 'gender': 'male'}

# 实例化对象
stu_obj2 = Student()
print(stu_obj2.__dict__)  # {}
# 类提供快捷方式
stu_obj2.name = 'qz'
stu_obj2.age = 19
stu_obj2.gender = 'female'
print(stu_obj2.__dict__)  # {'name': 'qz', 'age': 19, 'gender': 'female'}

image-20240612033737037

2.5.6 访问实例对象独有属性
# 定义Student类:
class Student:
    # 定义对象的公有属性
    class_name = '301'

    # 定义对象的公有方法
    def elective(self, stu, course):
        stu['courses'].append(course)
        print('%s成功选择%s课程!' % (stu['name'], course))


# 实例化对象
stu_obj1 = Student()
print(stu_obj1.__dict__)  # {}
# 添加独有属性
stu_obj1.__dict__['name'] = 'kid'
stu_obj1.__dict__['age'] = 18
stu_obj1.__dict__['gender'] = 'male'
print(stu_obj1.__dict__)  # {'name': 'kid', 'age': 18, 'gender': 'male'}
# 使用独有属性
print(stu_obj1.__dict__['name'])  # kid
print(stu_obj1.__dict__['age'])  # 18
print(stu_obj1.__dict__['gender'])  # male

# 实例化对象
stu_obj2 = Student()
print(stu_obj2.__dict__)  # {}
# 类提供快捷方式
stu_obj2.name = 'qz'
stu_obj2.age = 19
stu_obj2.gender = 'female'
print(stu_obj2.__dict__)  # {'name': 'qz', 'age': 19, 'gender': 'female'}
# 使用独有属性
print(stu_obj2.name)  # qz
print(stu_obj2.age)  # 19
print(stu_obj2.gender)  # female

# 访问不存在的属性
# print(stu_obj1.__dict__['xx'])  # KeyError: 'xx'
# print(stu_obj2.xx)  # AttributeError: 'Student' object has no attribute 'xx'

image-20240612033645424

2.5.7 修改实例对象独有属性
# 定义Student类:
class Student:
    # 定义对象的公有属性
    class_name = '301'

    # 定义对象的公有方法
    def elective(self, stu, course):
        stu['courses'].append(course)
        print('%s成功选择%s课程!' % (stu['name'], course))


# 实例化对象
stu_obj1 = Student()
print(stu_obj1.__dict__)  # {}
# 添加独有属性
stu_obj1.__dict__['name'] = 'kid'
stu_obj1.__dict__['age'] = 18
stu_obj1.__dict__['gender'] = 'male'
print(stu_obj1.__dict__)  # {'name': 'kid', 'age': 18, 'gender': 'male'}
# 修改属性值
stu_obj1.__dict__['name'] = '张三'
stu_obj1.__dict__['age'] = 20
print(stu_obj1.__dict__)  # {'name': '张三', 'age': 20, 'gender': 'male'}

# 实例化对象
stu_obj2 = Student()
print(stu_obj2.__dict__)  # {}
# 类提供快捷方式
stu_obj2.name = 'qz'
stu_obj2.age = 19
stu_obj2.gender = 'female'
print(stu_obj2.__dict__)  # {'name': 'qz', 'age': 19, 'gender': 'female'}
# 修改属性值
stu_obj2.name = '李四'
stu_obj2.age = 21
print(stu_obj2.__dict__)  # {'name': '李四', 'age': 21, 'gender': 'female'}

image-20240612033538531

2.5.8 删除实例对象独有属性
# 定义Student类:
class Student:
    # 定义对象的公有属性
    class_name = '301'

    # 定义对象的公有方法
    def elective(self, stu, course):
        stu['courses'].append(course)
        print('%s成功选择%s课程!' % (stu['name'], course))


# 实例化对象
stu_obj1 = Student()
print(stu_obj1.__dict__)  # {}
# 添加独有属性
stu_obj1.__dict__['name'] = 'kid'
stu_obj1.__dict__['age'] = 18
stu_obj1.__dict__['gender'] = 'male'
print(stu_obj1.__dict__)  # {'name': 'kid', 'age': 18, 'gender': 'male'}
# 删除属性值
del stu_obj1.__dict__['name']
del stu_obj1.__dict__['age']
print(stu_obj1.__dict__)  # {'gender': 'male'}

# 实例化对象
stu_obj2 = Student()
print(stu_obj2.__dict__)  # {}
# 类提供快捷方式
stu_obj2.name = 'qz'
stu_obj2.age = 19
stu_obj2.gender = 'female'
print(stu_obj2.__dict__)  # {'name': 'qz', 'age': 19, 'gender': 'female'}
# 删除属性值
del stu_obj2.name
del stu_obj2.age
print(stu_obj2.__dict__)  # {'gender': 'female'}

image-20240612033505944

2.5.8 访问类属性
类属性在所有实例间共享: 类属性是属于类的, 而不是属于某个特定实例的.
因此, 所有通过该类创建的实例都会共享相同的类属性.
当你修改一个类属性的值时, 这个改变会反映到类的所有实例上.

注意: 类属性通常被设计为只读或几乎不会修改.
访问类属性的方式:
通过类名访问类属性: 可以直接使用类名来访问类属性, 因为它们是属于类本身的.
通过对象实例访问类属性: 虽然类属性是定义在类上的, 但可以通过类的实例(即对象)来访问它们.
这是因为在Python中, 实例会继承类的所有属性(包括类属性).

避免名称冲突: 为了避免混淆和潜在的错误, 通常不建议在对象的实例属性中使用与类属性同名的变量.
如果这样做, 通过对象实例访问该属性时, Python会首先查找实例属性; 如果实例属性不存在, 则会查找类属性.
# 定义Student类:
class Student:
    # 定义对象的公有属性
    class_name = '301'

    # 定义对象的公有方法
    def elective(self, stu, course):
        stu['courses'].append(course)
        print('%s成功选择%s课程!' % (stu['name'], course))


stu_obj1 = Student()
stu_obj2 = Student()

# 使用类名来访问类属性
print(Student.class_name)  # 301
print(Student.class_name)  # 301

# 使用类的实例来访问类属性
print(stu_obj1.class_name)  # 301
print(stu_obj2.class_name)  # 301

image-20240612033418258

2.5.9 self参数
在Python中, 当你实例化一个类的对象并调用其方法时,
Python会自动将实例对象(即调用方法的那个对象)作为第一个参数传递给该方法.
这个过程是Python面向对象编程的一个基本特性, 它使得方法能够访问和修改对象的内部状态(即对象的属性).
self只是一个约定俗成的名称, 但按照惯例, 应该始终使用self作为实例方法的第一个参数.
# 学生选课系统 Version 4 最终版本:
class Student:
    class_name = '301'

    # 改进选课方法
    def elective(self, course):
        self.courses.append(course)
        print('%s成功选择%s课程!' % (self.name, course))


# 实例化对象
stu_obj1 = Student()
# 添加独有属性
stu_obj1.name = 'kid'
stu_obj1.age = 18
stu_obj1.gender = 'male'
stu_obj1.courses = []
# 调用方法, 会自动将对象作为第一个参数传递, 如果不明白就看成是 stu_obj1.elective(stu_obj1, 'Python')
stu_obj1.elective('Python')  # kid成功选择Python课程!

# 实例化对象
stu_obj2 = Student()
# 添加独有属性
stu_obj2.name = 'qz'
stu_obj2.age = 19
stu_obj2.gender = 'male'
stu_obj2.courses = []

# 调用方法
stu_obj2.elective('Linux')  # kid成功选择Python课程!

image-20240612033347250

目前代码还存在实例化对象添加属性代码冗余的问题.

2.6 程序演变5

学生选课系统 Version 5 (为实例化对象添加属性代码冗余)
实例化对象添加属性过程重复, 将重复的代码封装到一个可重用的结构中(函数).
简单来说, 就是定义一个初始化函数, 为实例化对象添加独有属性.
# 定义Student类:
class Student:
    class_name = '301'

    # 选课方法
    def elective(self, course):
        self.courses.append(course)
        print('%s成功选择%s课程!' % (self.name, course))


# 定义初始化函数
def init(stu_obj, name, age, gender, courses):
    # 为对象添加独有属性
    stu_obj.name = name
    stu_obj.age = age
    stu_obj.gender = gender
    stu_obj.courses = courses


# 实例化对象
stu_obj1 = Student()  # 学生1
# 调用初始化方法
init(stu_obj1, 'kid', 18, 'male', [])
# 调用方法
stu_obj1.elective('Python')  # kid成功选择Python课程!

# 实例化对象
stu_obj2 = Student()  # 学生2
# 调用初始化方法
init(stu_obj2, 'qq', 19, 'female', [])
# 调用方法
stu_obj2.elective('Linux')  # qq成功选择Linux课程!

image-20240612033313652

2.7 程序演变6

学生选课系统 Version 6 (提高可读性)
在面向对象编程中, 当有多个对象需要共享相同的初始化和一些共同的行为时, 
将这些代码封装进一个类中, 使代码结构更清晰, 更易于阅读.
# 定义Student类:
class Student:
    class_name = '301'

    # 选课方法
    def elective(self, course):
        self.courses.append(course)
        print('%s成功选择%s课程!' % (self.name, course))

    # 定义初始化函数
    def init(self, name, age, gender, courses):
        # 为对象添加独有属性
        self.name = name
        self.age = age
        self.gender = gender
        self.courses = courses


# 实例化对象
stu_obj1 = Student()  # 学生1
# 调用初始化方法
stu_obj1.init('kid', 18, 'male', [])
# 调用方法
stu_obj1.elective('Python')  # kid成功选择Python课程!

# 实例化对象
stu_obj2 = Student()  # 学生2
# 调用初始化方法
stu_obj2.init('qq', 19, 'female', [])
# 调用方法
stu_obj2.elective('Linux')  # qq成功选择Linux课程!

image-20240612033234821

2.8 初始化方法

在Python中, 双下划线开头和结尾的方法被称为'魔法方法''特殊方法''双下划线方法'(dunder methods).
这些方法是Python类定义中的一部分, 它们为对象提供了一些特殊的行为或特性.

魔法方法通常由Python解释器在特定情况下自动调用, 以执行如对象初始化, 类型转换, 比较, 算术操作, 属性访问等任务.
这些方法的名称是固定的, 并且遵循特定的命名约定, 即以两个下划线开始和结束.
Python中, 提供一个特殊的方法__init__, 用于在创建类的新实例时初始化对象的属性.
它不是自动为每个类提供的, 而是需要程序员在定义类时显式地添加.

__init__方法被称为类的构造函数或初始化方法.
当创建类的一个新实例时, __init__方法会自动被调用, 用于初始化对象的属性或执行其他必要的设置.

通常, __init__方法会放在类的开始部分, 因为它定义了对象创建时需要执行的操作, 并设置了对象的初始状态.
这样做有助于读者快速了解如何创建和初始化该类的对象.

__init__方法的参数是由创建类实例时提供的.
当调用一个类的构造函数(即实例化一个类), 传递的参数会被用来调用该类的__init__方法.
__init__方法可以接收任意数量的参数, 但第一个参数总是一个特殊的引用, 它指向新创建的对象实例,
按照惯例, 这个参数通常被命名为 self(虽然你也可以使用其他名称, 但self是最常用的).

除了self之外, __init__方法可以接收任意数量的其他参数, 这些参数通常对应于实例化对象时传递给构造函数的参数.

注意: __init__ 方法不应该有返回值(或者说, 它应该返回 None).
这是因为__init__方法的主要目的是设置对象的状态, 而不是返回一个新的对象.
实际上, 如果尝试从__init__方法返回一个值(除了 None), Python解释器会忽略这个返回值, 并且仍然返回新创建的对象实例.

2.9 程序演变7

学生选课系统 Version 6 (自动初始化对象)
init只是一个普通的成员函数, 它不会被Python自动调用, 需要在创建对象后显式地调用它来进行初始化.
__init__是一个特殊的魔法方法, 在创建类的新实例时, Python会自动调用它来进行初始化.
__init__是Python中类的构造函数的约定, 所有Python开发者都了解它的作用.    
改进: 将自定义初始化方法init改为特殊方法__init__.
# 定义Student类:
class Student:
    class_name = '301'

    # 定义初始化函数
    def __init__(self, name, age, gender, courses):
        # 为对象添加独有属性
        self.name = name
        self.age = age
        self.gender = gender
        self.courses = courses

    # 选课方法
    def elective(self, course):
        self.courses.append(course)
        print('%s成功选择%s课程!' % (self.name, course))


# 创建一个Student类的实例时, 需要提供name, age, gender, courses参数
stu_obj1 = Student('kid', 18, 'male', [])  # 学生1

# 调用方法
stu_obj1.elective('Python')  # kid成功选择Python课程!

# 实例化对象
stu_obj2 = Student('qq', 19, 'female', [])  # 学生2
# 调用方法
stu_obj2.elective('Linux')  # qq成功选择Linux课程!

image-20240612033134397

最终的这个推导而来的就是面向对象编程, 先定义类, 后创造对象.
类可以被视为一个模板或蓝图, 它定义了对象的属性和方法.
这个模板描述了具有相似特性和行为的对象应当如何被构建和运作.

在定义类之后, 可以通过提供必要的参数来创建类的实例, 即对象.
每个对象都是根据其类的定义来创建的, 并且会拥有类中定义的属性和方法.
由于每个对象都可以有自己的属性状态, 因此即使它们都属于同一个类, 不同的对象之间也可能有不同的属性值.

image-20240612032003278

对象的独有属性通常是在类的构造函数(在Python中是__init__方法)中定义的,
而公有数据(包括公有属性, 方法和常量)则是直接在类中定义的.

image-20240612032027300

注意: 避免在__init__方法之外定义实例属性, 除非你有明确的理由(比如通过某种机制动态地添加属性).
否则, 其他开发者或未来的你可能不清楚这个属性是在哪里和如何被设置的, 导致代码更难理解和维护.
class A:
    def __init__(self, name):
        self.name = name

    def func(self, other):
        self.thoer = other
        # Instance attribute defined outside __init__
        # 在__init__外部定义的实例属性

image-20240612033041937

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1815236.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

compose for desktop

then 叠加修饰符功能的作用 val reusableModifier Modifier.fillMaxWidth().background(Color.Red).padding(12.dp)// Append to your reusableModifier reusableModifier.clickable { /*...*/ }// Append your reusableModifier otherModifier.then(reusableModifier)https:…

springboot物流管理系统-计算机毕业设计源码00781

摘要 信息化社会内需要与之针对性的信息获取途径&#xff0c;但是途径的扩展基本上为人们所努力的方向&#xff0c;由于站在的角度存在偏差&#xff0c;人们经常能够获得不同类型信息&#xff0c;这也是技术最为难以攻克的课题。针对物流管理系统等问题&#xff0c;对如何通过计…

时间处理获取交易日(考虑兼容性问题)

在获取交易日时间的处理上&#xff0c;出现了苹果14不兼容的问题&#xff0c;就这个问题记录下。 一、获取交易日的代码 封装了一个js文件&#xff0c;在untils目录下&#xff0c;先看代码&#xff0c;然后我讲下思路。 // 获取节假日数据 import { getCalendarHolidays } …

使用脚手架创建vue2项目(关闭eslint语法检查 、运行项目时自动打开网址、src文件夹简写方法)

使用脚手架创建vue2项目会默认安装的插件&#xff08;eslint) 这个插件是检查语法的。 假设我们在main.js中定义了一个变量&#xff0c;没有使用 eslint 就会检测出错误 &#xff08;事实是我们并没有写错而是eslint 给我们判断是错的&#xff0c;所以这样会很麻烦&#xff…

怎么监视员工电脑屏幕?电脑监控软件监控屏幕的六个步骤

监视员工电脑屏幕通常涉及使用专门的电脑监控软件&#xff0c;这些软件设计用于帮助企业管理人员合法合规地监督员工的工作状态、提高工作效率并确保信息安全。以下是实施员工电脑屏幕监视的一般步骤和注意事项&#xff1a; 1. 选择合适的监控软件 首先&#xff0c;选择一款适…

Idea多线程调试

在 IntelliJ IDEA 中调试多线程应用程序可能会有些复杂&#xff0c;因为多个线程可能会同时运行和交互。不过&#xff0c;IDEA 提供了一些强大的工具来帮助你进行多线程调试。以下是一些关键步骤和技巧&#xff0c;帮助你有效地调试多线程应用程序&#xff1a; 创建一个示例多线…

查分易成绩查询入口

今天我来分享一个老师超实用的小技巧&#xff0c;那就是如何用查分易来打造一个专属的成绩查询入口哦&#xff01;无论是我们勤奋的学生们&#xff0c;还是关心孩子学习的家长们&#xff0c;都可以轻松查到自己的成绩信息。来来来&#xff0c;让我来一步步教你怎么用查分易搞定…

优雅迷人的小程序 UI 风格

优雅迷人的小程序 UI 风格

基于Python的信号处理(包络谱,低通、高通、带通滤波,初级特征提取,机器学习,短时傅里叶变换)及轴承故障诊断探索

Python是一种广泛使用的解释型、高级和通用的编程语言&#xff0c;众多的开源科学计算软件包都提供了Python接口&#xff0c;如计算机视觉库OpenCV、可视化工具库VTK等。Python专用计算扩展库&#xff0c;如NumPy、SciPy、matplotlab、Pandas、scikit-learn等。 开发工具上可用…

20240612每日前端-------vue3实现聊天室(一)

先上效果图 讲讲布局设计 聊天室大致分三块&#xff1a; 左边导航右边聊天界面主界面 单独调整一下样式&#xff1a;外层friend-box先调整布局为flex&#xff0c;这样方便进行自适应布局&#xff0c;增加背景色为白色&#xff0c;设置边框圆角使得外观更加美观&#xff0c;使…

树状数组:解锁快速排名的高效利器

文章目录 引言一、快速排名问题概述二、树状数组的应用树状数组概述数据结构初始化查询排名更新排名示例代码 总结参考 引言 在大规模数据排名问题中&#xff0c;树状数组可以用来高效地实现快速排名查询和更新操作&#xff0c;特别是在处理动态变化的数据集时。使用树状数组可…

如何舒适的使用VScode

安装好VScode后通常会很不好用&#xff0c;以下配置可以让你的VScode变得好用许多。 VScode的配置流程 1、设置VScode中文2、下载C/C拓展&#xff0c;使代码可以跳转3、更改编码格式4、设置滚轮缩放5、设置字体6、设置保存自动改变格式7、vscode设置快捷代码 1、设置VScode中文…

Android studio如何导入项目

打开解压好的安装包 找到build.gradle文件 打开查看gradle版本 下载对应的gradle版本Index of /gradle/&#xff08;镜像网站&#xff09; 下载all的对应压缩包 配置gradle的环境变量 新建GRADLE_HOME 将GRADLE_HOME加入到path中 将项目在Android studio中打开进行配置 将gr…

手撕设计模式——计划生育之单例模式

1.业务需求 ​ 大家好&#xff0c;我是菠菜啊。80、90后还记得计划生育这个国策吗&#xff1f;估计同龄的小伙伴们&#xff0c;小时候常常被”只生一个好“”少生、优生“等宣传标语洗脑&#xff0c;如今国家已经放开并鼓励生育了。话说回来&#xff0c;现实生活中有计划生育&…

【Kadane】Leetcode 918. 环形子数组的最大和【中等】

环形子数组的最大和 给定一个长度为 n 的环形整数数组 nums &#xff0c;返回 nums 的非空 子数组 的最大可能和 。 环形数组 意味着数组的末端将会与开头相连呈环状。形式上&#xff0c; nums[i] 的下一个元素是 nums[(i 1) % n] &#xff0c;nums[i] 的前一个元素是 nums…

SortTable.js + vxe-table 实现多条批量排序

环境: vue3+vxe-table+sorttable.js 功能: 实现表格拖动排序,支持单条排序,多条排序 实现思路: sorttable.js官网只有单条排序的例子,网上也都是简单的使用,想要实现多条排序,就要结合着表格的复选框功能,在对其勾选的行统一计算! 最终效果: 实现代码 <template>…

PointNet论文导读

PointNet论文导读 关键点&#xff1a;网络结构输入数据特点&#xff1a;网络关键模块&#xff1a; 关键点&#xff1a; 1.设计一个新颖的网络来处理无序的点云数据&#xff1b; 2.pointnet网络可以被训练用来处理分类、部件分割和场景分割多种任务&#xff1b; 3.提供了数据…

WordPress模板推荐

WordPress外贸主题 wordpress跨境电商独立站主题&#xff0c;wordpress外贸建站模板。 手机配件wordpress外贸网站模板 充电器、移动电源、手机膜、手机电池、手机壳、手机转接头等手机配件wordpress外贸网站模板。 毛巾WordPress外贸主题 毛巾、面巾、婴童毛巾、浴巾、方巾、…

【云原生】Kubernetes----Helm包管理器

目录 引言 一、Helm概述 1.Helm价值概述 2.Helm的基本概念 3.Helm名词介绍 二、安装Helm 1.下载二进制包 2.部署Helm环境 3.添加补全信息 三、使用Helm部署服务 1.创建chart 2.查看文件信息 3.安装chart 4.卸载chart 5.自定义chart服务部署 6.版本升级 7.版本…

微软Win11 22H2/23H2六月更新补丁KB5039212发布!附完整更新日志

系统之家于6月12日发出最新报道&#xff0c;微软向Win11用户推出六月更新补丁KB5039212&#xff0c;22H2版本号升为22621.3737&#xff0c;23H2版本号升为22631.3737。此次更新解决了可能导致系统无法从休眠状态恢复等问题。感兴趣的用户可以继续阅读以下内容&#xff0c;获取更…