Python中类的用法

Python3.8

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

在 Python 中,类是面向对象编程的核心工具。它们用于封装数据和相关的操作。通过类,我们可以创建自己的数据结构,并且通过定义方法来操作这些数据。


一、类的基本用法

1. 类的定义和创建

类是通过 class 关键字定义的,类的实例是通过调用类的构造方法(__init__)创建的。

class Person:
    # 类的构造方法,用于初始化实例的属性
    def __init__(self, name, age):
        self.name = name  # 实例变量
        self.age = age    # 实例变量

    # 类的方法,用于执行操作
    def greet(self):
        print(f"Hello, my name is {self.name}, and I'm {self.age} years old.")

# 创建类的实例
person = Person("Alice", 30)
person.greet()  # 输出:Hello, my name is Alice, and I'm 30 years old.
2. 实例变量和类变量
  • 实例变量:每个实例(对象)拥有独立的变量。
  • 类变量:所有实例共享的变量。
class Counter:
    total = 0  # 类变量

    def __init__(self):
        self.count = 0  # 实例变量

    def increment(self):
        Counter.total += 1
        self.count += 1

# 创建多个实例
counter1 = Counter()
counter2 = Counter()

counter1.increment()
counter2.increment()
print(counter1.count, counter2.count, Counter.total)  # 输出:1 1 2
3. 继承和多态

继承可以让一个类从另一个类派生,获取父类的属性和方法。子类可以重写父类的方法,或者添加新的方法。

class Animal:
    def speak(self):
        print("Animal is making a sound.")

class Dog(Animal):  # Dog 类继承自 Animal
    def speak(self):  # 重写父类的方法
        print("Woof!")

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

# 多态的例子
animals = [Dog(), Cat()]
for animal in animals:
    animal.speak()  # 输出:Woof! Meow!

二、类在科研中的应用

在科研中,类常用于封装数据结构和算法。特别是在机器学习和数据分析中,类能帮助我们组织模型、数据预处理、训练过程等内容。

1. 定义机器学习模型类

例如,假设我们在实现一个简单的神经网络类:

import numpy as np

class SimpleNN:
    def __init__(self, input_size, hidden_size, output_size):
        # 初始化神经网络的层
        self.weights1 = np.random.rand(input_size, hidden_size)
        self.weights2 = np.random.rand(hidden_size, output_size)
        self.bias1 = np.zeros((1, hidden_size))
        self.bias2 = np.zeros((1, output_size))

    def forward(self, X):
        # 前向传播:输入 X 经过层1和层2的计算
        self.z1 = np.dot(X, self.weights1) + self.bias1
        self.a1 = self.sigmoid(self.z1)
        self.z2 = np.dot(self.a1, self.weights2) + self.bias2
        output = self.sigmoid(self.z2)
        return output

    def sigmoid(self, x):
        return 1 / (1 + np.exp(-x))

    def train(self, X, y, epochs=1000, lr=0.01):
        # 简单的训练过程
        for epoch in range(epochs):
            output = self.forward(X)
            loss = np.mean((output - y) ** 2)  # 均方误差损失
            # 反向传播和梯度更新过程略,实际中会计算梯度并更新权重
            print(f"Epoch {epoch}, Loss: {loss}")

这个类 SimpleNN 代表了一个简单的神经网络,包含了前向传播、损失计算和训练的方法。

2. 数据结构封装

我们可以用类来封装研究中的数据结构,比如时间序列、图像数据或实验结果:

class Experiment:
    def __init__(self, name, data):
        self.name = name
        self.data = data  # 存储实验数据

    def analyze(self):
        # 执行数据分析,如计算均值、标准差等
        return np.mean(self.data), np.std(self.data)

# 创建实验对象
exp = Experiment("Test Experiment", np.random.randn(100))
mean, std = exp.analyze()
print(f"Mean: {mean}, Std: {std}")

三、类在 LeetCode 中的应用

在 LeetCode 的算法题中,类主要用于组织状态、递归或动态规划算法,特别是当问题涉及到多层数据结构时(如树、图、链表等)。类能帮助我们将状态和逻辑分开,使得代码更加模块化和清晰。

1. 使用类来解决链表问题

假设要解决一个反转链表的问题,可以使用类来封装链表节点,并实现反转操作。

class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

def reverse_linked_list(head):
    prev = None
    curr = head
    while curr:
        next_node = curr.next
        curr.next = prev
        prev = curr
        curr = next_node
    return prev

# 示例链表: 1 -> 2 -> 3 -> 4
head = ListNode(1, ListNode(2, ListNode(3, ListNode(4))))
new_head = reverse_linked_list(head)
while new_head:
    print(new_head.val, end=" -> ")
    new_head = new_head.next
2. 使用类来解决树的问题

比如,在二叉树问题中,定义一个类来表示树节点,然后用递归方法进行操作。

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

def preorder_traversal(root):
    if root is None:
        return []
    return [root.val] + preorder_traversal(root.left) + preorder_traversal(root.right)

