Python面向对象基础概念

第一章、面向对象

一、总结

变量 / 属性当赋值一次后,若未重新赋值,其值一直不变

方法每调用一次,就执行一次

变量 / 属性不存在调用,只有方法才能调用

二、面向过程与面向对象

1、面向过程 Procedural Programming(蛋炒饭  耦合度高,扩展力低)

        面向过程编程(Procedural Programming)是一种程序设计范式,它将程序看作是一系列的步骤或过程,这些过程按照顺序逐步执行,从而完成任务或解决问题。在面向过程编程中,主要关注如何实现算法和控制流程,将程序分解为一系列的函数或过程,每个函数都完成一个特定的任务。

面向过程编程的特点包括:

  1. 顺序执行:程序按照指定的顺序逐步执行,每个步骤都依赖于前面步骤的结果。

  2. 函数或过程:程序被分解为多个函数或过程,每个函数负责执行特定的任务。

  3. 数据为参数:数据通常作为函数的参数传递给过程,函数对数据进行处理并返回结果。

  4. 重用性较低:在面向过程编程中,重用性相对较低,因为每个函数通常只完成一个特定的任务,难以灵活地重用代码。

        面向过程编程适用于一些简单的任务或小规模的程序,它注重程序的执行过程和算法实现,对于较大规模、复杂的程序,面向过程编程可能会导致代码难以维护和扩展。因此,在面向过程编程中,代码的可维护性和可扩展性可能较差。

2、面向对象 object-oriented(盖饭  耦合度低,扩展力高)

        面向对象编程(Object-Oriented Programming,简称OOP)是一种程序设计范式,它将程序看作是一组相互作用的对象,每个对象都有自己的数据和方法在面向对象编程中,重点是如何建立对象之间的关系,以及如何通过对象之间的交互来完成任务和解决问题。

面向对象编程的核心概念包括:

  1. 类(Class):类是对象的蓝图或模板,描述了一类对象所具有的共同属性和方法。它定义了对象的结构和行为。

  2. 对象(Object):对象是类的实例,表示具体的实体。每个对象都有自己的状态(属性)和行为(方法)。

  3. 封装(Encapsulation):封装是将数据和方法包装在一个单元(即类)中,限制对内部数据的直接访问,通过公共接口来访问和操作数据。

  4. 继承(Inheritance):继承是一种机制,允许一个类(子类)继承另一个类(父类)的属性和方法,从而实现代码的重用和扩展。

  5. 多态(Polymorphism):多态是一种能力,允许不同类的对象对相同的消息(方法调用)作出不同的响应,实现了接口的统一性和灵活性。(动作重载)

面向对象编程的优点包括:

  • 可维护性:面向对象编程提供了封装和抽象的机制,使得代码更易于维护和修改。

  • 可扩展性:通过继承和多态,可以实现代码的重用和扩展,增加新功能时不需要修改原有代码。

  • 高内聚低耦合:面向对象编程可以将数据和操作数据的方法封装在一个类中,使得类具有高内聚性,同时通过接口进行交互,减少类之间的依赖,实现低耦合性。

  • 提高开发效率:面向对象编程使得代码结构更加清晰和模块化,使得多人协作开发更加容易。

        Python是一种支持面向对象编程的多范式编程语言,它提供了丰富的面向对象编程特性,允许开发者使用类、对象、继承、多态等概念来组织和设计程序。面向对象编程在Python中得到广泛应用,许多Python标准库和第三方库都是基于面向对象编程构建的。

Python是面向对象的语言,支持面向对象编程,但也允许使用其他编程范式。它的灵活性使得开发者可以根据具体需求使用合适的编程范式,包括面向对象、面向过程和函数式编程。

3、类和对象的概念

类的定义:

  • 类是现实世界当中不存在的,是一个模板,是一个概念。是人类大脑抽象的结果
  • 类代表一类事物
  •  在现实世界中,对象A和对象B之间具有共同的特征,进行抽象总结出一个模板,称为类

对象的概念

  • 对象是现实世界中实际存在的个体。

软件开发的过程:

  • 程序员位于现实世界与虚拟实践的中间点,通过类和对象将现实世界中的东西在虚拟世界实现
  • 程序员先观察现实世界,从现实世界当中寻找对象
  • 寻找了N多个对象后,发现所有对象都有共同特征
  • 程序员在大脑中形成了一个模板(类)
  • 程序员可以通过Python代码来表述一个类
  • Python程序中有了类的定义
  • 可以通过类来创建对象
  • 有了对象之后,可以让对象直接协作起来形成一个系统

    类--(实例化)--》 对象

    对象--(抽象)--》类

    对象又被称为  实例 / instance

  •     类描述的是对象的共同特征
  •     在访问这些特征的时候,必须先创建对象
  •     一个类主要描述的是:状态 + 动作
    • 状态--》一个类的属性
    • 动作--》一个类的方法

    属性和方法具体到某个对象之后,可能最终的结果不一样

    对象和对象之间有共同特征,但是具体到对象之后有数据的差异

第二章、方法

一、概述

        在 Python 中,方法(Method)是指属于对象的函数。它们是面向对象编程的核心概念之一。方法与函数类似,但是它们是与特定对象相关联的。对象可以是 Python 中的任何数据类型,例如整数、字符串、列表、元组、字典等。

        方法的定义通常在类(Class)中,通过在类内部声明函数来创建方法。当对象调用方法时,方法将操作该对象的属性和行为。方法可以访问对象的状态并修改它们。

下面是一个简单的示例来说明方法的概念:

class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def bark(self):
        print(f"{self.name} is barking!")

    def introduce(self):
        print(f"My name is {self.name} and I am {self.age} years old.")

# 创建一个Dog对象
my_dog = Dog("Buddy", 3)

# 调用方法
my_dog.bark()
my_dog.introduce()

输出结果:

Buddy is barking!
My name is Buddy and I am 3 years old.

        在上面的示例中,bark()introduce() 都是 Dog 类的方法。bark() 方法输出狗的叫声,introduce() 方法输出狗的名字和年龄。这两个方法都可以访问对象的属性(self.nameself.age)。

总结来说,方法是与对象相关联的函数,它们定义在类中,并且可以操作对象的属性和行为。

一、函数与方法的区别

在 Python 中,函数(Function)和方法(Method)都是可执行的代码块,用于执行特定的任务或操作。它们之间的区别主要在于它们的调用方式和作用域:

  1. 调用方式:

    • 函数:函数是独立的代码块,可以在任何地方调用。函数调用使用函数名,后面跟着一对小括号,可以传递参数给函数。函数可以定义在模块层级或其他函数内部。
    • 方法:方法是与对象相关联的函数。它是对象的属性,因此需要通过对象来调用。方法调用使用对象名,后面跟着一个点号和方法名,然后再跟着一对小括号,可以传递参数给方法。方法只能在对象的上下文中调用。
  2. 参数传递:

    • 函数:函数可以接受零个或多个参数,参数通过函数定义的参数列表来传递。
    • 方法:方法的第一个参数通常是 self,它表示调用该方法的对象本身。当调用方法时,对象会作为第一个参数自动传递给方法,因此在方法的定义中需要包含 self 参数,用于访问对象的属性和方法。

下面是一个简单的示例来展示函数和方法的区别:

# 函数示例
def greet(name):
    return f"Hello, {name}!"

print(greet("Alice"))  # 函数调用,输出:Hello, Alice!

# 类示例
class Person:
    def __init__(self, name):
        self.name = name

    def greet(self):
        return f"Hello, {self.name}!"

person = Person("Bob")
print(person.greet())  # 方法调用,输出:Hello, Bob!

        在上面的示例中,我们定义了一个名为 greet 的函数,用于向给定的名字打招呼。然后,我们创建了一个名为 Person 的类,并在其中定义了一个方法 greet。通过调用函数和方法,我们可以得到相同的输出,但是调用方式和调用的对象有所不同。

总结:

  • 函数是独立的代码块,可在任何地方调用,不需要对象上下文。
  • 方法是与对象相关联的函数,必须在对象上下文中调用,并且第一个参数通常是 self,表示调用该方法的对象本身。

第三章、类

一、概述

Python中一切皆对象,一切皆类型,只要是属于该类型的变量(引用),都可以调该类型中所有的属性(包括实例和静态的)和方法

二、类的定义

语法结构:

class ClassName:

        # 数据成员(属性)的定义

        attribute1 = value1

        attribute2 = value2

        # 函数成员(方法)的定义

        def method1(self,arguments):

                #方法实现

                #可以访问属性和其他方法

                #可以使用传入的参数 arguments

        

        def method2(self,arguments):

                #方法实现

                #可以访问属性和其他方法

                #可以使用传入的参数 arguments

        #其他方法的定义

        #...          

Python语言中所有的class都属于引用数据类型,class后跟的“类名”为引用数据类型,因为通过class定义的是类,属于引用数据类型。

三、实例级别和类级别访问原则

实例级别   <   类级别

  • 访问方面
    • 小级别可以直接访问大级别的,大级别不能直接访问小级别的
  • 包含方面
    • 小级别中有大级别的信息,大级别中没有小级别的信息
      • 实例级别中有类级别的信息
      • 类级别中没有实例级别的信息

注意:类中的一切属性和方法,在类内类外都可以通过实例对象或self访问和修改

四、类中所有属性和方法都在运行时解析

当操作类中所有的属性和方法时,起决定性作用的是引用中保存的值,是在运行时确定

五、动态添加属性和方法

        在Python中,类的属性和方法通常在类定义时进行声明。然而,Python是一门灵活的语言,允许您在运行时动态地添加属性和方法到类或类的实例上,即在类定义时不声明,而是在调用时声明。

1、动态添加属性

在Python中,您可以通过直接给类或类的实例赋值来动态添加属性。例如:

class MyClass:
    pass

obj = MyClass()
obj.new_attribute = 42
print(obj.new_attribute)  # Output: 42

MyClass.new_class_attribute = "Hello"
print(MyClass.new_class_attribute)  # Output: Hello

        在上面的例子中,我们没有在类定义时声明new_attribute属性,而是在创建obj实例后动态地将其添加到了obj上。同样,我们也可以在类级别动态地添加new_class_attribute属性。

2、动态添加方法

您可以使用types.MethodType来动态地为类或类的实例添加方法。需要导入types模块。例如:

import types

class MyClass:
    def print_message(self):
        print("Hello, I am a method!")

def dynamic_method(self):
    print("This is a dynamic method!")

obj = MyClass()
obj.dynamic_method = types.MethodType(dynamic_method, obj)
obj.dynamic_method()  # Output: This is a dynamic method!

        在这个例子中,我们创建了一个名为dynamic_method的函数,并使用types.MethodType将其绑定到obj实例上,从而动态添加了一个方法。

        虽然Python允许在运行时动态添加属性和方法,但这样做可能会使代码更难维护和理解。通常情况下,最好在类定义时明确声明属性和方法,以便于代码的可读性和维护性。只有在某些特殊情况下,比如在动态生成类或进行元编程时,动态添加属性和方法才会更有用。

六、self参数和cls参数

1、self参数

  • self 是在类的实例方法中使用的第一个参数,它是一个约定俗成的名称,表示当前实例对象的引用(相当于java中的this)
  • 当我们调用实例方法时,Python会自动将实例对象作为第一个参数传递给 self,以便我们在方法内部访问实例的属性和调用实例的方法。
  • self 让我们能够在类的方法中对实例对象进行操作和管理。

