Python 变量和简单数据类型

一、变量

变量是 Python 中最基础的概念。与 Java 不同,Python 的变量更像是一个"标签",而不是"容器"。本章将从定义、命名规则、与 Java 的对比等多个维度,帮助你建立正确的变量心智模型。

1.1 变量的定义与使用

python 复制代码
message = "Hello Python world!"
print(message)

message = "Hello Python Crash Course world!"
print(message)

输出:

python 复制代码
Hello Python world!
Hello Python Crash Course world!

1.2 变量命名规则

良好的命名习惯是代码可读性的基石。Python 官方推荐使用小写字母 + 下划线(snake_case)命名变量。

python 复制代码
# ✅ 有效的变量名
message = "Hello"
message_1 = "Hello"
_message = "Hello"

# ❌ 无效的变量名(会报错)
1_message = "Hello"    # 不能以数字开头
message-1 = "Hello"    # 不能包含连字符
greeting message = "Hello"  # 不能包含空格

命名规则总结:

  1. 只能包含字母、数字和下划线
  2. 不能以数字开头
  3. 不能包含空格
  4. 不能使用Python关键字
  5. 区分大小写

1.3 Python关键字(不能用作变量名)

关键字是 Python 预留的、具有特殊含义的标识符,不能 用作变量名、函数名或类名。使用关键字作为变量名会直接导致 SyntaxError

python 复制代码
False, None, True, and, as, assert, break, class, continue, def,
del, elif, else, except, finally, for, from, global, if, import,
in, is, lambda, nonlocal, not, or, pass, raise, return, try,
while, with, yield

1.4 与Java变量对比

如果你有 Java 基础,这一节是理解 Python 变量的关键。两种语言在变量本质、声明方式、赋值语义上存在根本差异,理解这些差异能帮你避免大量"想当然"的错误。

一句话概括:

  • Java:变量是容器,类型固定,基本类型存值、对象存引用
  • Python:变量是标签,类型动态,一切皆对象、一切皆引用

1.4.1 变量本质(最核心差异)

Java:

  • 变量是固定类型的内存盒子,变量本身有类型,盒子里存数据值(基本类型)或者对象引用地址(引用类型)。

  • 声明时必须指定类型,变量类型终身固定 ,不能再存别的类型。

    java 复制代码
    // Java
    int a = 10;   // a 是int类型盒子,只能放整数
    String s = "hi"; // s存对象的地址

Python:

  • 变量是标签 / 名字,贴在对象上 ;对象才有类型,变量本身没有类型。

    python 复制代码
    a = 10
    a = "hello"   # 合法,把标签a撕下来贴到字符串对象上
  • a只是名字,没有类型;真正有类型的是右边的对象 10"hello"

  • 同一个变量名可以反复绑定不同类型对象。

1.4.2 声明与赋值

Java:显式类型声明

  • 先声明,再赋值;声明必须写类型

  • 局部变量不会自动初始化,不赋值不能使用。

    java 复制代码
    // 基本类型
    int age = 25;
    double price = 19.99;
    boolean isActive = true;
    char grade = 'A';
    
    // 引用类型
    String name = "Alice";
    List<String> list = new ArrayList<>();
    Map<String, Object> map = new HashMap<>();
    
    // 变量类型一旦声明,不能改变
    int x = 10;
    x = "hello";  // ❌ 编译错误:不兼容的类型

Python:无类型声明

  • 没有单独声明步骤,赋值即创建变量

  • 没有变量预先声明语法

    python 复制代码
    # 无需声明类型,直接赋值
    age = 25
    price = 19.99
    is_active = True
    grade = 'A'
    name = "Alice"
    my_list = []
    my_dict = {}
    
    # 变量可以重新指向不同类型
    x = 10
    x = "hello"  # ✅ 完全合法
    x = [1, 2, 3]  # ✅ 完全合法

1.4.3 赋值语义差异

Java:

  • 基本数据类型(int,double,boolean...):值拷贝

  • 引用类型(类、数组):拷贝引用地址,两个变量指向同一个对象

    java 复制代码
    // 1. 基本类型赋值:值复制
    int a = 10;
    int b = a;  // b = 10(复制值)
    b = 20;
    System.out.println(a);  // 10
    
    // 2. 对象类型赋值:引用复制
    List<String> list1 = new ArrayList<>();
    list1.add("A");
    List<String> list2 = list1;  // list2 获得 list1 的引用
    list2.add("B");
    System.out.println(list1);  // [A, B](list1 也被修改)
    
    // 3. 字符串赋值(不可变对象)
    String s1 = "hello";
    String s2 = s1;  // s2 获得 s1 的引用
    s2 = s2 + " world";  // 创建新对象,s1 不受影响
    System.out.println(s1);  // hello

Python:

  • 一切皆对象,没有原生基本类型;

  • 所有赋值都是"引用赋值"。

  • 不可变对象 重新赋值不是修改对象,是更换标签绑定。

    python 复制代码
    # 1. 不可变对象(int, float, str, tuple)
    a = 10
    b = a  # b 指向 a 指向的对象 10
    b = 20  # b 指向新对象 20,a 不受影响
    print(a)  # 10
    
    # 2. 可变对象(list, dict, set)
    list1 = [1, 2, 3]
    list2 = list1  # list2 指向 list1 指向的同一个列表
    list2.append(4)
    print(list1)  # [1, 2, 3, 4]
    
    # 3. 字符串(不可变)
    s1 = "hello"
    s2 = s1  # s2 指向 s1 指向的字符串对象
    s2 += " world"  # 创建新字符串对象,s1 不受影响
    print(s1)  # hello

1.4.4 常量

Java:final修饰,变量引用不可修改

java 复制代码
// final 变量:只能赋值一次
final int MAX_SIZE = 100;
// MAX_SIZE = 200;  // ❌ 编译错误

// final 引用:引用不能变,但对象内容可以变
final List<String> list = new ArrayList<>();
list.add("A");  // ✅ 可以修改对象内容
// list = new ArrayList<>();  // ❌ 编译错误

// 真正不可变:使用不可变类
final String s = "hello";
// s = s + " world";  // ❌ 编译错误(String 不可变)

Python:约定 + 机制

python 复制代码
# Python 没有真正的常量机制
# 约定:全大写表示常量
MAX_SIZE = 100
# 仍然可以修改(只是约定)
MAX_SIZE = 200  # ✅ 不会报错

# 模拟不可变性:使用元组
def get_config():
    return ("host", "localhost", "port", 8080)  # 返回元组

# 使用 @property 实现只读属性
class Circle:
    def __init__(self, radius):
        self._radius = radius
    
    @property
    def radius(self):
        return self._radius
    
    @property
    def area(self):
        return 3.14 * self._radius ** 2

c = Circle(5)
print(c.radius)  # 5
print(c.area)    # 78.5
# c.radius = 10  # ❌ AttributeError: can't set attribute

冻结对象(frozenset, tuple):

python 复制代码
# frozenset:不可变集合
s = frozenset([1, 2, 3])
# s.add(4)  # ❌ AttributeError

# tuple:不可变列表
t = (1, 2, 3)
# t[0] = 10  # ❌ TypeError

# 注意:tuple中的可变元素仍可修改
t = ([1, 2], 3)
t[0].append(4)  # ✅ 可以修改!
print(t)  # ([1, 2, 4], 3)

1.4.5 多变量赋值