# 示例树:    1
#           / \
#          2   3
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
print(preorder_traversal(root))  # 输出: [1, 2, 3]
3. 动态规划问题中的类应用

在某些动态规划问题中,我们可以使用类来封装状态。比如在解决背包问题时:

class Knapsack:
    def __init__(self, capacity):
        self.capacity = capacity
        self.dp = [0] * (capacity + 1)

    def add_item(self, weight, value):
        for w in range(self.capacity, weight - 1, -1):
            self.dp[w] = max(self.dp[w], self.dp[w - weight] + value)

    def get_max_value(self):
        return self.dp[self.capacity]

# 使用例子
knapsack = Knapsack(10)
knapsack.add_item(3, 4)  # 重量3,价值4
knapsack.add_item(4, 5)  # 重量4,价值5
knapsack.add_item(5, 7)  # 重量5,价值7
print(knapsack.get_max_value())  # 输出最大价值

总结

  • 在 Python 中是封装数据和操作的工具,可以用来创建复杂的数据结构和管理状态。
  • 科研 中,类可以用来封装模型、数据、算法等内容,帮助我们组织实验、训练模型、实现数据分析。
  • LeetCode 中,类帮助我们解决问题时,可以组织状态、封装递归算法,或者在动态规划、树、链表等问题中提供清晰的结构。

在实际应用中,类帮助我们将相关的数据和功能聚合起来,使得代码更加清晰、可维护和可扩展。


108. Convert Sorted Array to Binary Search Tree

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution(object):
    def sortedArrayToBST(self, nums):
        def build(nums,left,right):
            if left>right:
                return None
            mid=(left+right)//2
            root=TreeNode(nums[mid])
            root.left=build(nums,left,mid-1)
            root.right=build(nums,mid+1,right)
            return root
        return build(nums,0,len(nums)-1)
  1. root = TreeNode(nums[mid_index]) 为什么这样就可以创建一个值为 nums[mid]的根节点?

它触及了面向对象编程 (Object-Oriented Programming, OOP) 的核心概念,而不仅仅是这个特定算法。

代码 root = TreeNode(nums[mid_index]) 之所以能“创建”一个节点,是因为 TreeNode 是一个类 (Class)

让我们来分解这个过程:

1. 什么是类 (Class)?

你可以把 类(Class) 想象成一个蓝图模板

在你的代码示例中,TreeNode 这个类就是一张“二叉树节点”的蓝图。这张蓝图规定了:

  • 一个“二叉树节点”必须有一个值(我们叫它 val)。
  • 一个“二叉树节点”可以有一个指向左边子节点的指针(我们叫它 left)。
  • 一个“二叉树节点”可以有一个指向右边子节点的指针(我们叫它 right)。

这个蓝图在 Python 中是这样定义的:

class TreeNode:
    # 这个 __init__ 函数就是蓝图的 "构造说明"
    # 它会在你"建造"一个新节点时自动运行
    def __init__(self, val=0, left=None, right=None):
        self.val = val      # 把传入的 val 存为自己的 .val 属性
        self.left = left    # 把传入的 left 存为自己的 .left 属性
        self.right = right  # 把传入的 right 存为自己的 .right 属性

2. 什么是对象 (Object)?

如果说 TreeNode 类是蓝图,那么 对象 (Object) 就是根据这张蓝图实际建造出来的东西

在你的代码中,root 就是这个实际被建造出来的“二叉树节点”对象。

3. TreeNode(nums[mid_index]) 这一步在做什么?

这一步叫做实例化 (Instantiation),也就是“根据蓝图建造一个实例(对象)”的过程。

当你调用 TreeNode(...) 时,你就是在对 Python 说:
“嘿,请按照 TreeNode 这张蓝图,帮我造一个新的二叉树节点对象!”

  • TreeNode:指定要用的蓝图。
  • ( ... ):这是“开始建造”的命令。
  • nums[mid_index]:这是你递给建造工人的材料

这个材料(nums[mid_index] 的值,比如 0)会被传递给蓝图的“构造说明”,也就是 __init__ 函数:

# 你的调用:
TreeNode(nums[mid_index]) 

# 对应到 __init__ 函数:
def __init__(self, val=0, left=None, right=None):
    # self:        (Python 自动处理,它代表"正在被建造的这个新对象")
    # val:         接收了 nums[mid_index] 这个值
    # left:        (你没提供,所以使用默认值 None)
    # right:       (你没提供,所以使用默认值 None)
    
    # "构造说明" 开始执行:
    self.val = nums[mid_index]  # 新对象的 .val 属性被设为 nums[mid_index]
    self.left = None            # 新对象的 .left 属性被设为 None
    self.right = None           # 新对象的 .right 属性被设为 None

4. root = ... 这一步在做什么?

这一步是赋值 (Assignment)

TreeNode(nums[mid_index]) 成功地在内存中建造了一个新的“二叉树节点”对象(它有 valleftright 三个属性)。