示例:

class MyClass:
    def __init__(self, value):
        self.value = value

    def display_value(self):
        print(self.value)

obj = MyClass(42)
obj.display_value()  # 输出: 42

        在上述代码中,display_value() 方法中的 self 参数表示实例对象 obj 自身的引用。通过 self.value,我们可以访问并输出实例对象的属性值。

        在实例方法中,Python会自动将实例对象作为第一个参数传递给 self,它表示当前实例对象的引用。这意味着在调用实例方法时,你不需要显式地传递实参给 self例如:

class MyClass:
    def instance_method(self):
        print("This is an instance method.")

obj = MyClass()
obj.instance_method()  # Python会自动传递obj给self

2、cls参数

  • cls 是在类方法中使用的第一个参数,它也是一个约定俗成的名称,表示当前类的引用
  • 当我们定义一个类方法时,需要使用装饰器 @classmethod 来标记这个方法为类方法。类方法的第一个参数通常命名为 cls
  • cls 允许我们在类方法中访问类级别的属性,而不是实例对象的属性。

示例:

class MyClass:
    class_attr = 10

    @classmethod
    def class_method(cls):
        print(cls.class_attr)

MyClass.class_method()  # 输出: 10

在上述代码中,class_method() 是一个类方法,它接收 cls 参数表示类本身的引用。通过 cls.class_attr,我们可以访问类的属性 class_attr

        在类方法中,Python会自动将当前类作为第一个参数传递给 cls,它表示当前类的引用。这意味着在调用类方法时,你不需要显式地传递实参给 cls。要定义一个类方法,你需要使用装饰器 @classmethod 来标记这个方法为类方法。例如:

class MyClass:
    class_attr = 10

    @classmethod
    def class_method(cls):
        print("This is a class method.")
        print(cls.class_attr)

MyClass.class_method()  # Python会自动传递MyClass给cls

总结:在调用实例方法时,Python会自动传递实例对象作为第一个参数给 self;在调用类方法时,Python会自动传递类本身作为第一个参数给 cls。因此,你不需要手动传递这两个参数,Python会在方法调用时自动处理。

3、总结

  • self 代表当前实例对象的引用,在实例方法中使用。
  • cls 代表当前类的引用,在类方法中使用。
  • selfcls 都是约定俗成的名称,但它们实际上是普通的参数,你可以使用其他的名称代替,但通常为了代码的可读性和一致性,最好遵循这种约定。

七、属性

1、实例变量(实例属性)纯实例私有

Python中的实例属性通常是采用 self.变量名 的形式来完成定义的,为局部变量,在构造方法体中定义,声明和赋值必须同时,没有默认值

  • 实例属性是在类的构造方法 __init__() 中定义的属性
  • 实例属性是实例级别,属于类的实例对象而不属于类
  • 实例属性不被所有实例对象共享,每个实例对象都有自己的一组属性。
  • 访问实例属性:实例属性被实例对象所私有,类不能访问
    • 实例对象(引用).属性名
class MyClass:
    def __init__(self, instance_attr):
        self.instance_attr = instance_attr

# 创建两个实例对象
obj1 = MyClass("I am instance attribute 1.")
obj2 = MyClass("I am instance attribute 2.")

# 访问实例属性
print(obj1.instance_attr)  # 输出: I am instance attribute 1.
print(obj2.instance_attr)  # 输出: I am instance attribute 2.

#类级别不能访问实例级别的属性
#运行报错:AttributeError: type object 'MyClass' has no attribute 'instance_attr'
# print(MyClass.instance_attr)

2、静态变量(类属性)纯类私有

Python中类属性通常是采用一个变量的形式来完成定义的,为成员变量,在类体中,方法体外定义,声明和赋值必须同时,没有默认值

  • 类属性是在类体中定义的属性
  • 类属性是类级别,属于类本身而不属于任何实例对象
  • 访问类属性:类属性被类及其实例对象所共享
    • 类名.属性名
    • 实例对象(引用).属性名

注意:在 Python 中,如果通过实例对象修改类属性,其实是在实例对象中创建了一个同名的实例属性,并不会改变类属性本身。要修改类属性,需要通过类名来修改。

class MyClass:
    class_attr = "I am a class attribute."

# 访问类属性
print(MyClass.class_attr)  # 输出: I am a class attribute.

# 通过实例对象访问类属性
obj = MyClass()
print(obj.class_attr)  # 输出: I am a class attribute.

演示:self.静态变量 不会修改静态变量的值

class MyClass(object):
    num = 10
    num1 = 20
    count = 0

    def __init__(self):
        self.num += 1 #相当于新创建了一个实例变量,并不会对原来的静态变量进行修改
        self.num1 = 90  #相当于新创建了一个实例变量,并不会对原来的静态变量进行修改

        # 比如此处 须使用类名.静态变量 来调用修改静态变量
        # 使用self.静态变量 不会修改
        MyClass.count += 1 #特殊情况:对实例对象进行计数


    def print_count(self):
        print(self.num)
        print(MyClass.num)
        print(self.num1)
        print(MyClass.num1)
        print(MyClass.count)

myclass1 = MyClass()
myclass2 = MyClass()
myclass3 = MyClass()

myclass2.print_count()
"""
11
10
90
20
3
"""

3、声明为实例变量和静态变量的原则

  • 声明为实例变量:
    •  所有对象都有这个属性,但是这个属性的值会随着对象的变化而变化《不同对象的这个属性具体的值不同]
  • 声明为静态变量:
    • 所有对象都有这个属性,并目所有对象的这个属性的值是一样的,建议定义为静态变量,节省内存的开销。

静态变量在类定义的时候初始化,内存在方法区中开辟。访问的时候不需要创建对象,直接使用“类名 . 静态变量名"的方式访问。

八、方法

1、实例方法  纯实例私有

        在Python类中,实例方法是定义在类中的普通方法,它们的第一个参数通常被命名为self,用于表示类的实例对象。实例方法在调用时会自动传入实例对象作为第一个参数,因此可以通过self来访问和修改实例的属性以及调用其他实例方法。

可以理解为:self参数是实例方法的标记,只有实例对象才能调用,类不能调用

  • 实例方法存储在堆内存中的实例中,类内类外都通过  引用.方法名()  调用
  • 实例方法可直接访问实例变量和实例方法,有“当前对象”(self对象)

  • 也能访问静态变量和静态方法或类方法,通过  类名.变量名/方法名 (或者self.变量名/方法名)

示例代码:

class Dog:
    category = "狗"

    def __init__(self, name, age):
        self.name = name
        self.age = age

    @staticmethod
    def drink():
        print("正在喝水",end=" ")

    def bark(self):
        Dog.drink()
        print(Dog.category,end=" ")
        print(f"{self.name} is barking!")

    def get_age_in_human_years(self):
        return self.age * 7


# 创建类实例
dog1 = Dog("Buddy", 3)
dog2 = Dog("Charlie", 5)

# 调用实例方法
dog1.bark()  # 输出: 正在喝水 狗 Buddy is barking!
dog2.bark()  # 输出: 正在喝水 狗 Charlie is barking!

# 调用实例方法获取狗的年龄对应的人类年龄
print(dog1.get_age_in_human_years())  # 输出: 21
print(dog2.get_age_in_human_years())  # 输出: 35

        需要注意的是,实例方法是与类的实例对象绑定的,因此每个实例对象都有一份属于自己的实例方法。当通过实例对象调用实例方法时,Python会自动传入对应的实例对象作为第一个参数。

1.1、实例方法的特殊调用方式(不推荐)
# 作   者:JZQ
# 开发时间:2023/8/16 9:50

class MyClass(object):
    def doSome(self):
        print("doSome")

if __name__ == '__main__':
    #一般调用方式
    myclass = MyClass()
    myclass.doSome()

    #特殊调用方式
    #采用类来调用,手动传入实例对象作为参数
    MyClass.doSome(myclass)
    """
    doSome
    doSome
    """

2、静态方法 纯类私有

        在Python类中,静态方法是定义在类中的一种特殊方法,使用@staticmethod装饰器来声明。与实例方法不同,静态方法不依赖于类的实例对象或类本身,在调用时不会自动传入实例或类作为第一个参数。

  • 静态方法存储在方法区内存中的class对象中,类内类外都通过  类名.方法名()   调用
  • 静态方法只初始化一份,存储在方法区的class对象中,属于类级私有,所有实例共同的方法
    • 实例对象与类共享:也可通过  实例对象(引用). 方法名()  调用
  • 静态方法可直接访问静态变量和静态方法和类方法,没有“当前对象”(self对象)
  • 要在静态方法中访问实例变量和方法,必须先创建实例对象,然后使用实例来访问

        静态方法通常用于实现与类相关的功能,但不需要访问实例或类的状态。它们类似于类的工具函数,可以直接通过类名来调用,无需创建类的实例。静态方法在类的所有实例之间共享,因为它们不绑定到特定的实例。

        静态方法的定义中不需要传入selfcls这样的特殊参数,因为它们不依赖于实例或类。如果需要在方法内部使用类的属性或方法,应该直接使用类名来访问,而不是通过selfcls

示例代码:

class MathUtils:
    #静态变量
    a = 10
    #实例变量
    def __init__(self):
        self.cc = 20

    @staticmethod
    def add(x, y):
        #在静态方法中调用实例方法或实例变量,需要先实例化对象
        m = MathUtils()
        print(m.cc)

        #可直接访问静态变量
        print(MathUtils.a)

        #可直接访问类方法
        print(MathUtils.sub(x,y))

        #可直接访问静态方法
        print(MathUtils.multiply(1, 2))
        return x + y

    @classmethod
    def sub(cls,x, y):
        return x - y

    @staticmethod
    def multiply(x, y):
        return x * y

# 调用静态方法
result1 = MathUtils.add(5, 3)
result2 = MathUtils.multiply(4, 6)

print(result1)  # 输出: 8
print(result2)  # 输出: 24

        在上面的例子中,MathUtils类定义了两个静态方法addmultiply,它们分别用于执行加法和乘法操作。我们可以直接通过类名调用这些静态方法,无需创建类的实例。

        需要注意的是,静态方法并不是类的必需组成部分,它们是一种在类中组织代码的方式,用于更好地组织相关的功能。如果方法需要访问实例属性或进行与实例状态相关的操作,应该使用实例方法;如果方法需要访问类属性或进行与类相关的操作,应该使用类方法或静态方法。选择不同类型的方法取决于方法需要的功能和访问权限。

3、类方法  纯类私有

        在 Python 中,类方法(Class Method)是一种特殊的方法,它与类绑定而不是与实例对象绑定。类方法使用 @classmethod 装饰器来定义,第一个参数通常是类本身,通常以 cls 作为参数名。

  • 类方法存储在方法区内存中的class对象中,类内类外都通过  类名.方法名()   调用
  • 类方法只初始化一份,存储在方法区的class对象中,属于类级私有,所有实例共同的方法
    • 实例对象与类共享:也可通过  实例对象(引用). 方法名()  调用
  • 类方法可直接访问静态变量和静态方法和类方法,没有“当前对象”(self对象)
  • 要类方法中访问实例变量和方法,必须先创建实例对象,然后使用实例来访问

下面是一个类方法的示例:

class MyClass:
    #静态变量(属性)
    class_attr = "I am a class attribute."
    static_attr = "I an a static attribute."

    #构造方法
    def __init__(self, instance_attr):
        #实例变量
        self.instance_attr = instance_attr

    # 类方法的定义
    @classmethod
    def class_method(cls):
        print("This is a class method.")
        #直接访问静态变量
        print("Class attribute:", cls.class_attr)
        #直接访问静态方法
        MyClass.static_method()
        cls.static_method()
        print("=============================")
    # 实例方法的定义
    def instance_method(self):
        print("This is an instance method.")
        print("Instance attribute:", self.instance_attr)

    #静态方法定义
    @staticmethod
    def static_method():
        print("This is a static method.")
        print("Static attribute:",MyClass.static_attr)

# 创建实例对象
obj = MyClass("I am an instance attribute.")

# 调用类方法
MyClass.class_method()
# 输出:
"""
This is a class method.
Class attribute: I am a class attribute.
This is a static method.
Static attribute: I an a static attribute.
This is a static method.
Static attribute: I an a static attribute.
"""

# 通过实例对象调用类方法
obj.class_method()
# 输出:
"""
This is a class method.
Class attribute: I am a class attribute.
This is a static method.
Static attribute: I an a static attribute.
This is a static method.
Static attribute: I an a static attribute.
"""

# 调用实例方法
obj.instance_method()
# 输出:
# This is an instance method.
# Instance attribute: I am an instance attribute.

        在上面的例子中,我们定义了一个 MyClass 类,其中包含一个类方法 class_method() 和一个实例方法 instance_method()。类方法通过 @classmethod 装饰器进行定义,并使用 cls 参数来访问类的属性。实例方法则通过 self 参数来访问实例对象的属性。

        总结:类方法是一种与类绑定的方法,使用 @classmethod 装饰器来定义。类方法与实例方法的区别在于参数和调用方式。类方法可以通过类名调用,也可以通过类的实例对象调用,但不能访问实例的属性。类方法常用于实现与类相关的功能和操作。

3.1、静态方法与类方法的区别和联系

        由于 Python 是一种动态语言,没有强制的访问控制机制,因此在实际使用中,静态方法和类方法之间的区别可能会更加模糊。有时候,开发者可以根据习惯或特定场景来选择使用静态方法还是类方法。

        在实践中,类方法通常用于实现与类相关的功能,而静态方法通常用于实现独立于类和实例的功能,不依赖于类的状态和实例对象的属性。但由于 Python 的灵活性,你可以根据需要来选择使用静态方法或类方法,根据具体的业务逻辑和编程风格进行决策。

区别:

  1. 参数:

    • 静态方法:静态方法没有特殊的第一个参数,它不需要引用类本身(即没有 selfcls 参数)。
    • 类方法:类方法的第一个参数通常是 cls,指向类本身。
  2. 访问成员:

    • 静态方法:没有类或实例的上下文,也可以直接访问,静态变量,静态方法,类方法,但不能直接访问实例变量和实例方法
    • 类方法:没有实例的上下文,但有cls,类的上下文。也可以直接访问,静态变量,静态方法,类方法,但不能直接访问实例变量和实例方法
  3. 使用装饰器:

    • 静态方法和类方法都使用装饰器进行定义。静态方法使用 @staticmethod 装饰器,而类方法使用 @classmethod 装饰器。

联系:

  1. 绑定类型:

    • 静态方法和类方法都是与类绑定的方法,而不是与实例对象绑定。它们可以在不创建实例对象的情况下调用。
  2. 调用方式:

    • 静态方法:静态方法可以通过类名调用,也可以通过类的实例对象调用
    • 类方法:类方法可以通过类名调用,也可以通过类的实例对象调用

4、构造方法

        在 Python 类中,构造方法是一种特殊的方法,用于在创建类的实例对象时进行初始化操作。构造方法的名称是 __init__(),它在实例化对象时自动被调用。通过构造方法,我们可以给类的实例对象设置初始状态,为实例对象添加属性,并执行其他必要的初始化工作。

  • 构造方法既不属于实例方法,也不属于静态方法,是一种特殊的方法,主要用于实例对象初始化中
    • 实例变量全部且必须在构造方法中完成初始化赋值
    • Python中实例变量不用先在类体中声明,而是在构造方法中直接使用 self.实例变量名 声明并赋值(初始化)
  • 构造方法存储在方法区内存中的class对象中,由PVM自动调用创建实例对象,不能使用  引用.  来调用

构造方法有以下特点:

  1. 名称固定:构造方法的名称必须是 __init__,其中前后各有两个下划线。

  2. 自动调用:当创建类的实例对象时,构造方法会自动被调用,不需要手动调用。

  3. 参数:构造方法的第一个参数通常是 self,用于指向类的实例对象。在构造方法中,我们可以通过 self 来访问和操作实例对象的属性。

下面是一个简单的构造方法示例:

class MyClass:
    def __init__(self, name, age):
        self.name = name
        self.age = age

# 创建类的实例对象
obj1 = MyClass("Alice", 30)
obj2 = MyClass("Bob", 25)

# 访问实例对象的属性
print(obj1.name)  # 输出: Alice
print(obj1.age)   # 输出: 30

print(obj2.name)  # 输出: Bob
print(obj2.age)   # 输出: 25

        在上面的例子中,我们定义了一个 MyClass 类,其中的构造方法 __init__ 接受两个参数 nameage。当我们创建类的实例对象 obj1obj2 时,构造方法会自动被调用,并且通过构造方法初始化了实例对象的属性 nameage

        构造方法是在类的实例对象创建时自动执行的,它允许我们在创建对象时为对象提供初始数据,并进行一些初始化工作,使得对象在创建后具备一定的状态。

4.1、构造方法不止可以给实例变量赋值,也可给静态变量赋值

class Child(Parent):
    i = 10

    def __init__(self, name, age = None):
        super().__init__(name)  # 调用父类的构造方法来初始化name属性
        self.i = 20  #相当于在实例对象中新建了一个实例变量i,并不会修改原来的静态变量
        # Child.i = 30 #会修改原来的静态变量
        if age is None:
            print(123)

5、声明为实例方法,静态方法,类方法的原则

        在 Python 中,类中的方法可以声明为实例方法、静态方法和类方法,选择声明哪种类型的方法取决于该方法需要访问的类和实例成员以及是否需要类的上下文。以下是每种类型方法的原则和适用情况:

  1. 实例方法(Instance Method):

    • 声明方式:在方法定义中,第一个参数通常是 self,指向类的实例对象。
    • 原则:实例方法经常用于访问和操作实例对象的属性,并且可以访问类的属性和调用其他实例方法。
    • 适用情况:当方法需要访问和操作实例对象的属性,或需要调用其他实例方法时,应该声明为实例方法。实例方法可以访问实例对象和类的属性,也可以访问全局变量和其他模块的内容。
  2. 静态方法(Static Method):

    • 声明方式:在方法定义中,使用 @staticmethod 装饰器,没有特殊的第一个参数(不需要 selfcls 参数)。
    • 原则:静态方法是与类绑定的方法,它不需要实例对象的上下文,并且不能访问实例对象的属性。它主要用于实现独立于实例对象和类的功能,不依赖于类的状态。
    • 适用情况:当方法不需要访问和操作实例对象的属性,也不需要访问类的属性时,应该声明为静态方法。静态方法可以用于创建工具函数或辅助功能,它不依赖于具体的实例对象。
  3. 类方法(Class Method):

    • 声明方式:在方法定义中,使用 @classmethod 装饰器,第一个参数通常是 cls,指向类本身。
    • 原则:类方法是与类绑定的方法,它可以访问类的属性,但不能直接访问实例对象的属性。类方法主要用于操作类的属性和实现与类相关的功能。
    • 适用情况:当方法需要访问类的属性,但不需要直接访问实例对象的属性时,应该声明为类方法。类方法常用于实现与类相关的工厂方法、属性访问和操作等功能。

        总结:选择声明方法的类型取决于方法需要访问的类和实例成员,以及方法是否依赖于类的状态。实例方法适用于操作实例对象的属性和方法;静态方法适用于独立于实例对象和类的功能;类方法适用于操作类的属性和实现与类相关的功能。正确选择不同类型的方法有助于更好地组织和设计类的结构,使代码更具可读性和可维护性。

九、类初始化

1、Python中类当解释器遇到class关键字进行类定义时会进行类初始化,类只在定义时时初始化一次,存储在方法区的class对象中,属于类级私有,所有实例共同的属性和方法

2、类初始化时初始化所有静态变量和静态方法和类方法,其他实例变量和实例方法在对象初始化时进行

注意:

  • 在 Python 中,类的加载和初始化是在解释器解析类定义时自动进行的,并没有类似于 Java 中的静态代码块。类定义中的类属性和静态方法,类方法会在类加载时创建
  • 当定义一个类时,Python会创建一个类对象,类中定义的静态属性和静态方法和类方法都属于这个类对象,而不是类的实例对象(instance object)。这意味着这些静态属性和静态方法在所有类的实例之间共享,并且它们不依赖于类的实例化。

        在Python中,当解释器遇到class关键字进行类定义时会进行类初始化,会在方法区(Method Area)中进行类的初始化。类的初始化是PVM在首次使用该类时进行的,主要包括静态变量,静态方法和类方法的初始化。以下是Python中类的初始化过程: 

  1. 类的加载:

    • 当 Python 解释器遇到类定义时,会加载类的定义,并创建类对象。类对象是一个特殊的对象,它代表了该类的整体,可以通过类名访问类的属性和方法。
  2. 类属性和方法的创建:

    • 在类加载过程中,类的属性和方法会被创建。类属性是定义在类体中的变量,它属于类本身而不是类的实例对象。类方法是定义在类体中的方法,它是与类绑定的方法,可以通过类名或实例对象进行调用。
  3. 初始化方法的查找:

    • 在类加载过程中,解释器会查找是否定义了特殊名称的初始化方法 __init__。如果类中包含了 __init__ 方法,它会成为类的构造方法,在创建类的实例对象时被调用。

第四章、访问权限控制符

一、概述

        在Python中,没有像其他编程语言(如Java)中的严格访问权限控制符(例如public、private、protected)那样直接的概念。Python采用了一种更灵活的方式来管理访问权限,主要通过命名约定和属性装饰器来实现。

二、公有访问控制(Public Access Modifier):

  • 默认情况下,Python 中的类成员都是公有的,可以在类的内部和外部访问。
  • 在类的方法中通过 self 在类内来访问和修改类的公有属性,也可以通过实例对象在类外来访问和修改
class MyClass:
    def __init__(self):
        self.public_variable = 42

    def public_method(self):
        return "This is a public method."

obj = MyClass()
print(obj.public_variable)  # 可以访问公有变量
print(obj.public_method())  # 可以调用公有方法

三、受保护访问控制(Protected Access Modifier):

  • 在类的属性或方法名前添加一个下划线 _,即可将其设置为受保护的成员,只能在类的内部和子类中访问
    • 相当于java中的protected
  • 受保护成员可以通过 self._protected_member 在类的内部访问,但在子类中也可以访问。
class MyClass:
    def __init__(self,value):
        self._protected_variable = value

    def _protected_method(self):
        return "This is a protected method."

class MySubclass(MyClass):
    def access_protected_member(self):
        return self._protected_variable  # 子类可以访问受保护变量

obj = MySubclass(42)
print(obj.access_protected_member())  # 可以从子类访问受保护变量

四、私有访问控制(Private Access Modifier):

  • 在类的属性或方法名前添加两个下划线 __,即可将其设置为私有成员,只能在类的内部访问,外部无法直接访问。
    • 相当于java中的private
  • 私有成员可以通过self.__private_member在类的内部访问。