Java:

java 复制代码
int a,b,c;
a=b=c=10; // 支持链式赋值

Python:

python 复制代码
# 链式赋值
a = b = c = 10

# 序列解包(Java没有)
x,y = 10,20
x,y = y,x  # 交换变量,不需要临时变量

1.4.6 类型系统差异

Java:

  • 静态类型系统,强制转换,变量类型不变

    java 复制代码
    // 编译时类型检查
    String s = "hello";
    int i = 10;
    
    // 类型转换需要显式进行
    Object obj = "hello";
    String str = (String) obj;  // 需要强制类型转换
    
    // 自动装箱/拆箱
    Integer boxed = 10;   // 自动装箱
    int unboxed = boxed;  // 自动拆箱

Python:

  • 动态类型系统,转换产生新对象 ,变量本身类型不受限制

    python 复制代码
    # 运行时类型检查
    s = "hello"
    i = 10
    
    # 无需类型转换,直接重新赋值
    obj = "hello"
    str_val = obj  # 无需转换,直接引用
    
    # 没有基本类型和包装类型的区分
    # Python中 int 就是对象,没有 int vs Integer 的区别
    x = 10  # x 是 int 对象
    print(type(x))  # <class 'int'>

1.4.7 变量作用域

Java:

  • 块级作用域:{ }就会产生新作用域

    java 复制代码
    {
    	   int x = 10;
    }
    // x 外面不可见

Python:

  • 没有块级作用域(if/for/while 不会新建作用域) ,只有函数、类、模块作用域

    python 复制代码
    for i in range(3):
    	num = 100
    print(num) # 可以访问,for的块不隔离变量

1.4.8 内存模型对比

Java 特点:

  • 基本类型(int, double, boolean等):值直接存储在栈中
  • 对象类型(String, List等):引用存储在栈中,对象存储在堆中
  • 赋值给另一个变量时:基本类型复制值,对象类型复制引用
java 复制代码
// Java
int a = 10;
int b = a;  // b 获得 a 的值副本
b = 20;
System.out.println(a);  // 10(a 不受影响)

String s1 = "hello";
String s2 = s1;  // s2 获得 s1 的引用副本
// s1 和 s2 指向同一个字符串对象
// 但 String 是不可变的,所以修改 s2 不会影响 s1

Python 特点:

  • 没有基本类型:所有数据都是对象,存储在堆中
  • 变量只是名字/标签,指向堆中的对象
  • 赋值操作:只是让变量指向新的对象(类似Java的对象引用赋值)
  • 使用引用计数进行内存管理
python 复制代码
# Python
a = 10
b = a  # b 指向 a 指向的同一个对象
b = 20  # b 现在指向新的对象 20
print(a)  # 10(a 仍然指向 10)

# 但注意:对于可变对象
list1 = [1, 2, 3]
list2 = list1  # list2 指向 list1 指向的同一个列表对象
list2.append(4)
print(list1)  # [1, 2, 3, 4](list1 也被修改了!)

1.5 可变对象 vs 不可变对象

理解了"变量是标签"之后,下一个核心概念就是对象的可变性。可变对象可以原地修改,不可变对象一旦创建就不能改变。这一特性直接决定了赋值、传参、拷贝时的行为差异。

1.5.1 与Java对比

类型 Java Python
不可变 String, Integer, Double(包装类) int, str, tuple, float, bool
可变 ArrayList, HashMap, StringBuilder list, dict, set
特点 基本类型值传递,对象引用传递 一切皆引用,可变性决定行为

1.5.2 可变对象

python 复制代码
# 可变对象:创建后可以修改
# list, dict, set

# 示例1:列表
lst = [1, 2, 3]
print(id(lst))    # 4389562048
lst.append(4)     # 原地修改
print(id(lst))    # 4389562048(相同地址)

# 示例2:字典
d = {"a": 1}
print(id(d))      # 4389562048
d["b"] = 2        # 原地修改
print(id(d))      # 4389562048(相同地址)

# 示例3:集合
s = {1, 2, 3}
print(id(s))      # 4389562048
s.add(4)          # 原地修改
print(id(s))      # 4389562048(相同地址)

1.5.3 不可变对象

python 复制代码
# 不可变对象:创建后不能被修改
# int, float, bool, str, tuple, frozenset

# 示例1:整数
x = 10
print(id(x))  # 140735892123456
x += 1        # x 指向新的对象 11
print(id(x))  # 140735892123488(不同地址)

# 示例2:字符串
s = "hello"
print(id(s))   # 4389562048
s += " world"  # 创建新字符串
print(id(s))   # 4389563088(不同地址)

# 示例3:元组
t = (1, 2, 3)
# t[0] = 10  # ❌ TypeError: 'tuple' object does not support item assignment

1.6 深拷贝与浅拷贝

为什么浅拷贝修改嵌套元素会影响原对象? 关键在于:浅拷贝只复制了最外层列表 ,而嵌套的子列表(如 [3, 4])并没有被复制,两个外层列表仍然共享同一个子列表对象。下面用 ASCII 示意图直观展示这一差异。

original = [1, 2, [3, 4]] 为例,内存中的引用关系如下:

浅拷贝(Shallow Copy):

复制代码
original ──▶ [ 1 , 2 , ────────┐ ]
                            │
shallow ──▶ [ 1 , 2 , ──────┐ │ ]
                            │ │
                            ▼ ▼
                         [ 3 , 4 ]   ← 同一个子列表,被两个外层列表共享
  • originalshallow 是两个不同的外层列表 (所以 shallow[0] = 100 不影响 original);
  • 但它们的 [2] 都指向同一个子列表 [3, 4]
  • 因此 shallow[2][0] = 300 会直接修改那个共享的子列表,original 也会看到变化。

深拷贝(Deep Copy):

复制代码
original ──▶ [ 1 , 2 , ────────┐ ]
                            │
                            ▼
                         [ 3 , 4 ]   ← 独立的子列表

deep ──▶ [ 1 , 2 , ────────┐ ]
                            │
                            ▼
                         [ 3 , 4 ]   ← 另一个独立的子列表
  • deep 是全新的外层列表,其嵌套子列表 [3, 4] 也是重新创建的;
  • 两层列表完全独立,deep[2][0] = 300 只修改 deep 自己的子列表,original 不受任何影响。

一句话总结: 浅拷贝只复制「外壳」,嵌套对象仍是共享引用;深拷贝连「内壳」也一并复制,做到完全独立。

1.6.1 与Java对比

操作 Java Python
引用赋值 List b = a; b = a
浅拷贝 new ArrayList<>(a) a: 或 a.copy()
深拷贝 手动递归或使用序列化 copy.deepcopy(a)

1.6.2 深拷贝(Deep Copy)

深拷贝特点:

  • 完全独立,不影响原始对象
python 复制代码
import copy

original = [1, 2, [3, 4]]
deep = copy.deepcopy(original)

deep[0] = 100       
deep[2][0] = 300    

print(original)   # [1, 2, [3, 4]]
print(deep)       # [100, 2, [300, 4]]

1.6.3 浅拷贝(Shallow Copy)

浅拷贝特点:

  • 创建新对象
  • 但只复制最外层,嵌套对象仍然是引用
python 复制代码
import copy

# 方式1:切片
shallow1 = original[:]

# 方式2:list()构造函数
shallow2 = list(original)