root = 这部分代码就是给这个刚刚诞生的新对象贴上一个名叫 root 的标签,这样你后续才能通过这个名字找到它、使用它(比如,去设置 root.left = ...)。

总结

所以,root = TreeNode(nums[mid_index]) 这行代码的完整含义是:

  1. 计算 nums[mid_index]:从数组中取出中间的那个值(例如 0)。
  2. 调用 TreeNode 类的构造函数:请求“建造”一个 TreeNode 的新实例(对象)。
  3. 传递参数:把 0 这个值作为 val 参数传给构造函数。
  4. 执行 __init__:构造函数运行,创建了一个新对象,并设置 self.val = 0self.left = Noneself.right = None
  5. 赋值:将这个新创建的对象(它现在是一个功能齐全的、值为 0 的根节点)赋值给 root 这个变量。

这就是为什么这行代码能够创建—个值为 nums[mid] 的根节点。这是面向对象编程的一个最基本、最常见的操作。

  1. 但是这个代码里值是0啊,nums[mid]到底是怎么传进去的

val=0 是一个默认参数(Default Argument),而不是一个固定的值。

这行代码:

def __init__(self, val=0, left=None, right=None):

它的意思是:
“当我(__init__)被调用来创建一个新节点时:

  1. 请你(调用者) 给我一个值,我会把它存为 val
  2. 如果你不给我 val,那我就用 0 作为备用的默认值。”

它是如何工作的

情况 A:你不提供值(使用默认值)

如果你这样写代码:
my_node = TreeNode()

Python 就会去看 __init__ 的定义,发现你没有提供 val,于是它就使用了默认值。

  • my_node.val 会是 0
  • my_node.left 会是 None
  • my_node.right 会是 None

情况 B:你提供了值(你的代码就是这种情况)

在你的代码中,你写的是:
root = TreeNode(nums[mid])

假设 nums = [-10, -3, 0, 5, 9] 并且 mid = 3
那么 nums[mid] 的值就是 5

所以,你的代码实际上是在调用:
root = TreeNode(5)

当 Python 看到这个,它会把 5 作为第一个参数传递给 __init__

# 你的调用: TreeNode(5)
# Python 这样匹配参数:
def __init__(self, val=0, left=None, right=None):
    # val 接收了你传进来的 5 (默认值 0 被忽略)
    # left 你没传,所以使用默认值 None
    # right 你没传,所以使用默认值 None
    
    self.val = val    # 也就是 self.val = 5
    self.left = left  # 也就是 self.left = None
    self.right = right# 也就是 self.right = None

因此,root.val 就被设置成了 5

  1. 是默认nums[mid]作为第一个参数被传进去吗?为什么不会被默认成left或者right?

是的,你完全说对了!nums[mid] 默认就是作为第一个参数被传进去的

这涉及到 Python 中函数参数的两种类型:位置参数(Positional Arguments)关键字参数(Keyword Arguments)

1. Python 如何匹配参数

当你调用一个函数时,Python 会严格按照顺序来匹配参数。

我们来看 TreeNode 的“蓝图” ( __init__ 函数):

def __init__(self, val=0, left=None, right=None):

这个函数定义了它能接收的参数顺序:

  • self:这个是 Python 自动处理的(代表对象自己),你不用管它。
  • 第 1 个你能传的参数是:val
  • 第 2 个你能传的参数是:left
  • 第 3 个你能传的参数是:right

2. 你的调用:root = TreeNode(nums[mid])

假设 nums[mid] 的值是 5。你的调用实际上是 TreeNode(5)

Python 会这样匹配:

  1. “我收到了1 个参数(值是 5)。”
  2. “我应该把它赋给谁?”
  3. “按照顺序,第 1 个参数是 val。”
  4. “好的,val = 5。”
  5. “(val 的默认值 0 被覆盖了,不再使用)。”
  6. “调用者没有提供第 2 个参数(left),所以我使用 left 的默认值 None。”
  7. “调用者没有提供第 3 个参数(right),所以我使用 right 的默认值 None。”

这就是为什么它不会被误认为是 leftright。它严格遵守从左到右的顺序。

3. 如何才能把它传给 left

如果你想按顺序,你必须使用关键字参数(即明确“指定名字”)来告诉 Python 你的意图。

情况 A(你的代码):位置参数

root = TreeNode(5) 
  • 结果: root.val5, root.leftNone, root.rightNone

情况 B:关键字参数
如果你想跳过 val,直接给 left 赋值,你必须这样写:

root = TreeNode(left=5) 
  • 结果: root.val0 (因为你没传,所以用了默认值), root.left5, root.rightNone

情况 C:混合使用

root = TreeNode(5, left=some_node, right=another_node)
  • 结果: root.val5, root.leftsome_node, root.rightanother_node

总结:

因为代码 TreeNode(nums[mid]) 只提供了一个参数,Python 会默认把它分配给参数列表中的第一个可用位置,也就是 val

您可能感兴趣的与本文相关的镜像

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值