class MyClass:
    def __init__(self):
        self.__private_variable = 42

    def __private_method(self):
        return "This is a private method."

obj = MyClass()

# 以下代码会引发错误,因为私有变量无法直接从类的外部访问
# print(obj.__private_variable)
# print(obj.__private_method())

# 如果要访问私有成员,可以通过名称修饰(Name Mangling)的方式实现:
print(obj._MyClass__private_variable)  # 可以通过这种方式访问私有变量
print(obj._MyClass__private_method())  # 可以通过这种方式调用私有方法

        请注意,尽管Python使用双下划线作为私有成员的命名约定,但这只是一种约定,实际上Python中的私有成员仍然是可以从外部访问的,只不过不建议这么做,因为这会破坏类的封装性。 

五、三种访问权限命名约定之间的关系

        在Python中,没有严格的访问权限控制符。类的设计者可以通过命名约定来指示成员的访问权限,但这仅是一种约定,并不会强制执行。建议遵循良好的编程实践,尽量避免直接访问私有成员,以确保类的封装性和数据安全性。

第五章、类封装

一、概述

        在面向对象编程中,封装是一种将数据和方法打包在一个单元中的概念,使其对外部用户隐藏实现的细节,只暴露一些必要的接口供外部使用。在 Python 中,类的封装是通过访问控制来实现的,它使用不同的属性访问限定符来控制类成员的可见性

二、属性封装(针对实例属性)

1、一般针对实例属性,静态属性也可以封装

在 Python 中,属性封装针对的是实例属性,而不是静态属性。

因为静态属性是所有对象所共享的,应该是public公共的,私有化没有意义

        实例属性是定义在类的实例对象上的属性,每个实例对象都可以拥有不同的属性值。属性封装通过访问控制符(例如私有属性)来限制外部直接访问和修改实例属性,从而隐藏实现细节,只暴露必要的接口供外部使用。

        静态属性(类属性)是定义在类本身上的属性,它属于类而不是类的实例对象。静态属性在所有实例对象之间是共享的,每个实例对象的静态属性都指向同一个内存地址。因为静态属性是与类绑定的,所以它不需要通过实例对象来访问,而是直接通过类名来访问。

        虽然静态属性在类的定义中声明,但它并不属于类的封装范畴。静态属性的访问不受访问控制符的限制,因为它不属于类的实例对象。任何外部用户都可以直接通过类名来访问静态属性。

2、封装的步骤

  • __init__构造方法中采用  self.__实例变量名  的方式将实例属性私有化
    • 相当于java中的private关键字进行修饰,private表示类和该类的实例共有的,修饰的所有数据只能在本类中访问.
  • 对外提供简单的操作入口,也就是说以后外部程序想要访问私有属性,必须通过这些简单的入口进行访问
    • 对外提供两个公开的方法,分别是set方法和cet方法
    • 想修改age属性,调用set方法
    • 想读取age属性,调用get方法
  • setter方法命名规范

def  set__private_var(self, value):

        #编写业务代码进行控制

        self.__private_var = value

  • getter方法命名规范

def  get__private_var(self):

        return self.__private_var

class MyClass:
    def __init__(self):
        self.__private_var = None

    # @property
    def get__private_var(self):
        return self.__private_var

    # @private_var.setter
    def set__private_var(self, value):
        if value < 0:
            raise ValueError("值不能为负数")
        self.__private_var = value

# 创建类的实例对象
obj = MyClass()

# 使用getter方法获取实例属性
print(obj.get__private_var()) #None

# 使用setter方法设置实例属性
obj.set__private_var(20)
print(obj.get__private_var()) #20

3、强制在类外访问封装后属性

语法:

访问private属性:实例对象 . _类名__实例变量名

访问protected属性:实例对象._实例变量名

class MyClass:
    def __init__(self, public_var, private_var, protected_var):
        #public公共的,哪都可以访问
        self.public_var = public_var
        #private私有的,只能在本类访问
        self.__private_var = private_var
        #protected受保护,只能在本类及其子类和本模块中访问
        self._protected_var = protected_var

    def public_method(self):
        print("This is a public method.")
        print("Public attribute:", self.public_var)
        print("Private attribute:", self.__private_var)
        print("Protected attribute:", self._protected_var)

# 创建类的实例对象
obj = MyClass("Public", "Private", "Protected")

# 访问公有成员
print(obj.public_var)          # 输出: Public
obj.public_method()
# 输出:
# This is a public method.
# Public attribute: Public
# Private attribute: Private
# Protected attribute: Protected

# 访问私有成员(注意:不建议直接访问私有成员,这里仅作演示)
print(obj._MyClass__private_var)   # 输出: Private

# 访问受保护成员(注意:不建议直接访问受保护成员,这里仅作演示)
print(obj._protected_var)       # 输出: Protected

三、方法封装(针对实例方法)

1、一般针对实例方法,静态方法,类方法也可以封装

在 Python 中,方法封装针对的是实例方法,而不是静态方法。

因为静态方法是所有对象所共享的,应该是public公共的,私有化没有意义

一般很少封装方法,只封装属性

2、封装的步骤

要在Python中封装方法,需要遵循以下步骤:

  1. 将方法设置为私有(private):这样该方法就只能在当前类内部访问,无法被其他类访问。

  2. 提供公共的方法(public method):这些公共方法可以被其他类访问,并且这些方法可以访问私有方法。

class MyClass:

    def __private_method(self):
        return "This is a private method."

    def public_method(self):
        return self.__private_method()

obj = MyClass()
print(obj.public_method()) #This is a private method.

3、强制在类外访问封装后方法

语法:

访问private方法:实例对象 . _类名__实例方法名()

访问protected方法:实例对象._实例方法名()

class MyClass:

    def _protected_method(self):
        return "This is a protected method."

    def __private_method(self):
        return "This is a private method."

obj = MyClass()
print(obj._protected_method()) #This is a protected method.
print(obj._MyClass__private_method()) #This is a private method.

四、私有的属性和方法不能被子类继承

        在 Python 中,私有属性和方法是带有双下划线 __ 前缀的属性和方法(例如 __private_attribute__private_method())。这些私有成员在类外部是不可访问的,包括子类。子类不能继承或访问父类中的私有属性和方法。

让我们看一个示例来说明这一点:

class Parent:
    def __init__(self):
        self.__private_attribute = 42

    def __private_method(self):
        print("This is a private method in Parent.")

class Child(Parent):
    def access_private_attribute(self):
        # 试图在子类中访问父类的私有属性(这是不允许的)
        # print(self.__private_attribute)  # 会引发 AttributeError

    def call_private_method(self):
        # 试图在子类中调用父类的私有方法(这是不允许的)
        # self.__private_method()  # 会引发 AttributeError
        pass

        在上面的例子中,Parent 类有一个私有属性 __private_attribute 和一个私有方法 __private_method()。子类 Child 试图在 access_private_attribute() 方法中访问父类的私有属性和在 call_private_method() 方法中调用父类的私有方法,但这是不允许的,因为私有成员是不可继承和访问的。

        虽然子类无法继承或访问父类的私有成员,但可以通过父类提供的公共接口(如公共方法)来访问和操作这些私有成员。这是封装的一部分,使得类的内部实现细节对外部类和子类都是隐藏的。

第六章、类继承

一、概述

继承是面向对象的三大特征之一,分别是封装,继承,多态

1、继承的作用:

继承“基本"的作用是:代码复用。

最"重要”的作用是:有了继承才有了以后"方法的覆盖”和”多态机制”。

  • 代码重用:继承允许子类继承父类的属性和方法,从而使子类可以重用父类的代码,减少了代码的冗余。这样可以提高代码的可维护性和扩展性,避免重复编写相似的代码。
  • 继承层次:通过继承,可以创建类之间的继承层次结构。子类可以继续被其他类继承,形成更复杂的继承关系,这样可以更好地组织和管理代码。
  • 代码抽象:通过继承,可以将通用的属性和方法放在父类中,而子类只需要实现特定的功能。这样可以实现代码的抽象,将类的通用特性和具体实现分离,使代码更加清晰和易于理解。
  • 多态性:继承是实现多态性的基础。多态性允许在父类的引用上调用子类的方法,从而实现不同对象对同一消息的响应。这样可以提高代码的灵活性和可扩展性。

2、继承语法格式

class  类名 (父类名):#若未显示继承则()可省略,默认继承object

        #子类的定义部分

        #可以包含属性和方法的定义 

子类将继承父类的属性和方法,这意味着子类可以直接使用父类中定义的属性和方法,无需重新编写相同的代码。子类也可以重写父类的方法或添加新的属性和方法,以满足子类自身的需求。

3、Python支持多继承

        在Python中,多继承是指一个子类可以同时继承多个父类。这意味着子类可以从多个父类中继承属性和方法。多继承的语法允许在定义子类时,在括号内用逗号分隔指定多个父类。子类将按照从左到右的顺序继承这些父类的特性。

class ChildClass(ParentClass1, ParentClass2, ...):
    # 子类的定义部分
    # 可以包含属性和方法的定义
    pass

示例:

class Animal:
    def speak(self):
        return "Animal speaks."

class Flyable:
    def fly(self):
        return "Can fly."

class Dog(Animal, Flyable):  # Dog类同时继承Animal类和Flyable类
    def speak(self):
        return "Dog barks."

dog = Dog()
print(dog.speak())  # 输出:"Dog barks."
print(dog.fly())    # 输出:"Can fly."

        在上面的例子中,Dog类同时继承了Animal类和Flyable类,通过多继承,Dog类可以使用Animal类和Flyable类中定义的方法。

        多继承的优点是可以在一个子类中获取多个父类的功能,使得代码更加灵活和可复用。然而,多继承也带来一些挑战和注意事项:

  1. 命名冲突:如果多个父类中存在相同的方法名,子类在调用时可能出现冲突。在这种情况下,子类会按照从左到右的顺序使用第一个匹配到的方法。可以通过调整继承顺序或者使用super()函数来解决冲突。

  2. 复杂性:多继承会增加代码的复杂性,尤其在继承层次较深或者父类之间的关系较复杂时,代码的理解和维护可能变得困难。因此,应该谨慎使用多继承,并遵循适当的设计原则。

  3. 方法解析顺序(Method Resolution Order,MRO):Python中有一个称为MRO的算法,用于确定多继承的方法解析顺序。子类的方法调用会按照MRO的顺序查找匹配的方法。可以通过类名.__mro__或者子类的mro()方法来查看MRO顺序。

print(Dog.__mro__) #(<class '__main__.Dog'>, <class '__main__.Animal'>, <class '__main__.Flyable'>, <class 'object'>)
print(Dog.mro()) #[<class '__main__.Dog'>, <class '__main__.Animal'>, <class '__main__.Flyable'>, <class 'object'>]

        总之,多继承是Python面向对象编程中的一项强大功能,允许子类从多个父类中继承特性,提高了代码的灵活性和复用性。但是,要注意合理使用多继承,并在可能的情况下优先考虑使用单继承,以避免潜在的问题和复杂性。

4、关于继承中的一些术语

B类继承A类,其中:

  • A类称为:父类、基类、超类、superclass
  • B类称为:子类、派生类、subclass

5、子类继承父类的数据:

私有的不支持继承

构造方法不支持继承