# 方式3:copy模块
shallow3 = copy.copy(original)

original = [1, 2, [3, 4]]
shallow = original.copy()  # 或 original[:]

shallow[0] = 100        # 不影响 original
shallow[2][0] = 300     # 影响 original!因为嵌套列表是引用

print(original)   # [1, 2, [300, 4]]
print(shallow)    # [100, 2, [300, 4]]

1.6.4 实战案例:Python 与 Java 对比

下面用一个包含嵌套列表/ArrayList 的对象,分别演示浅拷贝和深拷贝后修改嵌套元素对原对象的影响。

Python 版本:

python 复制代码
import copy

class Box:
    """模拟一个包含嵌套列表的对象"""
    def __init__(self, items):
        self.items = items  # items 是一个嵌套列表

    def __repr__(self):
        return f"Box({self.items})"

# 创建一个包含嵌套列表的对象
original = Box([[1, 2], [3, 4]])

# 浅拷贝:只复制最外层对象,嵌套列表仍是共享引用
shallow = copy.copy(original)
# 深拷贝:连嵌套列表也一并复制,完全独立
deep = copy.deepcopy(original)

# 修改浅拷贝的嵌套元素
shallow.items[0][0] = 100
print("浅拷贝后 original:", original)   # Box([[100, 2], [3, 4]])  ← 原对象被影响!
print("浅拷贝后 shallow: ", shallow)    # Box([[100, 2], [3, 4]])

# 修改深拷贝的嵌套元素
deep.items[0][0] = 999
print("深拷贝后 original:", original)   # Box([[100, 2], [3, 4]])  ← 原对象不受影响
print("深拷贝后 deep:    ", deep)       # Box([[999, 2], [3, 4]])

Java 版本:

java 复制代码
import java.util.ArrayList;
import java.util.List;

public class Box {
    private List<List<Integer>> items;

    public Box(List<List<Integer>> items) {
        this.items = items;
    }

    public List<List<Integer>> getItems() {
        return items;
    }

    @Override
    public String toString() {
        return "Box(" + items + ")";
    }

    public static void main(String[] args) {
        // 创建一个包含嵌套 ArrayList 的对象
        List<List<Integer>> data = new ArrayList<>();
        data.add(new ArrayList<>(List.of(1, 2)));
        data.add(new ArrayList<>(List.of(3, 4)));
        Box original = new Box(data);

        // 浅拷贝:只复制最外层 Box 引用,嵌套 ArrayList 仍是共享引用
        Box shallow = new Box(original.getItems());
        // 深拷贝:手动递归复制嵌套 ArrayList,完全独立
        List<List<Integer>> deepData = new ArrayList<>();
        for (List<Integer> inner : original.getItems()) {
            deepData.add(new ArrayList<>(inner));
        }
        Box deep = new Box(deepData);

        // 修改浅拷贝的嵌套元素
        shallow.getItems().get(0).set(0, 100);
        System.out.println("浅拷贝后 original: " + original);  // Box([[100, 2], [3, 4]])  ← 原对象被影响!
        System.out.println("浅拷贝后 shallow:  " + shallow);   // Box([[100, 2], [3, 4]])

        // 修改深拷贝的嵌套元素
        deep.getItems().get(0).set(0, 999);
        System.out.println("深拷贝后 original: " + original);  // Box([[100, 2], [3, 4]])  ← 原对象不受影响
        System.out.println("深拷贝后 deep:     " + deep);      // Box([[999, 2], [3, 4]])
    }
}

运行结果对比:

操作 Python 输出 Java 输出
浅拷贝后修改嵌套元素 original 变为 Box([[100, 2], [3, 4]]) original 变为 Box([[100, 2], [3, 4]])
深拷贝后修改嵌套元素 original 保持 Box([[100, 2], [3, 4]]) 不变 original 保持 Box([[100, 2], [3, 4]]) 不变

结论: 无论 Python 还是 Java,浅拷贝都只复制最外层对象,嵌套的 list/ArrayList 仍是共享引用,修改嵌套元素会波及原对象;深拷贝则连嵌套层一并复制,做到完全独立。这正是「浅拷贝只复制外壳,深拷贝连内壳也复制」的直观体现。

1.6.5 性能与边界条件

copy.copy 与 copy.deepcopy 的时间复杂度对比:

对比维度 copy.copy(浅拷贝) copy.deepcopy(深拷贝)
时间复杂度 O(n),n 为最外层元素个数 O(N),N 为对象图中所有节点总数(含所有嵌套层)
复制范围 只复制最外层容器 递归复制所有嵌套对象
内存开销 低,仅新建最外层容器 高,需为每个嵌套对象分配新内存
适用场景 对象只有一层结构,或嵌套对象可安全共享 需要完全独立副本,嵌套对象会被修改

deepcopy 的性能隐患:

  1. 递归过深 :当对象嵌套层级非常深(如深度超过 1000 层)时,deepcopy 的递归实现可能触发 RecursionError,导致程序崩溃。
  2. 循环引用 :当对象存在自引用或互相引用(如链表成环、图结构)时,deepcopy 需要借助内部记忆表(memo)来避免无限递归。虽然标准库已处理循环引用,但会显著增加时间和内存开销。
  3. 复制无关对象deepcopy 会复制对象图中所有可达对象,即使某些对象根本不需要独立副本(如只读配置、单例、缓存对象),造成不必要的性能浪费。
python 复制代码
import copy
import sys

# 1. 递归过深示例:深层嵌套列表
deep_list = []
current = deep_list
for _ in range(1500):          # 嵌套 1500 层
    current.append([])
    current = current[-1]

try:
    copy.deepcopy(deep_list)   # ❌ RecursionError: maximum recursion depth exceeded
except RecursionError as e:
    print("deepcopy 递归过深报错:", e)

# 2. 循环引用示例:自引用列表
cyclic = []
cyclic.append(cyclic)          # 列表引用自身
deep_cyclic = copy.deepcopy(cyclic)   # ✅ 能处理,但开销更大
print(deep_cyclic is deep_cyclic[0])  # True(副本内部也保持自引用关系)

优化方案一:使用 copyreg 注册自定义复制函数

对于频繁深拷贝的自定义类型,可以通过 copyreg.pickle() 注册专门的复制函数,跳过不必要的递归复制,大幅提升性能。

python 复制代码
import copy
import copyreg

class HeavyObject:
    """模拟一个包含大量不可变数据的对象"""
    def __init__(self, data, cache=None):
        self.data = data          # 需要独立复制的数据
        self.cache = cache or {}  # 只读缓存,无需复制

def _copy_heavy(obj):
    """自定义深拷贝:只复制 data,cache 直接共享"""
    return HeavyObject(copy.deepcopy(obj.data), obj.cache)

# 注册自定义复制函数
copyreg.pickle(HeavyObject, _copy_heavy)

obj = HeavyObject([1, 2, [3, 4]], {"config": "readonly"})
new_obj = copy.deepcopy(obj)

new_obj.data[2][0] = 999
print(obj.data)       # [1, 2, [3, 4]](data 独立复制,互不影响)
print(new_obj.data)   # [1, 2, [999, 4]]
print(new_obj.cache is obj.cache)  # True(cache 共享,节省内存)

优化方案二:自定义 __deepcopy__ 方法

在类中实现 __deepcopy__ 方法,可以精确控制哪些属性需要深拷贝、哪些可以共享,避免 deepcopy 复制无关对象。

python 复制代码
import copy

class Config:
    """配置对象:data 需要独立,meta 是只读元数据"""
    def __init__(self, data, meta):
        self.data = data
        self.meta = meta

    def __deepcopy__(self, memo):
        """只深拷贝 data,meta 直接共享引用"""
        new_obj = Config(
            copy.deepcopy(self.data, memo),  # data 独立复制
            self.meta                        # meta 共享,不复制
        )
        memo[id(self)] = new_obj             # 记录到 memo,处理循环引用
        return new_obj

cfg = Config([1, 2, [3, 4]], {"version": "1.0"})
cfg_copy = copy.deepcopy(cfg)

cfg_copy.data[2][0] = 999
print(cfg.data)       # [1, 2, [3, 4]](data 独立)
print(cfg_copy.data)  # [1, 2, [999, 4]]
print(cfg_copy.meta is cfg.meta)  # True(meta 共享)

选择建议:

  • 对象只有一层结构 → 用 copy.copy,性能最好;
  • 对象嵌套较深且需要完全独立 → 用 copy.deepcopy,但注意递归深度;
  • 对象包含大量只读/共享属性 → 用 copyreg 或自定义 __deepcopy__ 精准控制复制范围,避免不必要的性能损耗。

实战案例:循环引用 + 深拷贝

下面构造一个包含自引用的链表结构(Node 类,next 指向自身),分别用 copy.copycopy.deepcopy 复制,直观展示浅拷贝下修改共享节点会影响原对象、深拷贝下完全独立。

python 复制代码
import copy

class Node:
    """链表节点:next 指向自身,形成自引用环"""
    def __init__(self, value):
        self.value = value
        self.next = None

    def __repr__(self):
        return f"Node({self.value})"

# 构造自引用链表:node.next 指向 node 自身
node = Node(1)
node.next = node          # 自引用,形成环

# 浅拷贝:只复制最外层 Node 对象,next 仍是共享引用
shallow = copy.copy(node)
# 深拷贝:连 next 指向的 Node 也一并复制,完全独立
deep = copy.deepcopy(node)

print("浅拷贝后 shallow.next is shallow:", shallow.next is shallow)  # True(共享同一个自引用环)
print("深拷贝后 deep.next is deep:      ", deep.next is deep)        # True(副本内部也保持自引用)

# 修改浅拷贝的共享节点
shallow.value = 100
print("浅拷贝后 node.value:  ", node.value)    # 100  ← 原对象被影响!
print("浅拷贝后 shallow.value:", shallow.value) # 100

# 修改深拷贝的独立节点
deep.value = 999
print("深拷贝后 node.value:", node.value)       # 100  ← 原对象不受影响
print("深拷贝后 deep.value:", deep.value)       # 999

# 验证深拷贝的独立性:deep.next 是全新的 Node 对象
print("deep.next is node:", deep.next is node)  # False(完全独立)

运行结果注释:

python 复制代码
浅拷贝后 shallow.next is shallow: True   # 浅拷贝只复制外壳,next 仍指向原 node
深拷贝后 deep.next is deep:      True   # 深拷贝连自引用关系也一并复制
浅拷贝后 node.value:   100              # 浅拷贝修改共享节点,原对象被影响!
浅拷贝后 shallow.value: 100
深拷贝后 node.value: 100                # 深拷贝修改独立节点,原对象不受影响
深拷贝后 deep.value: 999
deep.next is node: False                # deep 的 next 指向全新的 Node,完全独立

要点总结:

  • copy.copy 只复制最外层 Node,其 next 仍指向原对象 ,所以修改 shallow.value 会波及 node
  • copy.deepcopy 借助内部 memo 表识别循环引用,会为环中的每个节点创建独立副本,并保持副本内部的环结构deep.next is deepTrue);
  • 这正是「浅拷贝只复制外壳,深拷贝连内壳也复制」在循环引用场景下的体现------深拷贝不仅能处理自引用,还能保证副本完全独立。

1.7 本章小结

对比维度 Java Python
变量本质 固定类型的内存盒子 贴在对象上的标签
类型系统 静态类型,编译期检查 动态类型,运行时检查
赋值语义 基本类型值拷贝,对象引用拷贝 一切皆引用
常量 final 强制不可变 全大写命名约定
作用域 块级作用域 函数/类/模块作用域
内存模型 基本类型在栈,对象在堆 一切皆对象,皆在堆

核心要点:

  • Python 变量没有类型,类型属于对象;
  • 可变对象(list/dict/set)原地修改,不可变对象(int/str/tuple)重新绑定;
  • 浅拷贝只复制最外层,深拷贝完全独立。

二、字符串

2.1 字符串的定义方式

python 复制代码
# 单引号
name = 'Alice'

# 双引号
name = "Alice"

# 三引号(多行字符串)
message = """This is a
multi-line string."""

# 包含引号的字符串
description = 'He said, "Python is awesome!"'
description = "It's a beautiful day."

2.2 字符串常用方法

2.2.1 字符串拼接

(1) 如果是两个字符串常量拼接的话,可以直接将两个字符串紧挨着写在一起,也可以使用+连接符进行拼接;

(2)如果是使用字符串变量进行拼接的话,则只能使用+连接符进行拼接;

(3)如果需要将字符串和数字拼接在一起,必须先将数字转换成字符串,再通过+连接符进行拼接在一起。

bash 复制代码
1.将两个字符串常量拼接在一起
str = "hello" "world"
str = "hello" + "world"

2.使用变量进行连接
str1 = "hello"
str2 = "world"
str = str1 + str2

3.将字符串与数字进行拼接
name = "小明"
age = 18
str = name + "今年刚刚" + str(age) + "岁!"

2.2.2 字符串截取

字符串是由多个字符构成的,本身就是一个序列,可以通过索引进行字符串的截取。

(1)字符串的索引从左往右依次从0开始递增;

(2)字符串的索引从右往左依次从-1开始递增;

bash 复制代码
str = "helloworld"
1.截取单个字符
print(str[1])   	## e
print(str[-1])		## d

2.截取多个字符串,具体格式为:strname[start : end : step]
strname:要进行截取的字符串;
start:开始截取的索引位置(包含该索引)。可以不指定,则默认为 0,也就是从字符串的开头截取;
end:结束截取的索引位置(不包含该索引,也就是截取到该索引前一位)。可以不指定,则默认截取到字符串最后一位;
step:指的是从 start 索引处的字符开始,每 step 个距离获取一个字符,直至 end 索引出的字符。step 默认值为 1,当省略该值时,最后一个冒号也可以省略。

str = "0123456789"
3.开始截取位置为1,结束截取位置9,指定step为2
str1 = str[1:9:2]   ## 1357

4.开始截取位置为1,结束截取位置9,不指定step
str2 = str[1:9] 	## 12345678

5.不指定开始截取位置,结束截取位置为9,不指定step
str3 = str[:9]		## 012345678

6.开始截取位置为1,不指定结束截取位置,不指定step
str4 = str[1:]		## 123456789

7.不指定开始截取位置,不指定结束截取位置,不指定step
str5 = str[::]		## 0123456789

8.不指定开始截取位置,不指定结束截取位置,指定step为3
str6 = str[::3]		## 0369

2.2.3 len()函数: 获取字符串长度或字节数

用于获取具体字符串的长度或者字符串占用多少个字节。