其它数据都可以被继承,属性(实例变量,静态变量)方法(实例方法,静态方法)

  • 实例变量:子类可以继承父类中的所有属性。这意味着子类可以直接访问和使用父类中定义的实例变量。

  • 实例方法:子类可以继承父类中的所有方法。这使得子类可以直接调用父类的方法,无需重新编写相同的代码。

  • 静态变量):如果父类有类变量,子类也可以继承父类的类变量。子类可以通过类名或实例访问这些类变量。

  • 静态方法:子类可以继承父类中的静态方法。子类可以直接调用父类的静态方法。

  • 类方法:子类可以继承父类中的类方法。子类可以通过类名调用父类的类方法。

        需要注意的是,子类继承父类的方法时,子类可以对这些方法进行重写(即覆盖),从而实现多态性。重写父类的方法允许子类根据自身的需要来实现特定的功能,而不改变方法的调用方式。

示例:

class Animal:
    class_variable = "Animal class variable"

    def __init__(self, name):
        self.name = name

    def speak(self):
        return f"{self.name} speaks."

    @staticmethod
    def static_method():
        return "This is a static method."

    @classmethod
    def class_method(cls):
        return f"This is a class method of {cls.__name__}."

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)  # 调用父类的初始化方法
        self.breed = breed

    def speak(self):  # 重写父类的speak方法
        return f"{self.name} barks."

dog = Dog("Buddy", "Golden Retriever")
print(dog.speak())        # 输出:"Buddy barks."
print(dog.class_variable) # 输出:"Animal class variable"
print(dog.static_method()) # 输出:"This is a static method."
print(dog.class_method())  # 输出:"This is a class method of Dog."

        在上面的例子中,Dog类继承了Animal类,从父类中继承了属性(class_variable)、方法(speak()static_method()class_method())。同时,Dog类重写了父类的speak()方法,实现了多态性,使得Dog类的speak()方法返回不同的结果。 

6、间接继承

        在Python中,间接继承是指一个类通过其他中间类来间接地继承其他类的属性和方法。这种继承链中,子类并不直接继承父类,而是通过一个或多个中间类来继承。

让我们用示例来说明间接继承:

class Animal:
    def speak(self):
        return "Animal speaks."

class Mammal(Animal):
    def run(self):
        return "Mammal runs."

class Dog(Mammal):
    def fetch(self):
        return "Dog fetches."

dog = Dog()

print(dog.speak())  # 间接继承Animal类的speak方法
print(dog.run())    # 间接继承Mammal类的run方法
print(dog.fetch())  # Dog类自己定义的fetch方法

        在上面的例子中,我们定义了三个类:AnimalMammalDogMammal继承自AnimalDog继承自Mammal。这样,Dog类间接继承了Animal类和Mammal类的属性和方法。

        当我们创建Dog类的实例(dog对象)时,dog可以直接调用Mammal类和Animal类中定义的方法,例如run()speak()。同时,Dog类也可以定义自己的方法,例如fetch()

        间接继承使得类之间的继承关系更加灵活和可扩展。通过在继承链中添加中间类,我们可以实现更复杂的继承结构,将类的功能划分得更加清晰。这种设计模式有助于提高代码的可维护性和扩展性。

        需要注意的是,虽然间接继承是一种有用的工具,但过度的嵌套和复杂的继承层次可能会导致代码难以理解和维护。因此,应该根据实际情况谨慎使用间接继承,并确保代码结构合理和简洁。

7、所有类均默认继承Object类

Python语言中假设一个类没有显示的继承任何类该类

  • 默认继承object 类
  • Python语言中任何一个类中都有object类的特征。

        在Python中,所有类默认情况下都会隐式地继承自内置类object。这种继承称为“新式类”,而在早期的Python版本中,可能还存在“经典类”,它们没有隐式继承自object

在 Python 3.x 中,所有类都是新式类,因此它们都隐式继承自object。例如:

class MyClass:
    pass

在上面的代码中,MyClass 隐式继承自object,因为没有指定显式的父类。这等效于:

class MyClass(object):
    pass

8、继承链

若实例对象或类调用的实例属性或静态属性和实例方法或静态方法或类方法在子类中没有,则从父类中查找,一级一级向上,直到object类

        当在Python中定义类并使用继承时,类之间可以形成一种继承关系。这种继承关系构成了所谓的继承链,它描述了类之间的层次结构。

        在继承链中,有一个特殊的类称为基类(也叫父类或超类)。所有其他类都可以从基类继承属性和方法。这样的类称为子类(也叫派生类)。子类可以直接继承自基类,也可以是其他子类的子类,从而形成多层次的继承关系。

        当一个类继承自另一个类时,它拥有并继承了父类的所有属性和方法。这意味着子类可以重用父类的代码,并且还可以添加新的属性和方法,以满足自身的需求。子类可以通过调用父类的方法来扩展或重写父类的行为。

        继承链的结构类似于一个树状结构,其中根节点是基类,子类分布在不同的层次。每个子类可以有自己的子类,从而形成一个层层嵌套的结构。

        继承链的好处在于它允许代码的重用和组织。在基类中定义通用的属性和方法,然后在子类中实现特定的行为,可以使代码更加模块化和可维护。

        需要注意的是,继承链的设计应该遵循合理的逻辑和实际需求。过于深层次的继承关系可能导致复杂性增加,难以理解和维护。因此,在设计继承链时,应该考虑代码的简洁性和可读性,并避免过度复杂的继承结构。

class Animal:
    def __init__(self, name):
        self.name = name

    def make_sound(self):
        pass

class Dog(Animal):
    def make_sound(self):
        return "Woof!"

class Cat(Animal):
    def make_sound(self):
        return "Meow!"

class Bulldog(Dog):
    def make_sound(self):
        return "Bark!"

class PersianCat(Cat):
    def make_sound(self):
        return "Purr!"

在这个例子中,我们有一个基类 Animal,它有一个子类 Dog 和另一个子类 Cat。然后,Dog 又有一个子类 BulldogCat 也有一个子类 PersianCat

继承链如下所示:

Animal -> Dog -> Bulldog
       -> Cat -> PersianCat

在这个继承链中:

  • Animal 是所有类的根类或基类。
  • DogCat 继承自 Animal,它们是直接子类。
  • Bulldog 继承自 Dog,它是 Dog 的子类,同时也是 Animal 的孙子类。
  • PersianCat 继承自 Cat,它是 Cat 的子类,同时也是 Animal 的孙子类。

当我们创建一个实例时,子类可以访问其父类的方法和属性。例如:

bulldog = Bulldog("Rocky")
print(bulldog.name)        # Output: "Rocky"
print(bulldog.make_sound()) # Output: "Bark!"

        在这个例子中,Bulldog 实例可以访问 Animal 类中的 name 属性,并且调用了 Dog 类中的 make_sound 方法。

这就是Python类的继承链的基本概念。通过继承链,类可以形成层次结构,共享和扩展功能。

8.1、MRO算法:继承顺序

        在 Python 中,MRO(Method Resolution Order)算法用于确定多继承类中方法的查找顺序。多继承是指一个类可以继承自多个父类,形成继承层次结构。在多继承中,如果一个方法在当前类中没有定义,Python 将按照 MRO 算法的顺序查找方法。MRO 的计算过程遵循 C3 线性化算法,确保类的继承层次结构是有序的。

MRO 的计算顺序可以使用类的 __mro__ 属性查看,例如:

class A:
    def method(self):
        print("Method from class A")

class B(A):
    def method(self):
        print("Method from class B")

class C(A):
    def method(self):
        print("Method from class C")

class D(B, C):
    pass

print(D.__mro__)

输出结果:

(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

        在上面的例子中,我们定义了四个类:A、B、C 和 D。类 D 继承自 B 和 C。当调用 D 类的 method() 方法时,Python 将按照 MRO 算法的顺序查找方法:

  1. Python 首先查找 D 类的 method() 方法,如果找到则调用,否则继续下一步。
  2. 然后查找 B 类的 method() 方法,如果找到则调用,否则继续下一步。
  3. 接着查找 C 类的 method() 方法,如果找到则调用,否则继续下一步。
  4. 最后查找 A 类的 method() 方法,如果找到则调用,否则继续下一步。
  5. 如果在以上类中都没有找到 method() 方法,Python 将调用 object 类的 method() 方法(所有类的基类)。

        注意:MRO 算法确保继承层次结构是有序的,这样可以保证方法查找的一致性。如果出现了无法确定的方法冲突(例如钻石继承结构),Python 将引发 TypeError

        在设计多继承类时,应该理解 MRO 算法,并根据需要选择正确的继承顺序,以避免潜在的方法冲突和不一致性。

9、堆内存中实例对象继承图示

二、子类级别和父类级别访问原则

子类级别   <   父类级别

  • 访问方面
    • 小级别可以直接访问大级别的,大级别不能直接访问小级别的
  • 包含方面
    • 小级别中有大级别的信息,大级别中没有小级别的信息

三、继承属性(主要针对实例属性)

1、若引用修饰类型为父类类型,则优先直接调用父类的(父类必须得有该属性,否则静态绑定失败,编译报错),根据引用变量中实际的值,根据继承链,最后决定调哪一个

2、若引用修饰类型为子类类型,则优先直接调用子类的(子类必须得有该属性,否则静态绑定失败,编译报错),根据引用变量中实际的值,根据继承链,最后决定调哪一个

class Parent:
    #静态属性
    id = "parent"

    #构造方法
    def __init__(self, name:str):
        #实例属性
        self.__name = name

    def setName(self,name:str) -> None:
        self.__name = name

    def getName(self) -> str:
        return self.__name

class Child(Parent):
    #静态属性
    id:str = "child"

    #构造方法
    def __init__(self,name:str,age:int):
        #调用父类的构造方法进行实例对象初始化
        super().__init__(name)
        #实例变量
        self.__age:int = age

    def setAge(self,age:int) -> None:
        self.__age = age

    def getAge(self) -> int:
        return self.__age


c:Parent = Child("jzq",27)
print(c.id) #child
print(c.id,c.getName(),c.getAge()) #child jzq 27

Child.id = "子类"
c.setName("111")
c.setAge(22)
print(c.id,c.getName(),c.getAge()) #子类 111 22

Child.aaa = "999"
print(c.aaa) #999

四、继承方法(主要针对实例方法)

1、若引用修饰类型为父类类型,则优先直接调用父类的(父类必须得有该方法,否则静态绑定失败,编译报错),根据引用变量中实际的值,然后从子类到父类查询,最后决定调哪一个

2、若引用修饰类型为子类类型,则优先直接调用子类的(子类必须得有该方法,否则静态绑定失败,编译报错),根据引用变量中实际的值,然后从子类到父类查询,最后决定调哪一个

class Parent:
    def parent_instance_method(self):
        print("parent instance method")

    @staticmethod
    def parent_static_method():
        print("parent static method")

    @classmethod
    def parent_class_method(cls):
        print("parent class method")

class Child(Parent):
    pass

c = Child()
c.parent_instance_method() #parent instance method
c.parent_class_method() #parent class method
c.parent_static_method() #parent static method

#这样也可以调用,需要传一个实例对象进去
Parent.parent_instance_method(c) #parent instance method

五、方法重写(覆盖 override)

方法覆盖又被称为方法重写,英语单词:override[[官方的]/overwrite

建议方法重写的时候尽量复制粘贴,不要编写,容易出错,导致没有产生覆盖。

1、方法重写的意义

        方法重写的主要意义在于实现多态性(polymorphism)和代码的可扩展性。多态性是面向对象编程的一个重要特性,它允许不同的对象对相同的方法调用做出不同的响应。通过方法重写,子类可以重新定义父类的方法,从而在不改变方法名和参数的情况下实现不同的行为。

        为什么不直接定义一个新的方法而是要重写父类方法的方法名和参数?这是因为方法重写实现了方法的覆盖,子类可以将自己特定的行为“覆盖”在父类方法的基础上,而不用另外定义新的方法。这样做的好处有:

  1. 统一接口:在多态的实现中,往往会使用父类类型引用指向子类对象,然后通过父类引用调用方法。如果子类直接定义新的方法,那么在使用多态时,无法通过父类引用调用子类特定的方法。而方法重写可以保持方法名和参数的一致性,使得通过父类引用调用方法时可以在不同的子类上产生不同的行为。

  2. 扩展功能:通过重写方法,子类可以扩展父类的功能,而不用从头开始实现一个全新的方法。这样,子类可以在保留原有功能的基础上,添加自己的特定功能,提高代码的复用性。

  3. 继承关系:方法重写维持了子类和父类的继承关系,使得子类仍然是父类的一种类型。这有利于代码的组织和维护,并且使得多态性能够在不同子类间有效运行。

        总结来说,方法重写使得代码更加简洁、灵活和易于维护。通过重写父类的方法,子类可以实现自己特定的行为,而不用重新定义新的方法名和参数。这是面向对象编程中多态性的重要体现,提供了代码的可扩展性和灵活性

2、方法重写定义

        当父类中的方法已经无法满足当前子类的业务需求,子类有必要将父类中继承过来的方法进行重新编写这个重新编写的过程称为 方法重写/方法覆盖

3、方法重写满足的条件

方法重写就是将父类中相同返回值类型相同,方法名相同,参数列表相同的方法给覆盖了,并不是父类中的该方法没有了,而是将父类中的该方法隐藏了,需要使用另一方式调用

  • 方法重写发生在具有继承关系的父子类之间
    • 方法重写要求存在继承关系,即一个类必须是另一个类的子类。子类可以继承父类的方法。
    • 方法名为 __ 上下划线开头的私有方法不能被重写,因为不能被子类继承
  • 方法重写的时候: 方法名相同,形参列表相同
    • 子类的重写方法的方法名和参数必须与父类中要重写的方法的方法名和参数完全一致。这意味着方法名和参数的个数及顺序都必须匹配。
  • 方法重写的时候:访问权限不能更低,可以更高。
    • 子类只能重写父类中允许被重写的方法。在 Python 中,默认情况下,所有方法都可以被重写。但如果想明确禁止重写某个方法,可以在父类的方法前加上final关键字,这样子类就无法对该方法进行重写。
    • 子类重写方法的访问权限不能比父类更严格。例如,如果父类方法是 public 访问权限,子类重写的方法也必须是 public 访问权限。
class Parent:
    def greet(self):
        print("Hello from Parent")

class Child(Parent):
    def greet(self):
        print("Hello from Child")
        super().greet()  # 调用父类的 greet 方法

# 创建子类的实例
child = Child()

# 调用重写的方法
child.greet()
"""
Hello from Child
Hello from Parent
"""

        在这个示例中,Child 类继承自 Parent 类,并且重写了 greet() 方法。在 Child 类中的 greet() 方法中,我们使用 super() 来调用了父类 Parentgreet() 方法,以保留父类方法的功能并在子类中添加额外的功能。

        注意:在使用方法重写时,子类应该仔细考虑是否真的需要修改父类方法的行为。过度的方法重写可能会导致代码混乱和难以维护。谨慎地使用方法重写,并确保在进行重写时保持方法签名的一致性,以避免不必要的错误。

4、方法不能重写的情况

方法没有重写就是没有将父类中的方法覆盖了,没有将父类中的该方法隐藏了,而是在子类中新建了一个新的方法,还是使用原来的方法调用

  • 私有方法不能继承,所以不能覆盖。

        Python 中私有方法是以双下划线 __ 开头的方法(例如 __private_method())。私有方法只能在声明它们的类内部使用,不能被子类访问或重写。

class Parent:
    def __private_method(self):
        print("This is a private method in Parent.")

class Child(Parent):
    def __private_method(self):  # 子类试图重写父类的私有方法,但不会成功
        print("This is a private method in Child.")

        注意:Python 中私有方法的重写并不会产生编译时错误,但是它实际上不会覆盖父类中的私有方法,而是会在子类中创建一个新的方法。 

  • 构造方法不能继承,所以不能覆盖。
  • 静态方法不存在覆盖。

        静态方法是用 @staticmethod 装饰器定义的方法,它不接收 selfcls 参数。静态方法属于类而不是实例,因此不能被子类重写。

class Parent:
    @staticmethod
    def static_method():
        print("This is a static method in Parent.")

class Child(Parent):
    @staticmethod
    def static_method():  # 子类试图重写父类的静态方法,但不会成功
        print("This is a static method in Child.")

和私有方法一样,静态方法的重写并不会产生编译时错误,但它也不会覆盖父类中的静态方法。 

  • 类方法不存在覆盖。

        类方法是用 @classmethod 装饰器定义的方法,它接收 cls 参数代表类本身。类方法属于类而不是实例,因此也不能被子类重写。

class Parent:
    @classmethod
    def class_method(cls):
        print("This is a class method in Parent.")

class Child(Parent):
    @classmethod
    def class_method():  # 子类试图重写父类的类方法,但不会成功
        print("This is a class method in Child.")

类方法的重写同样不会产生编译时错误,但它也不会覆盖父类中的类方法。 

  • 覆盖只针对实例方法,不针对属性和静态方法。

        总的来说,Python 中的方法重写对于普通实例方法是有效的,但对于私有方法、静态方法和类方法是不会有效的。需要注意的是,Python 允许在子类中重新定义这些方法,但它们并不是真正意义上的重写,而是在子类中创建了新的方法。

第七章、多态

一、概述

        多态(Polymorphism)是面向对象编程中的一个重要概念,它允许不同的对象对相同的消息(方法调用)做出不同的响应。在 Python 中,多态是一种动态类型特性,允许在运行时根据对象的实际类型来决定调用哪个方法。

1、Python中都是运行时多态

Python 是一种动态类型语言,因此它支持运行时多态(Run-time Polymorphism)。

        在运行时多态中,方法的调用是在运行时(而不是编译时)根据对象的实际类型来确定的。当一个对象调用方法时,Python 解释器会根据对象的类型找到正确的方法实现进行调用。这种灵活性允许我们在运行时使用不同类型的对象来执行相同的操作,从而实现多态性的特性。

        在 Python 中,方法重写(Method Overriding)是实现运行时多态的主要机制。当子类重写父类的方法后,调用该方法时会根据对象的实际类型调用相应的重写方法。这允许我们在调用相同的方法时,根据对象的类型执行不同的行为。

2、多态的分类

Python 中的多态性主要通过方法重写(Method Overriding)和鸭子类型(Duck Typing)来实现。

1、方法重写(Method Overriding)多态

子类对父类中已有的方法,重新给出自己的实现版本,这个过程叫做方法重写(Override)

在重写方法的过程中,不同的子类可以对父类的同一个方法给出不同的实现版本,那么该方法在运行时就会表现出多态行为

         方法重写是指子类继承父类的方法后,对该方法进行了重新实现,从而覆盖了父类中的方法。当子类对象调用被重写的方法时,将使用子类中的方法实现,而不是父类中的方法。这使得在运行时能够根据对象的实际类型调用正确的方法。

class Animal:
    def make_sound(self):
        print("Generic animal sound")

class Dog(Animal):
    def make_sound(self):
        print("Woof! Woof!")

class Cat(Animal):
    def make_sound(self):
        print("Meow!")

def animal_sound(animal):
    animal.make_sound()

dog = Dog()
cat = Cat()

animal_sound(dog)  # Output: Woof! Woof!
animal_sound(cat)  # Output: Meow!

        在这个例子中,DogCat 类重写了 make_sound() 方法,当调用 animal_sound() 函数时,会根据传入的不同类型的对象(dogcat)调用相应的重写方法。

2、鸭子类型(Duck Typing)多态

        鸭子类型是一种动态类型检查机制,它关注对象是否具有特定的方法或属性,而不关心对象的类型。如果一个对象可以像某种特定类型的对象一样进行方法调用,那么它就可以被视为该类型的对象。

class Bird:
    def make_sound(self):
        print("Generic bird sound")

class Duck:
    def make_sound(self):
        print("Quack! Quack!")

def bird_sound(bird):
    bird.make_sound()

bird1 = Bird()
bird2 = Duck()

bird_sound(bird1)  # Output: Generic bird sound
bird_sound(bird2)  # Output: Quack! Quack!

        在这个例子中,BirdDuck 类没有继承关系,但它们都有一个 make_sound() 方法。通过鸭子类型,我们可以在 bird_sound() 函数中传递不同类型的对象,只要这些对象具有 make_sound() 方法,就可以被视为相应类型的对象。

        多态性使得我们能够编写更通用、灵活的代码,减少重复性的代码。它增强了代码的可扩展性和可维护性,并促进了代码的重用。在 Python 中,多态性是一种强大的编程特性,允许我们以更简洁的方式处理不同类型的对象。

3、多态的作用

降低程序的耦合度,提高程序的扩展力

能使用多态尽量使用多态

4、多态的核心

核心:面向抽象编程,不要面向具体编程

父类类型引用指向子类实例对象

        Python中的多态性的核心概念是“父类型引用指向子类型对象”。多态性是面向对象编程的一个重要特性,它允许我们使用父类的引用来引用子类的对象,并在运行时动态地选择调用相应子类的方法。

        在Python中,每个类都可以看作是一个类型,并且一个类可以继承自另一个类,形成父子类关系。当一个父类引用指向子类的对象时,可以根据实际引用的对象来调用相应的方法,而不需要明确知道引用的对象的具体类型。

        这种多态性的特性使得代码更加灵活和可扩展。例如,如果你有一个接受父类对象作为参数的函数,你可以传递任何子类的对象作为参数,这样你的代码就可以适应不同类型的对象,而无需修改函数本身。

class Shape:
    def area(self):
        pass

class Square(Shape):
    def __init__(self, side):
        self.side = side

    def area(self):
        return self.side * self.side

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * self.radius * self.radius

# 多态性在这里体现
shapes = [Square(4), Circle(3)]
for shape in shapes:
    print(shape.area())

        上面的例子中,我们定义了一个Shape类作为父类,并分别定义了SquareCircle作为子类,它们继承自Shape。这两个子类都实现了area方法,但是具体的实现逻辑不同。通过将这两个子类对象存储在一个列表中,并循环调用area方法,实现了多态性的效果。

二、所有的属性和方法都是动态绑定

        动态绑定发生在运行时,它是通过引用中实际存储的值(实例对象的动态类型)来确定调用哪个属性和方法。具体来说,动态绑定是指在程序运行时,根据实际对象的类型来确定要调用的属性和方法。

        动态绑定:在动态类型语言(如 Python)中,方法调用的解析发生在运行时,即在运行时根据对象的实际类型来确定调用的方法。这被称为动态绑定。 Python 中的方法调用是动态绑定的,解释器根据对象的类型来决定调用哪个方法。

1、Python中不存在向上转型和向下转型

        向上转型和向下转型:在Java中,向上转型是指将子类对象赋值给父类引用,这是隐式的,不需要显式的转换。而向下转型是指将父类引用转换为子类引用,这需要进行显式的类型转换。

        在Python中,由于它是一种动态类型语言,不存在Java中的明确的向上转型和向下转型。Python中的对象赋值和引用是灵活的,一个对象可以被赋值给不同类型的引用,而不需要显式的类型转换。然而,在执行某些操作时,需要确保对象的类型是正确的,否则可能会引发运行时错误。

        虽然Python没有Java中的明确的向上转型和向下转型,但多态性的特性允许在运行时根据对象的实际类型来调用相应的方法,从某种意义上说,多态性在一定程度上实现了类似于向上转型和向下转型的效果。

三、Python中的多态实现案例

1、函数多态性

        在Python中,函数可以接受不同类型的参数并执行相应的操作。例如,len()函数可以用于获取不同类型数据的长度,无论是字符串、列表、字典还是元组。

print(len("Hello"))        # 输出: 5
print(len([1, 2, 3, 4]))   # 输出: 4
print(len({"a": 1, "b": 2}))  # 输出: 2

2、运算符多态性

        某些运算符也支持多态性,它们可以用于不同类型的数据,并根据数据类型执行适当的操作。例如,加法运算符+可以用于连接字符串或者执行数字相加。

print("Hello" + " World")  # 输出: "Hello World"
print(2 + 3)               # 输出: 5

3、多态集合类型

在Python中,像列表、字典和集合这样的集合类型也支持多态性。它们可以包含不同类型的元素

my_list = [1, "hello", True, 3.14]
my_dict = {"name": "Alice", "age": 30, "is_student": True}
my_set = {1, 2, "apple", (3, 4)}

四、Python中不存在ClassCastException类型转换异常

        在Python中,没有类似于Java中的运行时异常ClassCastException,因为Python是一种动态类型语言,类型转换是更加灵活的,并且在很多情况下是隐式进行的。在Python中,你可以在运行时进行类型转换,而不需要担心出现类型不匹配的异常。

        在Python中,你可以使用一些内置的类型转换函数来进行类型转换,如int()float()str()等。如果进行不合适的类型转换,Python通常会尝试进行隐式的类型转换或者抛出其他类型的异常,而不是像Java中那样抛出明确的ClassCastException。

        举个例子,在Python中进行整数转换时,如果转换成功,将返回对应的整数值,如果无法转换,则会引发ValueError异常:

num_str = "123"
try:
    num_int = int(num_str)
    print(num_int)
except ValueError as e:
    print("Error: ", e)

        如果num_str是一个合法的整数字符串,转换会成功,并打印出整数123。如果num_str是一个非法的字符串,如"abc",则会引发ValueError异常,打印出相应的错误信息。

        总之,Python中的类型转换是动态的,而且通常会在运行时处理类型不匹配的情况,而不是像Java那样产生明确的类型转换异常。这使得Python更加灵活和易于处理不同类型的数据。

第八章、实例对象

一、概述

每次新new一个对象,即使初始化时相同的,内存地址都不同的,都是一个新的实例对象

二、实例对象初始化过程

1、实例对象初始化步骤

  • 实例对象初始化声明( 类名() )
  • 执行 __new__(cls) 方法
  • 执行__init__(self) 构造方法
  • 在堆内存中的实例对象中初始化实例属性和实例方法
  • 返回实例对象的引用

当执行实例对象初始化声明代码时,开始执行实例对象初始化过程,从上往下依次执行。

2、__new__和__init__方法

在 Python 中,__init__() 方法和 __new__() 方法都是特殊方法,但它们在对象创建和初始化过程中有不同的作用。

  1. __new__() 方法:存储在方法区内存中的class对象中

    • __new__() 是一个类级别的方法,用于创建一个新的实例对象。
    • 它是在对象实例化之前调用的,用于创建对象并返回该对象的引用。
    • __new__() 方法的第一个参数是类本身,接下来的参数用于传递给 __init__() 方法。
    • __new__() 方法的返回值是一个新创建的实例对象。
    • 在普通情况下,我们很少需要重写 __new__() 方法,除非需要控制对象的创建过程。
  • __new__() 是一个类方法,它在对象实例化之前被调用,用于创建实例对象
  • __new__() 方法是Python中一个特殊的类方法,用于创建类的实例对象。它的作用是在对象初始化之前创建实例对象,并返回该实例对象
  • __new__() 方法的 cls 参数通常是必须传递的
  1. __init__() 方法:存储在堆内存中的实例对象中

    • __init__() 是一个实例级别的方法,用于对新创建的实例对象进行初始化操作。
    • 它在对象实例化后调用,即在 __new__() 方法返回对象后调用。
    • __init__() 方法的第一个参数是表示实例对象自身的 self 参数,后续参数用于接收外部传递给构造函数的参数。
    • __init__() 方法通常用于初始化对象的属性,并为对象设置初始状态。

联系:

  • __new__() 方法创建一个新的实例对象,并返回该对象的引用。然后,Python 解释器会调用 __init__() 方法来对该实例进行初始化操作。
  • 在普通情况下,我们很少需要重写 __new__() 方法。大多数情况下,我们只需要重写 __init__() 方法来进行对象的初始化。

示例:

class MyClass:
    def __new__(cls, *args, **kwargs):
        # 在这里可以自定义对象的创建过程
        instance = super(MyClass, cls).__new__(cls)
        return instance

    def __init__(self, value):
        self.value = value


obj = MyClass(142)
print(obj.value)  # 输出:142

a = MyClass.__new__(MyClass)
a.__init__(90)
print(a.value) # 输出:90

        在上面的示例中,我们同时重写了 __new__()__init__() 方法。__new__() 方法用于创建新的实例对象,而 __init__() 方法用于对新创建的实例进行初始化操作。注意,__new__() 方法的返回值是创建的实例对象,而 __init__() 方法的返回值应该为 None。

3、注意事项

1、每创建一个对象都要进行一次初始化

2、需要注意的是,在Python中,静态成员变量和静态方法的初始化是在类加载时进行的,而不是在对象创建时进行的。静态成员变量和静态方法只会被初始化一次,且在整个程序运行期间都会存在。

3、需要注意的是,所有属性的初始化顺序是按照声明的顺序进行的,即先声明的属性会先被初始化。

4、实例对象初始化(类名() )会在堆上分配内存,并返回一个指向该对象的引用。因此,可以说 创建了一个新的对象,并将其引用存储到了操作数栈的栈顶。

5、对象和引用

  • 对象:目前在使用实例对象初始化语法在堆内存中开辟的内存空间称为对象
  • 引用:是一个变量,不一定是局部变量,还可能是成员变量。引用保存了内存地址,指向了堆内存当中的对象
  • 所有访问实例相关的数据,都需要通过“引用."的方式访问,因为只有通过引用才能找到对象。
  • 只有一个空的引用,访问对象的实例相关的数据会出现AttributeError
     

4、对象初始化过程演示

  1. 类的实例化:

    • 当使用类名后跟括号 () 创建类的实例对象时,解释器会调用构造方法 __init__ 来创建对象。构造方法的第一个参数通常是 self,它指向类的实例对象,并用于初始化实例对象的属性。
  2. 实例属性的创建和初始化:

    • 构造方法 __init__ 在实例化对象时会执行一系列初始化操作。在构造方法内部,我们可以通过 self 来设置实例对象的属性,为对象提供初始状态。
  3. 返回实例对象:

    • 构造方法执行完毕后,会返回创建的实例对象。我们可以将这个实例对象赋值给一个变量,然后通过该变量来访问和操作实例对象的属性和方法。

下面是一个简单的示例来说明类的加载和初始化过程:

class MyClass:
    class_var = "Class variable"

    def __init__(self, instance_var):
        self.instance_var = instance_var

    def instance_method(self):
        print("Instance method called.")

# 类的加载和创建类对象
print(MyClass.class_var)  # 输出: Class variable

# 创建类的实例对象和初始化
obj = MyClass("Instance variable")
print(obj.instance_var)   # 输出: Instance variable

# 调用实例方法
obj.instance_method()     # 输出: Instance method called.

        在上面的示例中,我们定义了一个 MyClass 类,类的加载过程会创建类对象,并在类加载时创建类属性 class_var。当我们创建类的实例对象 obj 时,构造方法 __init__ 被调用,初始化了实例属性 instance_var。随后,我们通过实例对象调用实例方法 instance_method()

        总结:在 Python 中,类的加载和初始化是在解释器解析类定义时自动进行的。在类加载过程中,类属性和类方法会被创建,而初始化方法 __init__ 在实例化对象时自动执行,用于初始化实例对象的属性。创建类的实例对象后,我们可以通过实例对象来访问和操作实例的属性和方法。

5、__init__方法返回值类似为None,没有返回值

        在 Python 中,__init__ 方法是用于初始化新创建的对象的特殊方法,它负责在对象创建后进行初始化操作,并不需要显式地返回任何值。Python 中对于 __init__ 方法没有指定返回值的要求,它的默认返回值是 None,这是因为 __init__ 主要用于对象初始化,它通常会在对象创建后自动调用,而不需要显式返回值。
        当你创建一个类的实例时,Python 会在分配内存空间并创建对象后调用 __init__ 方法来执行一些必要的初始化操作,然后返回这个新创建的对象。__init__ 方法本身不返回任何内容,它只是用于对象初始化。

class MyClass:
    def __init__(self, value):
        self.value = value
        # 没有返回值,因为__init__方法主要用于初始化

obj = MyClass(10)  # 创建MyClass的实例obj,并传入值10进行初始化

        如果在 __init__ 方法中显式返回一个值,Python 会忽略这个返回值,因为 __init__ 方法的目的是初始化对象,而不是为了返回特定的结果。通常情况下,我们在 __init__ 中设置对象的初始状态,而不关心其返回值。

三、对象的默认初始化(构造方法参数赋默认值)

        在Python中,你可以像Java中一样,在类中定义构造函数并在实例化对象时给实例属性或类属性赋默认值。Python的构造函数是__init__方法,用于在创建对象时初始化对象的属性。类属性可以在类的定义中直接赋值,而实例属性则在构造函数中通过参数进行初始化。

下面是一个示例,演示如何在Python中给实例属性和类属性赋默认值:

class MyClass:
    # 类属性
    class_attr = "I am a class attribute"

    def __init__(self, prop1=None, prop2=None):
        # 实例属性
        self.prop1 = prop1 if prop1 is not None else "Default value for prop1"
        self.prop2 = prop2 if prop2 is not None else "Default value for prop2"

# 实例化对象,并使用默认值初始化实例属性
obj1 = MyClass()
print(obj1.prop1)  # 输出: Default value for prop1
print(obj1.prop2)  # 输出: Default value for prop2

# 实例化对象,并传递参数初始化实例属性
obj2 = MyClass("Custom value for prop1", "Custom value for prop2")
print(obj2.prop1)  # 输出: Custom value for prop1
print(obj2.prop2)  # 输出: Custom value for prop2

# 访问类属性
print(MyClass.class_attr)  # 输出: I am a class attribute

        在上面的示例中,我们定义了一个名为MyClass的类,其中包含一个类属性class_attr和一个构造函数__init__。构造函数接收两个参数prop1prop2,并用这些参数来初始化实例属性self.prop1self.prop2。如果在实例化对象时不提供这些参数,它们将使用默认值。

四、对象的显式初始化(构造方法参数不赋默认值)

1、构造方法语法结构

又称 构造方法 / 构造器 / Constructor

def  __int__(self, param1, param2, ...):

        self.实例变量名1= param1

        self.实例变量名2= param2

2、构造方法的作用

  • 构造方法存在的意义是,通过构造方法的调用,可以创建对象
  • 一般用于给实例变量赋值,也可以给静态变量赋值(比较少用)
  • 实例变量的内存空间是在构造方法执行过程当中完成开辟的。完成初始化的。
    系统在默认赋值的时候,也是在构造方法执行过程当中完成的赋值。

3、构造方法应该怎么调用

  • 第一种方式:类名( )  #实例对象初始化语法

4、构造方法调用执行之后,返回对象的引用

  • 每一个构造方法实际上执行结束之后都有返回值,但是这个"return 值;"这样的语向不需要写。构造方法结束的时候Python程序自动返回值。
  • 并且返回值类型是构造方法所在类的类型。由于构造方法的返回值类型就是类本身,所以返回值类型不需要编写。

1、当一个类中没有定义任何构造方法的话,系统默认给该类提供一个无参数的构造方法,这个构造方法被称为 缺省构造器;

2、当一个类显示的将构造方法定义出来了,那么系统则不再默认为这个类提供缺省构造器了

Python中不支持显示的方法重载,因此也只能定义一个构造方法
3、Python中不支持显示的方法重载机制,因此一个类中只能定义一个构造方法

4、只要使用  类名()  就会调用构造器创建对象,在“堆内存”中开辟该对象的内存空间,并返回实例对象的引用

5、构造方法传递的参数不止用于给属性赋值,也可用于条件判断

    def __init__(self, name, age = None):
        super().__init__(name)  # 调用父类的构造方法来初始化name属性
        # self.age = age
        if age is None:
            print(123)

五、对象的创建和使用

1、创建实例对象语法

语法:类名( );

用法:引用名 =  类名( )

2、访问实例变量的语法

读取数据: 引用.变量名;

修改数据:引用.变量名 = 值; //赋值语法

3、访问实例方法的语法

实例对象引用 . 实例方法名( 实参列表 );

class Person:
    def __init__(self):
        # 显式初始化实例属性
        self.name = "Unknown"
        self.age = 0

# 创建Person类的实例对象,并显式初始化属性
person1 = Person()
person1.name = "Alice"
person1.age = 30

# 创建另一个Person类的实例对象,并显式初始化属性
person2 = Person()
person2.name = "Bob"
person2.age = 25

# 输出实例对象的属性
print(person1.name)  # 输出: Alice
print(person1.age)   # 输出: 30
print(person2.name)  # 输出: Bob
print(person2.age)   # 输出: 25

#动态添加静态变量(属性)
Person.category = "人类"
print(person1.category)
print(person2.category)
"""
人类
人类
"""

六、AttributeError:属性错误

        在Python中,没有类似于Java中的NullPointerException(空指针异常)。这是因为Python中的变量不需要在声明时指定类型,并且在没有值的情况下,默认为None,而不是空指针。

        在Java中,如果尝试在空对象上调用方法或访问属性,会抛出NullPointerException,因为空对象不允许进行任何操作。

        在Python中,当你尝试在None值上调用方法或访问属性时,并不会抛出类似于Java的空指针异常,而是会引发AttributeErrorAttributeError表示试图访问不存在的属性或方法,而不是简单地访问空值。

下面是一个示例来说明Python中的情况:

# 在Java中,这段代码会抛出 NullPointerException
# String str = null;
# int length = str.length();

# 在Python中,这段代码会引发 AttributeError
str_value = None
length = len(str_value)  # AttributeError: 'NoneType' object has no attribute 'length'

        在Python中,要避免AttributeError,你需要确保在使用变量之前检查它是否为None,或者在可能为None的情况下添加条件判断来避免访问不存在的属性或方法。

七、self关键字

1、定义

        解析器在调用函数每次都会向函数内部传递进一个隐含的参数,这个隐含的参数就是self,self指向的是一个对象,这个对象我们称为函数执行的上下文对象,根据函数的调用方式的不同,this会指向不同的对象。self不可省略

1、self是一个关键字,翻译为:这个,Python实例方法中self参数是显示定义形参,隐式实参传递
2、self是一个引用,self是一个变量,self变量中保存了内存地址指向了自身,self存储在PVM堆内存Python对象内部。self也可以使用其他标识符替换,只要满足Python标识符命名规则

3、创建100个Python对象,每一个对象都有self且一个对象至少有两个self,(一个是该对象本身,另一个是object对象)一般只嵌套层最外面的那个self,也就说有100个不同的self

4、self是不能省略的

5、self 让我们能够在类的方法中对实例对象进行操作和管理

2、this可以出现的位置

  • this可以出现在“实例方法”当中,( 语法:self.当前类实例变量名/实例方法名
    • self指向当前正在执行这个动作或属性的类的实例对象。不能用在带有@staticmethod或者@classmethod 修饰符的“静态方法或类方法”中,因为静态方法或类方法是纯类私有,没有self 对象
  • self 可以出现在“构造方法”当中, (语法: this.实例变量名  #属于刚开始定义
    • 构造方法中self只是用于定义实例变量,并初始化实例变量
  •  类内与类外:
    • 一般在类外使用 对象名 调用当前类的实例变量和实例方法;
    • 一般在类内(实例方法或构造方法中)使用 self 来调用当前类的实例变量和实例方法
       

3、self的指向

4、self和this的区别

        在Python中,实例方法中的self关键字和Java中的this关键字有相似之处。selfthis都用于表示当前类的实例,允许在实例方法内部访问实例的属性和方法。

下面是self在Python和this在Java中的比较:

  • selfthis表示当前类的实例:
    • 在Python中,self是在实例方法中表示当前实例的约定名称。它是方法的第一个参数,在声明实例方法时需要显式定义。
    • 在Java中,this是指向当前类实例的引用。它在非静态方法中隐式可用,并用于访问实例变量和方法。
  • 访问实例变量和方法:
    • 在Python和Java中,分别使用selfthis来从实例方法内部访问实例的属性和方法。

七、super类( class super(object) )

1、定义

super() 对象 代表的就是“当前对象”的那部分父类型的实例特征,是self引用变量的一部分

2、super()对象 的出现位置

  • super可以出现在“实例方法”当中,(语法:super().父类实例数据  )
    • super() 指向当前正在执行这个动作或属性的类的实例对象的父类的实例(嵌套)。不能用在带有@staticmethod或者@classmethod 修饰符的“静态方法或类方法”中,因为静态方法和类方法是纯类私有,没有super对象
    • 在父和子中有同名的属性,或者说有同名的方法,如果要在子类中访问父类的数据,必须使用super().
  • super可以出现在“构造方法”当中, (语法:super().父类实例数据 )
    • 需要再子类的构造方法中,显示调用父类的构造方法,这样才能完成实例对象的嵌套
  • 主方法与非主方法:

    • 一般在主方法中使用 对象名 调用当前类的实例变量和实例方法;
    • 不在主方法中使用 super来调用当前类实例的父类的实例变量和实例方法
  • 类内与类外:
    • 一般在类外使用 对象名 调用当前类的父类的实例变量和实例方法;
    • 一般在类内(实例方法或构造方法中)使用 super() 来调用当前类的父类 的实例变量和实例方法

3、演示super() 的用法

        在Python中,super是一个特殊的关键字,用于调用父类(超类)的方法。它是用于实现方法重写时的一种常见技巧,特别是在多重继承的情况下。

        使用super关键字,您可以在子类中调用父类的方法,而不必显式指定父类的名称。这样做的好处是,如果类的继承层次发生变化,您无需修改所有调用父类方法的地方,只需要调整一处即可。

        需要注意的是,super()并没有直接指定父类的名称,它会自动查找继承链上的下一个类,并调用其方法。这使得在更改继承结构时代码更加灵活。

        请注意,super()只能在新式类(继承自object的类)中使用,因为它是Python 2.2之后引入的概念,并且在Python 3中成为标准。如果您的代码中使用的是Python 2,建议确保您的类都是新式类。在Python 3中,所有类都被视为新式类,无需显式继承object

class Parent:
    def __init__(self, name):
        self.name = name

    def say_hello(self):
        print(f"Hello, I'm {self.name} from the parent class!")

class Child(Parent):
    def __init__(self, name, age):
        super().__init__(name)  # 调用父类的构造方法来初始化name属性
        self.age = age

    def say_hello(self):
        super().say_hello()  # 调用父类的say_hello()方法
        print(f"I'm {self.name} and I'm {self.age} years old!")

child_obj = Child("Alice", 5)
child_obj.say_hello()
"""
Hello, I'm Alice from the parent class!
I'm Alice and I'm 5 years old!
"""

        在这个例子中,我们有两个类:ParentChildChildParent的子类,它继承了Parent__init__say_hello方法。

        在Child类的构造方法中,我们使用super().__init__(name)来调用父类Parent的构造方法,以便初始化name属性。

        在Child类的say_hello方法中,我们使用super().say_hello()来调用父类的say_hello方法,并在子类的方法中添加额外的逻辑。

注意,super()会在继承链中查找父类,因此如果子类的父类也有继承关系,super()将按照方法解析顺序(MRO)进行查找,并调用下一个类中的方法。这使得多重继承时使用super()尤为强大。

4、类继承时,若不显示定义构造方法,则默认自动调用父类的构造方法,若显示定义构造方法,则须手动调用父类构造方法

5、手动调用父类构造方法的两种方式

class Parent:
    def __init__(self, name):
        self.name = name

class Child(Parent):

    def __init__(self, name):
        #第一种调法
        # super().__init__(name)  # 调用父类的构造方法来初始化name属性

        #第二种调法
        Parent.__init__(self,name)  #虽然此处self的类型为Child,但是Child是Parent的子类,因此也是Parent类型的实例对象

if __name__ == '__main__':
    child_obj = Child("jzq") #jzq
    print(child_obj.name)

八、销毁对象(del关键字与 __del__方法)

__del__ 方法是 Python 中的一个特殊方法,用于对象的析构(即销毁)操作。当对象不再被引用或不再被使用时,Python 解释器会自动调用对象的 __del__ 方法来执行一些清理操作。然而,需要注意的是,__del__ 方法的执行时机和行为并不是完全可控的,因此在使用时需要小心。

你可以在自定义类中重写 __del__ 方法,以便在对象被销毁前执行一些操作。以下是一个示例:

class MyClass:
    def __init__(self, name):
        self.name = name

    def __del__(self):
        print(f"Object {self.name} is being deleted.")

# 创建一个对象
obj = MyClass("example")

# 不再引用对象,垃圾回收器会在适当时机调用 __del__ 方法
del obj  # 触发 __del__ 方法的调用

        需要注意的是,__del__ 方法不是一种严格的垃圾回收方法,而是一种析构方法。Python 的垃圾回收器会在适当的时机自动执行清理操作,而不是立即调用 __del__ 方法。因此,你不能完全依赖于 __del__ 方法来管理资源和执行清理操作。

        另外,Python 还提供了一种更可靠的方式来管理资源和执行清理操作,即使用上下文管理器(with 语句)来确保资源的正确释放。上下文管理器可以在进入和退出代码块时自动执行所需的操作,而不需要显式地依赖于 __del__ 方法。

        总之,__del__ 方法是一个特殊的析构方法,用于在对象销毁时执行清理操作。然而,在实际开发中,建议使用其他更可靠的方式来管理资源和执行清理操作,例如上下文管理器。

  • 25
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值