bash 复制代码
1.获取字符串长度
str = "helloworld"
print(len(str))   	## 10

2.获取字节数
str1 = "您好,欢迎光临world!"
print(len(str1.encode()))   	## 27, 默认采用UTF-8 编码,每个中文汉字及中文的标点符号占3个长度,每个英文字母及英文符号占1个长度
print(len(str1.encode('gbk')))  ## 20, 采用gunk 编码,每个中文汉字及中文的标点符号占2个长度,每个英文字母及英文符号占1个长度

2.2.4 split()函数: 分割字符串

将一个字符串按照指定的分隔符切分成多个子串,并将这些子串保存到列表中(不包含分隔符)返回。

bash 复制代码
1.基本语法格式如下:
str.split(sep,maxsplit)
str:表示要进行分割的字符串;
sep:用于指定分隔符,可以包含多个字符。此参数默认为 None;
maxsplit:可选参数,用于指定分割的次数,最后列表中子串的个数最多为 maxsplit+1。如果不指定或者指定为 -1,则表示分割次数没有限制。

str = "www.baidu.com"
2.采用默认分隔符进行分割
print(str.split())   		## ['www.baidu.com']

3.采用指定字符串作为分隔符进行分割
print(str.split("."))		## ['www', 'baidu', 'com']

4.采用指定字符串作为分隔符进行分割,并限制生成的列表中元素最大的个数
print(str.split(".", 1))	## ['www', 'baidu.com']

2.2.5 join()函数:合并字符串

将列表(或元组)中多个字符串采用固定的分隔符连接成一个字符串。

bash 复制代码
1.基本语法格式如下:
newstr = str.join(iterable)
newstr:表示合并后生成的新字符串;
str:用于指定合并时的分隔符;
iterable:做合并操作的源字符串数据,允许以列表、元组等形式提供。

2.将列表中的字符串通过`.`合并成一个字符串
lst = ['www', 'baidu', 'com']
str = '.'.join(lst)
print(str)  		## www.baidu.com

2.2.6 count()函数:统计字符串出现的次数

用于检索指定字符串在另一字符串中出现的次数,如果检索的字符串不存在,则返回 0,否则返回出现的次数。

bash 复制代码
1.基本语法格式如下:
str.count(sub, start, end)
str:表示原字符串;
sub:表示要检索的字符串;
start:指定检索的起始位置,也就是从什么位置开始检测。如果不指定,默认从头开始检索;
end:指定检索的终止位置,如果不指定,则表示一直检索到结尾。

str = "www.baidu.com"
2.不指定开始和结束的检索位置,即检索整个字符串长度中`.`出现的次数
print(str.count(".")) 		## 2

3.从索引位置4开始到整个字符串末尾检索其中`.`出现的次数
print(str.count(".", 4))	## 1

4.从字符串开头到索引位置3之间检索其中`.`出现的次数
print(str.count(".", 0, 3))	## 0

2.2.7 find()、rfind()函数:查找字符串

用于检索字符串中是否包含目标字符串,如果包含,则返回第一次出现该字符串的索引;反之,则返回 -1。find()是从左边开始检索,而rfind()是从右边开始检索。

bash 复制代码
1.基本语法格式如下:
str.find(sub, start, end)
str:表示原字符串;
sub:表示要检索的目标字符串;
start:表示开始检索的起始位置。如果不指定,则默认从头开始检索;
end:表示结束检索的结束位置。如果不指定,则默认一直检索到结尾。

str = "www.baidu.com"
2.从整个字符串中检索首次出现 "." 的位置索引
print(str.find('.'))		## 3
print(str.rfind('.'))		## 9

3.从索引位置4开始到整个字符串末尾检索首次出现 "." 的位置索引
print(str.find('.', 4))		## 9

4.从索引位置1开始到索引位置4之间检索首次出现 "." 的位置索引
print(str.find('.', 1, 4))	## 3

5.从字符串开头到索引位置3之间检索首次出现 "." 的位置索引
print(str.find('.', 0, 3))	## -1

2.2.8 index()函数:获取字符串索引位置

用于获取指定字符串在某个字符串的索引位置,同 find() 方法作用类似,不同之处在于当指定的字符串不存在时,index() 方法会抛出异常。

bash 复制代码
1.基本语法格式如下:
str.index(sub, start, end)
str:表示原字符串;
sub:表示要检索的子字符串;
start:表示检索开始的起始位置,如果不指定,默认从头开始检索;
end:表示检索的结束位置,如果不指定,默认一直检索到结尾。

str = "www.baidu.com"
2.从整个字符串中检索首次出现 "." 的位置索引
print(str.index('.'))		## 3

3.从索引位置4开始到整个字符串末尾检索首次出现 "." 的位置索引
print(str.index('.', 4))	## 9

4.从索引位置1开始到索引位置4之间检索首次出现 "." 的位置索引
print(str.index('.', 1, 4))	## 3

5.从字符串开头到索引位置3之间检索首次出现 "." 的位置索引
print(str.index('.', 0, 3))	## 报错

2.2.9 ljust()、rjust()和center()函数:字符串对齐

用来进行文本对齐的方法,主要有左对齐,右对齐,剧中对齐等。

bash 复制代码
1.左对齐基本语法格式如下:
str.ljust(width, fillchar)
str.rjust(width, fillchar)
str.center(width, fillchar)
str:表示要进行填充的字符串;
width:表示包括 str 本身长度在内,字符串要占的总长度;
fillchar:作为可选参数,用来指定填充字符串时所用的字符,默认情况使用空格。

str = "www.baidu.com"
2.将字符串进行左对齐
print(str .ljust(20))			## www.baidu.com       , 这里不够20长度,右边默认使用空格进行填充
print(str .ljust(20, "-"))		## www.baidu.com-------

3.将字符串进行右对齐
print(str .rjust(20))			##        www.baidu.com, 这里不够20长度,左边默认使用空格进行填充
print(str .rjust(20, "-"))		## -------www.baidu.com

4.将字符串进行居中对齐
str = "www.baidu.com"
print(str .center(20))			##    www.baidu.com    , 这里不够20长度,左右两边默认使用空格进行填充
print(str .center(20, "-"))		## ---www.baidu.com----

2.2.10 startswith()和endswith()函数:判断字符串是否以指定字符串开头和结尾

startswith()用于检索字符串是否以指定字符串开头,endswith()用于检索字符串是否以指定字符串结尾,如果是则返回True,反之返回 False。

bash 复制代码
1.基本语法格式如下:
str.startswith(sub, start, end)
str.endswith(sub, start, end)
str:表示原字符串;
sub:要检索的子串;
start:指定检索开始的起始位置索引,如果不指定,则默认从头开始检索;
end:指定检索的结束位置索引,如果不指定,则默认一直检索在结束。

str = "www.baidu.com"
2.判断字符串是否以"www"子串开头
print(str.startswith("www"))		## True

3.判断字符串是否以"com"子串结尾
print(str.endswith("com"))			## True

4.判断字符串从索引位置3开始是否以"."子串开始
print(str.endswith(".", 3))			## True

2.2.11 title()、lower() 和 upper()函数:字符串大小写转换

title()用于将字符串中每个单词的首字母转为大写,其他字母全部转为小写;lower()用于将字符串中的所有大写字母转换为小写字母;upper()用于将字符串中的所有小写字母转换为大写字母。

bash 复制代码
str = "www.BaiDu.cOm"
1.将字符串中每个单词的首字母转成大写,其他字符转成小写
print(str.title())			## Www.Baidu.Com

2.将字符串中所有字母都转成小写
print(str.lower())			## www.baidu.com

3.将字符串中所有字母都转成大写
print(str.upper())			## WWW.BAIDU.COM

2.2.12 strip()、lstrip()和rstrip()函数:去除字符串中空格

strip():删除字符串前后(左右两侧)的空格或特殊字符;lstrip():删除字符串前面(左边)的空格或特殊字符;rstrip():删除字符串后面(右边)的空格或特殊字符。

注意:

由于string类型是是不可变的,这里即使通过这三个函数转变得到新的字符串,都只是重新申请内存保存的,原来字符串的值在内存中是没有改变的。

bash 复制代码
str = "  www.baidu.com \t\n\r"
1.删除字符串左右两个的空格和特殊字符
print(str.strip())			##www.baidu.com

2.去掉字符串左侧的空格和特殊字符
print(str.lstrip())			##www.baidu.com 	右边的空格依然存在

3.去掉字符串右侧的空格和特殊字符
print(str.rstrip())	  		##  www.baidu.com	左边边的空格依然存在

2.2.13 encode()和decode()函数:字符串编码转换

encode()将 string 类型转换成 bytes 类型;decode() 方法用于将 bytes 类型的二进制数据转换为 string 类型。默认编码格式为utf-8,解码时要选择和编码时一样的格式,否则会抛出异常。

bash 复制代码
str = "www.baidu.com"
1.默认采用 UTF-8 编码将字符串转换成 bytes 类型
bytes1 = str.encode()
print(bytes1)			## b'www.baidu.com'

2.默认采用 GBK 编码将字符串转换成 bytes 类型
bytes2 = str.encode('GBK')
print(bytes2)			## b'www.baidu.com'

3.将使用UTF-8 编码转换的 bytes 类型解码成string类型
str1 = bytes1.decode()
print(str1)				## www.baidu.com

4.将使用GBK 编码转换的 bytes 类型解码成string类型
str1 = bytes2.decode('GBK')
print(str1)				## www.baidu.com

2.2.14 replace() 替换与映射

字符串不可变,所以 replace() 返回新字符串,原字符串不变。

python 复制代码
s = "苹果很甜,苹果很红"
print(s.replace("苹果", "香蕉"))       # 香蕉很甜,香蕉很红
print(s.replace("苹果", "香蕉", 1))    # 香蕉很甜,苹果很红(只替换1次)

translate() + str.maketrans():按字符逐个映射替换,比多次 replace 更高效。

python 复制代码
table = str.maketrans("abc", "123")
"abcdef".translate(table)    # "123def"

2.2.15 Python 字符串方法速查表

下面按功能分类汇总本文介绍的字符串方法,并标注哪些在 Java String 中有对应、哪些是 Python 独有。

功能分类 方法 作用 示例代码 返回值 Java String 对应
拼接 + 字符串连接 "hello" + "world" 新字符串 concat() / +
拼接 join() 用分隔符合并序列 ".".join(["a","b"]) 新字符串 ❌ 无(Java 用 String.join()
截取 切片 [start:end:step] 按索引截取子串 "hello"[1:3] 新字符串 substring()(不支持 step)
查找 find() / rfind() 查找子串位置,找不到返回 -1 "abc".find("b") 索引或 -1 indexOf() / lastIndexOf()
查找 index() 查找子串位置,找不到抛异常 "abc".index("b") 索引 indexOf()(找不到返回 -1)
查找 count() 统计子串出现次数 "aaa".count("a") 次数 ❌ 无(需手动循环)
查找 startswith() / endswith() 判断开头/结尾 "abc".startswith("a") 布尔值 startsWith() / endsWith()
替换 replace() 替换子串 "a-b".replace("-","+") 新字符串 replace() / replaceAll()
替换 translate() + maketrans() 按字符逐个映射替换 "abc".translate(str.maketrans("ab","12")) 新字符串 ❌ 无(Python 独有)
大小写 upper() / lower() 转大写/小写 "Abc".upper() 新字符串 toUpperCase() / toLowerCase()
大小写 title() 每个单词首字母大写 "hello world".title() 新字符串 ❌ 无(需手动实现)
对齐 ljust() / rjust() / center() 左/右/居中对齐填充 "ab".center(4,"-") 新字符串 ❌ 无(需手动拼接)
去空格 strip() / lstrip() / rstrip() 去除两侧/左/右空白 " a ".strip() 新字符串 trim()(仅去首尾空格)
编码转换 encode() / decode() 字符串与 bytes 互转 "中".encode("utf-8") bytes / 字符串 getBytes() / new String(bytes)
格式化 f-string 内嵌变量格式化 f"x={x}" 新字符串 ❌ 无(Python 独有,Java 用 String.format()

说明:

  • ✅ 表示 Java String 中有功能相近的方法,但语义细节可能不同 (如 find() 找不到返回 -1,而 Java indexOf() 也返回 -1,但 Python index() 会抛异常);
  • ❌ 表示该方法是 Python 独有或 Java 没有直接对应,需要手动实现或用其他 API 替代;
  • translate() + maketrans() 和 f-string 是 Python 特有的高效写法,Java 中分别需要借助 StringBuilderString.format() 实现类似效果。

2.3 字符串与 Java 对比

Python 与 Java 的字符串都不可变,但在拼接方式、常用方法、底层实现上存在明显差异。下面用对比表格和对照代码逐一说明。

核心差异对比:

对比维度 Python Java
不可变性 str 不可变,所有操作返回新对象 String 不可变,所有操作返回新对象
拼接方式 +join() +StringBuilder / StringBuffer
拼接性能 循环 + 性能差,推荐 join() 循环 + 性能差,推荐 StringBuilder
截取子串 切片 [start:end:step] substring(start, end)(不支持 step)
分割字符串 split(sep) 返回列表 split(regex) 返回数组
合并字符串 sep.join(iterable) String.join(sep, iterable)
查找子串 find() 找不到返回 -1,index() 抛异常 indexOf() 找不到返回 -1
判断开头/结尾 startswith() / endswith() startsWith() / endsWith()
大小写转换 upper() / lower() toUpperCase() / toLowerCase()
去除空白 strip() / lstrip() / rstrip() trim()(仅去首尾空格)
替换 replace() / translate() replace() / replaceAll()(支持正则)
格式化 f-string / format() / % String.format() / MessageFormat
可变替代品 无内置(可用 list 模拟) StringBuilder / StringBuffer

Python 版本:

python 复制代码
# 1. 拼接:+ 每次创建新对象,循环中性能差
s1 = "hello"
s2 = s1 + " world"          # 拼接产生新字符串
print(s2)                   # hello world

# 推荐:join() 高效拼接
words = ["Python", "is", "awesome"]
sentence = " ".join(words)  # 用空格合并
print(sentence)             # Python is awesome

# 2. 截取:切片 [start:end:step]
s3 = "hello world"
sub = s3[0:5]               # 截取 "hello"
print(sub)                  # hello
print(s3[6:])               # world(从索引6到末尾)

# 3. 分割与合并
csv = "apple,banana,orange"
fruits = csv.split(",")     # 分割成列表
print(fruits)               # ['apple', 'banana', 'orange']
print("-".join(fruits))     # apple-banana-orange(合并)

# 4. 查找与判断
text = "hello world"
print(text.find("world"))   # 6(找不到返回 -1)
print(text.startswith("hello"))  # True
print(text.upper())         # HELLO WORLD

Java 版本:

java 复制代码
public class StringCompare {
    public static void main(String[] args) {
        // 1. 拼接:+ 每次创建新对象,循环中性能差
        String s1 = "hello";
        String s2 = s1 + " world";      // 拼接产生新字符串
        System.out.println(s2);         // hello world

        // 推荐:StringBuilder 高效拼接
        StringBuilder sb = new StringBuilder();
        String[] words = {"Java", "is", "awesome"};
        for (String w : words) {
            sb.append(w).append(" ");
        }
        System.out.println(sb.toString().trim());  // Java is awesome

        // 2. 截取:substring(start, end),end 不包含
        String s3 = "hello world";
        String sub = s3.substring(0, 5);  // 截取 "hello"
        System.out.println(sub);          // hello
        System.out.println(s3.substring(6));  // world

        // 3. 分割与合并
        String csv = "apple,banana,orange";
        String[] fruits = csv.split(",");  // 分割成数组
        System.out.println(Arrays.toString(fruits));  // [apple, banana, orange]
        System.out.println(String.join("-", fruits)); // apple-banana-orange

        // 4. 查找与判断
        String text = "hello world";
        System.out.println(text.indexOf("world"));   // 6(找不到返回 -1)
        System.out.println(text.startsWith("hello"));  // true
        System.out.println(text.toUpperCase());      // HELLO WORLD
    }
}

运行结果对比:

操作 Python 输出 Java 输出
拼接 + hello world hello world
高效拼接 Python is awesome Java is awesome
截取子串 hello / world hello / world
分割 ['apple', 'banana', 'orange'] [apple, banana, orange]
合并 apple-banana-orange apple-banana-orange
查找 6 6
判断开头 True true
转大写 HELLO WORLD HELLO WORLD

核心要点:

  • 两者字符串都不可变,任何「修改」操作都返回新对象,原字符串不变;
  • 大量拼接时,Python 用 join(),Java 用 StringBuilder,避免 + 的性能陷阱;
  • Python 切片支持 step(如 s[::2] 隔一个取一个),Java substring() 不支持;
  • Python split() 按普通字符串分割,Java split() 按正则表达式分割,跨语言迁移时注意转义;
  • Python find() 与 Java indexOf() 找不到都返回 -1,但 Python index() 会抛异常。

2.4 字符串不可变性与 Java 对比

字符串在 Python 和 Java 中都是**不可变(immutable)**对象,但两者的底层实现、观察方式和操作语义存在明显差异。下面分别用代码演示拼接、截取、替换时内存地址的变化。

Python:用 id() 观察地址变化

python 复制代码
# 1. 字符串拼接:每次 + 都会创建新对象
s1 = "hello"
print(id(s1))          # 140735892123456(示例地址)
s2 = s1 + " world"     # 拼接产生新字符串
print(id(s2))          # 140735892123888(地址变了!)
print(s1)              # hello(原字符串不变)

# 2. 字符串截取:切片同样产生新对象
s3 = "hello world"
sub = s3[0:5]          # 截取 "hello"
print(id(s3))          # 140735892124000
print(id(sub))         # 140735892124320(地址变了!)
print(s3)              # hello world(原字符串不变)

# 3. 字符串替换:replace() 返回新对象
s4 = "apple"
s5 = s4.replace("p", "b")
print(id(s4))          # 140735892124640
print(id(s5))          # 140735892124960(地址变了!)
print(s4)              # apple(原字符串不变)

# 4. 原地修改会报错:字符串不支持 item assignment
# s4[0] = "A"          # ❌ TypeError: 'str' object does not support item assignment

Java:用 ==equals() 对比引用与内容

java 复制代码
public class StringImmutability {
    public static void main(String[] args) {
        // 1. 字符串拼接:+ 创建新对象
        String s1 = "hello";
        String s2 = s1 + " world";   // 拼接产生新对象
        System.out.println(s1 == s2);        // false(引用不同)
        System.out.println(s1.equals(s2));   // false(内容不同)
        System.out.println(s1);              // hello(原字符串不变)

        // 2. 字符串截取:substring() 返回新对象
        String s3 = "hello world";
        String sub = s3.substring(0, 5);     // 截取 "hello"
        System.out.println(s3 == sub);       // false(引用不同)
        System.out.println(s3.equals(sub));  // false(内容不同)
        System.out.println(s3);              // hello world(原字符串不变)

        // 3. 字符串替换:replace() 返回新对象
        String s4 = "apple";
        String s5 = s4.replace('p', 'b');
        System.out.println(s4 == s5);        // false(引用不同)
        System.out.println(s4.equals(s5));   // false(内容不同)
        System.out.println(s4);              // apple(原字符串不变)

        // 4. 内容相同但引用不同:== 比较引用,equals 比较内容
        String a = new String("hello");
        String b = new String("hello");
        System.out.println(a == b);          // false(两个不同对象)
        System.out.println(a.equals(b));     // true(内容相同)

        // 5. 字符串常量池:直接赋值会复用常量池中的对象
        String c = "hello";
        String d = "hello";
        System.out.println(c == d);          // true(指向常量池同一个对象)
    }
}

运行结果对比:

操作 Python(id() 变化) Java(== / equals()
拼接 + 地址变化,原字符串不变 引用不同,原字符串不变
截取 substring / 切片 地址变化,原字符串不变 引用不同,原字符串不变
替换 replace 地址变化,原字符串不变 引用不同,原字符串不变
内容相同但非同一对象 id() 可能相同(小整数/短字符串驻留) ==falseequals()true
直接赋值相同字面量 id() 相同(字符串驻留) ==true(常量池复用)

不可变设计上的异同总结:

对比维度 Python Java
不可变性 str 不可变,所有操作返回新对象 String 不可变,所有操作返回新对象
观察方式 id() 观察对象地址 == 比较引用,equals() 比较内容
内存优化 短字符串/小整数有驻留(intern)机制 字符串常量池(String Pool)复用字面量
可变替代品 无内置可变字符串(可用 list 模拟) StringBuilder / StringBuffer
拼接性能 循环 + 性能差,推荐 join() 循环 + 性能差,推荐 StringBuilder

核心要点:

  • Python 和 Java 的字符串都不可变,任何「修改」操作实际都是创建新对象,原字符串保持不变;
  • Python 用 id() 观察地址变化,Java 用 ==(引用)和 equals()(内容)双重判断;
  • 两者都有字符串驻留/常量池机制,但不要依赖 它来判断字符串相等------Python 用 == 比较内容,Java 用 equals() 比较内容;
  • 大量拼接时,Python 用 join(),Java 用 StringBuilder,避免性能陷阱。

2.5 f-string格式化

python 复制代码
first_name = "ada"
last_name = "lovelace"
full_name = f"{first_name} {last_name}"

print(f"Hello, {full_name.title()}!")
# 输出:Hello, Ada Lovelace!

# 格式化数字
pi = 3.1415926
print(f"Pi = {pi:.2f}")        # Pi = 3.14
print(f"Pi = {pi:.4f}")        # Pi = 3.1416

# 对齐和填充
print(f"{'left':<10}")     # 'left      '
print(f"{'right':>10}")    # '     right'
print(f"{'center':^10}")   # ' center   '
print(f"{'center':*^10}")  # '*center***'

# 千分位分隔符
number = 1234567
print(f"{number:,}")       # 1,234,567

旧式格式化方法:

python 复制代码
name = "Alice"
age = 25

# format()方法
print("My name is {} and I am {} years old.".format(name, age))

# 位置参数
print("My name is {0} and I am {1} years old.".format(name, age))

# 关键字参数
print("My name is {n} and I am {a} years old.".format(n=name, a=age))

# %格式化(最古老)
print("My name is %s and I am %d years old." % (name, age))

2.6 本章小结

字符串核心要点:

  • 字符串是不可变序列,支持索引与切片;
  • 拼接用 + 或 f-string,不要用 + 拼接大量字符串 (性能差),推荐 join()
  • split()join() 是一对互逆操作;
  • find() 找不到返回 -1index() 找不到抛异常;
  • 编码转换:encode() 转 bytes,decode() 转回 str,编解码格式必须一致。

f-string 是 Python 3.6+ 推荐的格式化方式 ,比 %format() 更简洁、更易读,新代码应优先使用。

三、数字

3.1 整数

python 复制代码
# 整数运算
2 + 3      # 5
3 - 2      # 1
2 * 3      # 6
3 / 2      # 1.5(Python 3中/总是返回浮点数)
3 // 2     # 1(整除,类似Java的/)
3 % 2      # 1(取余)
2 ** 3     # 8(乘方)
10 ** 6    # 1000000

3.2 浮点数

python 复制代码
0.1 + 0.2    # 0.30000000000000004(浮点数精度问题)

# 处理精度问题
from decimal import Decimal, getcontext
getcontext().prec = 6
result = Decimal('0.1') + Decimal('0.2')
print(result)    # 0.3

3.3 与Java数字对比

对比维度 Java Python
整数范围 int 32 位有符号(-2^31 ~ 2^31-1),long 64 位 整数无上限,可任意大
除法语义 int / int 整除,结果仍为 int / 总是返回浮点数,// 才是整除
类型转换 显式强转 (int)(double),可能溢出或截断 动态转换 int()float(),自动扩容
精度处理 BigDecimal 处理精确计算 Decimal 处理精确计算

整数范围对比:

java 复制代码
// Java:int 溢出会回绕
int max = Integer.MAX_VALUE;   // 2147483647
int overflow = max + 1;        // -2147483648(溢出回绕,不报错)
long big = 9223372036854775807L;  // long 也有上限
python 复制代码
# Python:整数无上限,自动扩容
big = 10 ** 100          # 任意大,不溢出
print(big)               # 1000...(100个0)
print(2 ** 1000)         # 超大整数也能精确表示

除法语义对比:

java 复制代码
// Java:int / int 整除,小数被截断
int a = 7 / 2;           // 3(不是3.5)
double b = 7 / 2;        // 3.0(先整除再转double)
double c = 7.0 / 2;      // 3.5(需显式用浮点数)
python 复制代码
# Python:/ 总是返回浮点数,// 才是整除
a = 7 / 2                # 3.5
b = 7 // 2               # 3(整除)
c = 7 % 2                # 1(取余)

类型转换对比:

java 复制代码
// Java:显式强转,可能溢出或截断
int i = (int) 3.7;       // 3(截断小数)
int j = (int) 3000000000L;  // -1294967296(溢出回绕)
String s = String.valueOf(25);  // 数字转字符串
int k = Integer.parseInt("25"); // 字符串转数字
python 复制代码
# Python:动态转换,自动扩容
i = int(3.7)             # 3(截断小数)
j = int(3000000000)      # 3000000000(不溢出)
s = str(25)              # 数字转字符串
k = int("25")            # 字符串转数字

精度处理对比:

java 复制代码
// Java:BigDecimal 处理精确计算
import java.math.BigDecimal;
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
BigDecimal sum = a.add(b);   // 0.3(精确)
python 复制代码
# Python:Decimal 处理精确计算
from decimal import Decimal
a = Decimal('0.1')
b = Decimal('0.2')
sum = a + b              # 0.3(精确)

核心要点:

  • Python 整数无上限,Java int 溢出会回绕,这是最容易踩的坑;
  • Python 的 / 与 Java 的 / 语义不同,跨语言迁移时务必注意;
  • 涉及金额等精确计算,Java 用 BigDecimal,Python 用 Decimal,都不要用浮点数。
python 复制代码
# 字符串转数字
age = int("25")
price = float("19.99")

# 数字转字符串
str_age = str(25)
str_price = str(19.99)

# 浮点数转整数(截断小数)
int(3.7)     # 3
int(-3.7)    # -3

# 四舍五入
round(3.7)   # 4
round(3.2)   # 3
round(3.14159, 2)  # 3.14

四、总结与进阶建议

4.1 全文回顾

本文以 Java 为参照系,系统梳理了 Python 三大基础主题:

主题 核心结论
变量 变量是标签,一切皆对象、一切皆引用
字符串 不可变序列,掌握 14 类方法与 f-string 即可覆盖日常开发
数字 整数无上限,浮点数有精度问题,用 Decimal 处理精确计算

4.2 常见误区自查

  • ❌ 认为 a = b 复制了对象 → ✅ 只是让 a 指向 b 指向的对象;
  • ❌ 认为 list2 = list1 后修改 list2 不影响 list1 → ✅ 会互相影响,需用 copy()deepcopy()
  • ❌ 用 + 在循环里拼接大量字符串 → ✅ 用 join()
  • ❌ 用 == 判断浮点数相等 → ✅ 用 math.isclose()Decimal

4.3 下一步学习建议

  • 深入理解 Python 数据模型:阅读官方文档 Data Model
  • 练习列表、字典、集合的底层操作,巩固可变对象心智模型;
  • 学习函数参数传递(传对象引用),理解它与变量赋值的联系;
  • 动手实现一个深拷贝工具函数,检验对递归与引用的理解。
相关推荐
智搜广告2 小时前
智搜广告:科技行业AI回答优化公司如何破局
大数据·python·elasticsearch·geo
IpdataCloud2 小时前
AI智能体调用工具怎么核验来源IP?归属地、网络类型与代理风险识别(含Python代码)
数据库·python·tcp/ip
2601_966949653 小时前
只拿到股票代码还不够:量化程序到底还需要哪些标的信息?——从数据建模开始理解股票元数据
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash
蒸鱼Yuzheng3 小时前
Python 游戏测试开发怎么准备:日志解析、接口校验、并发与可维护性
自动化测试·python·测试开发·面试题·游戏测试
Ivanqhz3 小时前
BM1688 双核架构
python·深度学习·神经网络·cnn·mlir
Java后端的Ai之路4 小时前
Python进阶探索17 - Python中的深拷贝与浅拷贝
人工智能·python·ai·浅拷贝·深拷贝
西柚研究生1234564 小时前
论文分析17:YOLOv11_UAVNet:无人机航拍图像专用目标检测算法
人工智能·python·深度学习·算法·目标检测
沧海一笑-dj5 小时前
【Python】Python学习笔记-初识 Python
python·python安装·python解释器
打工仔折腾 AI5 小时前
FastAPI 从本机到生产服务器:Nginx+Gunicorn+Uvicorn 完整部署实录
人工智能·后端·python·nginx·fastapi·gunicorn