面向有 Java 经验的开发者,通过对比两种语言的核心概念差异,快速建立 Python 的正确思维模型。


1. 环境与工具链

1.1 开发环境

推荐使用 VS Code + Python 扩展(ms-python.python),安装后核心操作三步:

  • 选择解释器:底部状态栏点击 Python 版本,选择目标解释器(类似 IDEA 里选 Project SDK)
  • 运行脚本:右上角三角按钮 ▶,或右键选择 “Run Python File in Terminal”
  • 调试:行号左侧点击打断点,F5 启动调试,F10 步过,F11 步入(与 IntelliJ 操作逻辑一致)

1.2 包管理

概念JavaPython
包管理工具Maven / Gradlepip / poetry / uv
依赖声明文件pom.xml / build.gradlerequirements.txt / pyproject.toml
中央仓库Maven CentralPyPI
安装依赖mvn dependency:resolvepip install -r requirements.txt
安装单个包在 pom.xml 添加 dependencypip install <包名>

关键区别:Java 项目天然隔离(每个项目有自己的 classpath),Python 默认全局安装包,因此需要**虚拟环境(venv)**来隔离——可以理解为"每个项目一个独立的 Python 运行时副本"。

1.3 运行方式

# 安装依赖
pip install anthropic python-dotenv

# 设置环境变量并运行(等价于 java -DKEY=value -jar app.jar)
ANTHROPIC_API_KEY=sk-xxx python s01_agent_loop/code.py

命令前的 KEY=VALUE 是 shell 语法,表示"仅对本次命令临时设置环境变量"。


2. 核心概念对比

2.1 类型系统

Java 是静态强类型,变量声明时类型固定:

String name = "hello";
name = 123; // 编译错误

Python 是动态强类型

name = "hello"
name = 123  # 完全合法,变量可以指向不同类型

"1" + 1  # TypeError!强类型意味着不会隐式转换

Python 的 type hints(def foo(x: int) -> str)只是给 IDE 和静态检查工具看的提示,运行时完全不强制。

2.2 编译 vs 解释

JavaPython
流程.java → 编译 → .class 字节码 → JVM 执行.py → 解释器逐行执行
错误发现时机编译不过就跑不了语法错误在执行到那一行时才爆
中间产物.class 文件.pyc 缓存(对开发者透明)

2.3 入口点

Java 必须有明确入口:

public static void main(String[] args) { ... }

Python 没有强制入口,脚本从第一行开始执行。惯用写法:

if __name__ == "__main__":
    main()

含义:如果这个文件是被直接运行的(而不是被别人 import 的),就执行下面的代码。相当于一个可选的 main 方法。

if __name__ == "__main__": 详解

__name__ 是什么?

每个 Python 模块都有一个内置变量 __name__,它的值取决于这个文件是怎么被使用的:

  • 如果文件被直接运行python server.py),__name__ 的值是字符串 "__main__"
  • 如果文件被别人 importimport server),__name__ 的值是模块名(如 "server"

用一个例子彻底理解

假设有两个文件:

# server.py
def start():
    print("服务器启动了")

print(f"server.py 的 __name__ = {__name__}")

if __name__ == "__main__":
    start()
# main.py
import server   # 导入 server 模块
print("main.py 执行完毕")

场景一:直接运行 python server.py

server.py 的 __name__ = __main__
服务器启动了

__name__"__main__",条件成立,start() 被调用。

场景二:运行 python main.py(间接 import server)

server.py 的 __name__ = server
main.py 执行完毕

__name__"server"(模块名),条件不成立,start() 不会被调用。server.py 的顶层代码(print 那行)仍然执行了,但 if 块里的代码被跳过了。

为什么需要这个机制?

因为 Python 的 import 会执行目标文件的所有顶层代码(参见 3.2 节)。如果你在 server.py 顶层直接写 start(),那别人 import server 时服务器就意外启动了。if __name__ == "__main__": 就是一道"门",把"只有直接运行才该执行的逻辑"保护起来。

类比 Java

Java 不需要这个机制,因为 Java 的 import 不会执行代码,而且入口是强制的 public static void main——JVM 只会调用你指定的那个 main 方法,不会因为 import 了某个类就触发它的 main。Python 没有这种天然隔离,所以需要 if __name__ 这个运行时判断来手动隔离。

实际项目中的典型用法

# utils.py —— 既是工具库,也可以独立运行做测试
def calculate(x, y):
    return x + y

def validate(data):
    return data is not None

# 直接运行时执行测试,被 import 时不执行
if __name__ == "__main__":
    # 简单的自测代码
    assert calculate(1, 2) == 3
    assert validate("hello") == True
    print("所有测试通过!")

这种模式在 Python 项目中非常常见:模块既提供可复用的函数/类,又可以独立运行做简单验证。

2.4 作用域与访问控制

Java 有 public / private / protected / default 四级访问控制,由编译器强制执行。

Python 没有真正的访问控制,全靠命名约定:

命名含义
name公开
_name内部使用(约定,不强制)
__name触发名称改写(name mangling),子类不容易覆盖

Python 社区的哲学是"我们都是成年人"——约定优于强制。详细机制见 5.9 节。


3. import 与模块系统(重点)

这是 Java 开发者学 Python 时最容易产生误解的地方。

3.0 先厘清 Java 自身的 package 与 module

在对比 Python 之前,先把 Java 自己的两个概念理清楚,因为很多 Java 开发者自己也混淆它们:

Package(包)—— 从 Java 1.0 就有

Package 本质上是命名空间 + 目录约定com.meituan.service.UserService 里的 com.meituan.service 就是包名,对应磁盘上的 com/meituan/service/ 目录结构。它解决的问题是:避免类名冲突(两个团队都写了 Utils 类,放不同包里就不冲突了)。

关键特征:package 只是逻辑分组,没有强制的封装边界。只要你知道全限定类名,任何 public 类都能被任何地方访问,package 拦不住。

Module(模块)—— Java 9 引入的 JPMS

Java 9 引入了 Java Platform Module System(module-info.java),这才是真正的"模块"。它解决的问题是:package 管不住的封装性。一个 module 可以声明"我只对外暴露这几个包,内部实现包外面不许访问":

// module-info.java
module com.meituan.user {
    exports com.meituan.user.api;       // 只暴露 api 包
    requires com.meituan.common;        // 声明依赖
    // com.meituan.user.internal 包外部完全不可见
}

类比关系:Java 的 package 像是"文件夹",module 像是"带门禁的楼层"。大多数业务项目(尤其 Spring Boot 项目)其实没用 JPMS,所以你日常接触的主要是 package。

为什么要先说这个?

因为 Python 里"模块"和"包"的含义跟 Java 完全不同:

概念JavaPython
最小代码单元一个 .java 文件 = 一个类一个 .py 文件 = 一个模块
包(package)目录 + 命名空间约定包含 __init__.py 的目录
模块(module)Java 9 JPMS,强封装边界就是一个 .py 文件,import 后变成对象
封装粒度类级别(public/private)模块级别(命名约定)

Java 里一个文件只能有一个 public 类,文件是类的容器;Python 里一个文件本身就是一个模块,里面可以有任意多个函数、类、变量,它们都是这个模块对象的属性。

Python 的 package 详解

问题:为什么需要 package?

当项目只有几个 .py 文件时,模块就够用了。但当项目变大,你会有几十上百个 .py 文件,全平铺在一个目录下就乱了。Package 解决的就是模块的组织和分层问题——用文件夹把相关模块归到一起。

类比 Java:Java 的 com.meituan.user.service 包对应 com/meituan/user/service/ 目录,Python 的 package 也是目录,但有一个关键标志文件 __init__.py

什么是 package?

一个包含 __init__.py 文件的目录就是一个 package。看一个实际的项目结构:

my_project/
├── main.py
└── agents/                    # 这是一个 package
    ├── __init__.py            # 标志文件,可以为空
    ├── base.py                # agents.base 模块
    ├── chat.py                # agents.chat 模块
    └── tools/                 # 这是一个子 package(嵌套)
        ├── __init__.py
        ├── search.py          # agents.tools.search 模块
        └── calculator.py      # agents.tools.calculator 模块

使用时:

# 在 main.py 中
from agents.chat import ChatAgent          # 从 agents 包的 chat 模块导入
from agents.tools.search import web_search # 从嵌套子包导入
import agents.base                         # 导入整个模块

__init__.py 的作用

__init__.py 相当于这个 package 的"入口文件"。它有三个作用:

  1. 标识目录为 package:没有这个文件,Python 不认为这个目录是包(Python 3.3+ 有 namespace package 可以省略,但建议始终加上)
  2. 包被 import 时执行import agents 时,实际执行的是 agents/__init__.py 里的顶层代码
  3. 控制对外暴露的接口:可以在里面做统一导出,简化外部使用
# agents/__init__.py
from .base import BaseAgent      # 从子模块导入,挂到包级别
from .chat import ChatAgent

__all__ = ['BaseAgent', 'ChatAgent']  # 控制 from agents import * 的行为

这样外部就可以直接写 from agents import ChatAgent,而不需要知道它具体在哪个子模块里。类比 Java 的话,这有点像在一个 package 里提供一个 Facade 类,统一暴露内部能力。

对比 Java 的 package

维度Java packagePython package
物理形式目录目录 + __init__.py
标识方式package com.x.y; 声明目录里有 __init__.py
有无初始化逻辑无(package 本身不能执行代码)有(__init__.py 在 import 时执行)
能否作为对象使用不能(package 不是对象)能(package 也是模块对象)
嵌套方式目录嵌套目录嵌套,每层都要有 __init__.py

最关键的区别:Java 的 package 只是一个命名空间标签,它本身不能"做事";Python 的 package 本质上也是一个模块对象(由 __init__.py 生成),它可以有属性、可以执行代码、可以被赋值给变量。

import agents
print(type(agents))  # <class 'module'>  —— package 也是模块对象!

3.1 Java 的 import 是"声明"

import java.util.List; // 只是告诉编译器 List 的全限定名
// 不 import 也能用:java.util.List<String> list = ...

Java 的 import 是编译期的名称解析,不创建对象,不执行代码。

3.2 Python 的 import 是"赋值"

import os

这一行做了三件事:

  1. 查找:找到 os.py 文件
  2. 执行:运行里面的所有顶层代码(函数定义、变量赋值等)
  3. 赋值:把执行结果打包成一个模块对象,赋值给变量 os

因此 os 就是一个普通变量,指向一个模块对象。os.getcwd() 本质上是对象属性访问,和 someObject.someMethod() 没有区别。

什么是"顶层代码"?

"顶层代码"指的是没有缩进、不在任何函数或类内部的代码。用 Java 类比,可以理解为一个类里所有的静态初始化块 + 静态字段赋值——在类加载时就会执行的那些东西。

看一个具体例子,假设有个 utils.py

# ===== 以下都是"顶层代码",import 时会被执行 =====

import os                          # 顶层 import(会执行)
VERSION = "1.0"                    # 顶层变量赋值(会执行)
print("utils 模块被加载了")         # 顶层语句(import 时就会打印!)

def helper():                      # 顶层函数定义("定义"本身会执行,但函数体不会)
    print("我是 helper")
    x = 1 + 1                     # 这行不是顶层代码,调用 helper() 时才执行

class MyClass:                     # 顶层类定义(类体会执行,用于设置类属性)
    name = "test"                  # 会执行(设置类属性)

    def method(self):             # 方法定义会执行,但方法体不会
        print("method called")    # 调用 instance.method() 时才执行

# ===== 以上所有顶层代码在 import utils 时全部执行 =====

当你写 import utils 时,Python 解释器从上到下逐行执行这个文件里的顶层代码。执行完之后,VERSIONhelperMyClass 这些名字就变成了模块对象的属性,你就能通过 utils.VERSIONutils.helper()utils.MyClass() 来访问它们了。

顶层代码的执行规则总结
代码形式import 时是否执行说明
X = 10变量赋值,X 成为模块属性
import xxx嵌套导入其他模块
print("hello")任意顶层语句都会执行
def foo(): ...定义执行,函数体不执行foo 成为模块属性,调用时才执行体
class Bar: ...类体执行,方法体不执行Bar 成为模块属性
函数/方法内部的代码只有被调用时才执行
对比 Java

Java 里 class Foo { static int x = 10; }x = 10 在类加载时执行,但你不能在类体里随便写 System.out.println("hello")——Java 只允许静态初始化块和字段赋值。Python 没有这个限制,顶层可以写任何语句,import 时都会执行。

这也是为什么 Python 社区约定把"只有被直接运行才该执行的代码"放在 if __name__ == "__main__": 里面,防止被别人 import 时意外触发副作用(比如启动服务器、打印调试信息等)。

3.3 Python 的 import 是运行时语句

因为是运行时执行的,所以可以条件性导入(Java 里不可想象):

if platform == "windows":
    import win_module
else:
    import unix_module

3.4 import 的几种形式

import os                    # os 变量指向模块对象
import os as operating_sys   # 起别名
from os import getcwd        # 只取模块中的某个属性
from os import *             # 导入模块所有公开名称(不推荐)

3.5 定义顺序规则

Java 编译器在编译期做完整的符号解析,所以类内方法互相调用不受顺序限制。Python 没有这种"提前看到后面定义"的能力,一切都是运行时按顺序执行的。

唯一的硬性规则:名字在被"求值"(evaluate)的那一刻,必须已经存在于当前作用域中。

关键在于区分"定义时"和"执行时":

# 阶段1:模块加载,逐行执行顶层语句

def agent_loop():
    result = TOOL_HANDLERS[name]()  # 定义时:只记录字节码,不执行这行
    return result

# 此时 agent_loop 已存在,但 TOOL_HANDLERS 还不存在 —— 没问题!

TOOL_HANDLERS = {"bash": run_bash}  # 这行执行时,run_bash 必须已存在

agent_loop()  # 阶段2:调用时,函数体真正执行,此时 TOOL_HANDLERS 必须已存在

三种典型场景

场景一:函数内引用其他函数(互相调用)——顺序随便放:

def a():
    return b()  # 合法:b 只需在 a() 被调用时存在

def b():
    return 42

a()  # 调用时 b 已存在 ✓

def 只是"登记"函数对象,不执行函数体,所以函数之间互相引用不受定义顺序限制。

场景二:顶层代码引用变量——必须先定义:

print(x)  # NameError! 顶层代码是立即执行的,x 还不存在
x = 10

场景三:默认参数值——定义时立即求值:

DEFAULTS = [1, 2, 3]

def foo(items=DEFAULTS):  # 定义时立即求值 DEFAULTS,所以 DEFAULTS 必须在前面
    pass

默认参数是个例外——它在 def 语句执行时就被求值了,不是调用时。

底层原理

函数体内的名字引用,编译成字节码后是 LOAD_GLOBAL 指令——运行时才去 globals() 字典里查找。这就是为什么函数体内的引用不要求定义顺序,它是延迟解析的。

对比 Java

Java 的静态字段初始化也有顺序依赖(static int a = b;b 定义之前会得到 0),但方法互相调用不受限(编译器帮你处理了)。Python 更直白:没有编译器帮你"提前看到后面的定义",规则简单但需要你自己保证顺序。


4. “一切皆对象”——理解模块的本质

4.1 模块是对象

这是理解 Python 的关键心智模型。模块是对象,函数是对象,类本身也是对象。

import os

print(type(os))   # <class 'module'>
print(dir(os))    # 列出 os 模块上所有属性和方法

4.2 用 Java 类比

你可以把 Python 的模块想象成 Java 里一个全是 static 成员的工具类

// Java:调用静态方法
import java.nio.file.Files;
Files.readAllBytes(path);
# Python:看起来像静态调用,但底层是对象属性访问
import os
os.getcwd()

区别在于:Java 的 Files 是一个类,readAllBytes 是静态方法;Python 的 os 是一个真正的对象实例,getcwd 是它的属性(恰好是个函数对象)。

4.3 模块对象可以像普通对象一样操作

import os

# 赋值给别的变量
my_os = os
my_os.getcwd()  # 完全正常

# 放进列表
import sys
modules = [os, sys]

# 动态获取属性(类似 Java 反射)
func = getattr(os, 'getcwd')
func()  # 等价于 os.getcwd()

这些操作在 Java 里对一个"包"或"类的静态部分"是做不到的。

4.4 模块是单例的

Python 的模块系统有内置缓存机制:第一次 import os 时,解释器创建模块对象并放进 sys.modules 字典。之后无论在多少个文件里 import os,拿到的都是同一个对象:

import os
import os as os2

print(os is os2)  # True,同一个对象

类比 Java:模块就像 Spring 容器里的单例 Bean——你只能注入使用,不能自己 new 一个新的。

4.5 不能"new"一个模块

os 不是一个类,而是一个已经实例化好的单例对象。你没有 Os 类可以去实例化。

技术上,module 类型确实存在,可以手动创建空壳:

import types
fake_os = types.ModuleType("fake_os")
fake_os.name = "我是假的"

但这只是空壳,没有任何实际功能。真正的能力来自 os.py 文件里的代码定义。

4.6 打破 Java 思维定式

Java 里"对象"和"可以 new"是强绑定的。Python 里要打破这个假设:

对象类型创建方式是否通过构造函数
普通实例obj = MyClass()
模块import xxx
函数def foo(): ...
类本身class Foo: ...

它们都是对象,但创建方式各不相同,不走统一的构造函数路径。


5. 语法差异速查:Python vs Java

这一章专门讲日常编码中 Python 和 Java 在语法层面的具体差异。不涉及设计哲学,只讲"同样的事情,Python 怎么写"。

5.1 基础语法结构

缩进代替花括号

Python 用缩进(通常 4 个空格)来表示代码块,没有花括号 {},也没有分号 ;

// Java
if (x > 0) {
    System.out.println("正数");
} else {
    System.out.println("非正数");
}
# Python
if x > 0:
    print("正数")
else:
    print("非正数")

缩进不对会直接报 IndentationError,这不是风格问题,是语法要求。

变量声明

Java 必须声明类型,Python 直接赋值:

// Java
int count = 0;
String name = "hello";
final double PI = 3.14;
# Python
count = 0
name = "hello"
PI = 3.14  # 没有 final,大写命名只是约定"别改它"

Python 没有 final / const 关键字。常量用全大写命名表示"这是常量,请别修改",但运行时不强制。

类型注解(Type Hints)

Python 3.5+ 支持类型注解,但只是给 IDE 和静态检查工具看的,运行时不强制:

def greet(name: str, times: int = 1) -> str:
    return name * times

# 即使传错类型,运行时也不会报错(除非逻辑上出错)
greet(123, "abc")  # 不会抛 TypeError(但 IDE 会警告)

5.2 数据结构

列表(List)

Python 的 list 类似 Java 的 ArrayList,但语法更简洁,且可以混合类型:

// Java
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
String first = names.get(0);
# Python
names = ["Alice", "Bob"]
names.append("Charlie")
first = names[0]

# 可以混合类型(Java 泛型不允许)
mixed = [1, "hello", True, [1, 2]]

常用操作对比:

操作JavaPython
创建new ArrayList<>()[]list()
添加.add(item).append(item)
获取.get(index)[index]
长度.size()len(list)
切片无原生支持list[1:3]
判空.isEmpty()not listlen(list) == 0
包含.contains(item)item in list

切片(Slicing)—— Java 没有的语法

nums = [0, 1, 2, 3, 4, 5]

nums[1:4]    # [1, 2, 3]  —— 从索引1到索引4(不含4)
nums[:3]     # [0, 1, 2]  —— 从头到索引3
nums[3:]     # [3, 4, 5]  —— 从索引3到末尾
nums[-1]     # 5          —— 负索引,从末尾倒数
nums[-2:]    # [4, 5]     —— 最后两个元素
nums[::2]    # [0, 2, 4]  —— 步长为2
nums[::-1]   # [5, 4, 3, 2, 1, 0]  —— 反转

字典(Dict)

Python 的 dict 类似 Java 的 HashMap

// Java
Map<String, Integer> ages = new HashMap<>();
ages.put("Alice", 25);
int age = ages.get("Alice");
# Python
ages = {"Alice": 25, "Bob": 30}
ages["Charlie"] = 28
age = ages["Alice"]

# 安全获取(类似 getOrDefault)
age = ages.get("Unknown", 0)  # 不存在时返回默认值 0

元组(Tuple)—— Java 没有的类型

元组是不可变的列表,用圆括号表示:

# 元组一旦创建不能修改
point = (3, 4)
x, y = point  # 解构赋值(Java 没有)

# 常用于函数返回多个值
def get_name_and_age():
    return "Alice", 25  # 实际返回的是元组

name, age = get_name_and_age()

Java 里要返回多个值,你得定义一个 DTO 类或者用 Pair。Python 直接用元组就行。

集合(Set)

# Python
fruits = {"apple", "banana", "cherry"}  # 注意:{} 不是空集合,是空字典
empty_set = set()  # 空集合要用 set()

"apple" in fruits  # True,O(1) 查找

5.3 字符串

f-string(格式化字符串)

Python 3.6+ 的 f-string 是最常用的字符串格式化方式,类似 Java 的字符串模板(Java 21+):

// Java
String msg = "Hello, " + name + "! You are " + age + " years old.";
// 或 Java 21+
String msg = STR."Hello, \{name}! You are \{age} years old.";
# Python f-string
msg = f"Hello, {name}! You are {age} years old."

# 可以在 {} 里写任意表达式
msg = f"结果是 {1 + 2}"           # "结果是 3"
msg = f"大写: {name.upper()}"     # "大写: ALICE"
msg = f"保留两位: {3.14159:.2f}"  # "保留两位: 3.14"

多行字符串

# 三引号,保留换行和缩进
sql = """
SELECT *
FROM users
WHERE age > 18
"""

# Java 13+ 的 text block 类似

常用字符串方法对比

操作JavaPython
分割str.split(",")str.split(",")
去空格str.trim() / str.strip()str.strip()
替换str.replace("a", "b")str.replace("a", "b")
大小写str.toUpperCase()str.upper()
是否包含str.contains("x")"x" in str
拼接列表String.join(",", list)",".join(list)
开头/结尾str.startsWith("x")str.startswith("x")

注意 Python 的 startswith 是小写 w,和 Java 的 startsWith 不同。

5.4 控制流

for 循环

Python 的 for 是 for-each 风格,没有传统的 for(int i=0; i<n; i++)

// Java
for (int i = 0; i < 10; i++) { ... }
for (String item : list) { ... }
# Python
for i in range(10):       # range(10) 生成 0~9
    ...
for item in list:         # 直接遍历
    ...
for i, item in enumerate(list):  # 同时拿索引和值
    ...

while 循环

while condition:
    ...
    if should_stop:
        break
else:
    # 循环正常结束(没有被 break)时执行
    # Java 没有这个语法
    print("循环正常完成")

三元表达式

// Java
String result = x > 0 ? "正" : "非正";
# Python(注意顺序不同!)
result = "正" if x > 0 else "非正"

match-case(Python 3.10+,类似 switch)

match status_code:
    case 200:
        print("成功")
    case 404:
        print("未找到")
    case _:          # 类似 default
        print("其他")

5.5 函数

定义与默认参数

def greet(name: str, greeting: str = "Hello") -> str:
    return f"{greeting}, {name}!"

greet("Alice")              # "Hello, Alice!"
greet("Alice", "Hi")        # "Hi, Alice!"
greet(greeting="Hey", name="Bob")  # 关键字参数,可以不按顺序

Java 没有默认参数,只能通过方法重载模拟。Python 的关键字参数让调用更灵活。

位置参数与关键字参数

这是 Python 函数参数体系中最基础的概念。Java 只有一种传参方式——按位置传,Python 有两种:

def connect(host, port, timeout=30):
    print(f"连接 {host}:{port},超时 {timeout}s")

# 位置参数(positional):按顺序对应形参
connect("localhost", 8080, 10)

# 关键字参数(keyword):用 name=value 形式,不受顺序限制
connect(port=8080, host="localhost", timeout=10)

# 混合使用:位置参数必须在关键字参数前面
connect("localhost", 8080, timeout=10)

位置参数靠"第几个"来匹配形参,关键字参数靠"名字"来匹配。Java 中 connect("localhost", 8080) 只能按位置传,你没法写 connect(port=8080, host="localhost")

Python 还允许用 /* 强制限定参数的传递方式:

def example(pos_only, /, normal, *, kw_only):
    pass

# pos_only:只能按位置传(/ 左边的)
# normal:位置或关键字都行(/ 和 * 之间的)
# kw_only:只能按关键字传(* 右边的)

example(1, 2, kw_only=3)       # OK
example(1, normal=2, kw_only=3) # OK
# example(pos_only=1, ...)     # TypeError! pos_only 只能按位置
# example(1, 2, 3)             # TypeError! kw_only 只能按关键字

这个语法在标准库和框架中很常见,目的是让 API 更清晰、更不容易误用。

可变参数:*args**kwargs

Python 的 * 运算符在函数参数中有两个完全相反的作用,取决于它出现在定义侧还是调用侧:

位置语法作用结果
函数定义(形参)def f(*args)收集参数多个值打包成元组
函数调用(实参)f(*args)展开参数元组/列表拆成独立参数

一句话记忆:定义时收集,调用时展开。方向相反,符号相同。

定义时收集——把多个散落的值打包:

def log(*messages):
    print(type(messages))  # <class 'tuple'>
    for msg in messages:
        print(msg)

log("hello", "world", 123)
# 三个独立参数被收集成元组 ("hello", "world", 123)

对应 Java 的可变参数:

// Java
void log(String... messages) {
    // messages 是 String[],本质一样——多个参数收集成数组
}

区别:Java 的可变参数类型固定且只能放最后;Python 的 *args 收集为元组,可以混合任意类型。

调用时展开——把一个集合拆成独立参数:

def add(a, b, c):
    return a + b + c

nums = [10, 20, 30]

# 不展开——报错,传了一个参数(列表),函数需要三个
# add(nums)  # TypeError

# 展开——列表拆成三个独立参数
add(*nums)  # 等价于 add(10, 20, 30),结果 60

Java 中没有这个语法。如果你有一个 int[] 想传给 add(int a, int b, int c),只能手动写 add(arr[0], arr[1], arr[2])

**kwargs:对字典做同样的事

* 处理位置参数(列表/元组),** 处理关键字参数(字典):

# 定义时收集:关键字参数打包成字典
def config(**kwargs):
    print(type(kwargs))  # <class 'dict'>
    for key, value in kwargs.items():
        print(f"{key} = {value}")

config(host="localhost", port=8080)
# {'host': 'localhost', 'port': 8080}

# 调用时展开:字典拆成关键字参数
settings = {"host": "localhost", "port": 8080}
config(**settings)  # 等价于 config(host="localhost", port=8080)

组合使用:透传任意参数

装饰器和代理函数中最经典的模式——*args, **kwargs 全部兜住,再全部转发:

import time

def timer(func):
    def wrapper(*args, **kwargs):       # 收集:接住所有参数
        start = time.time()
        result = func(*args, **kwargs)  # 展开:原样传给被装饰函数
        print(f"{func.__name__} 耗时: {time.time() - start:.2f}s")
        return result
    return wrapper

@timer
def query(sql, timeout=30, retry=3):
    ...

query("SELECT *", timeout=10)
# wrapper 收到 args=("SELECT *",), kwargs={"timeout": 10}
# 然后展开为 func("SELECT *", timeout=10)

wrapper 不需要知道被装饰函数有几个参数、叫什么名字。这就是 Python 装饰器能通用于任何函数签名的原因。

赋值时的 *(解构收集)

Python 3 还允许在赋值语句中用 * 收集剩余元素:

first, *rest = [1, 2, 3, 4, 5]
# first = 1, rest = [2, 3, 4, 5]

head, *middle, tail = [1, 2, 3, 4, 5]
# head = 1, middle = [2, 3, 4], tail = 5

Java 中你得写 list.get(0)list.subList(1, list.size()) 来实现同样的效果。

参数体系总结

def full_example(
    pos_only,          # 位置参数(/ 前面时为仅位置)
    /,
    normal,            # 普通参数(位置或关键字都行)
    *args,             # 收集剩余位置参数
    kw_only,           # 仅关键字参数(* 后面的)
    **kwargs           # 收集剩余关键字参数
):
    pass

参数从左到右的顺序是固定的:仅位置 → 普通 → *args → 仅关键字 → **kwargs

Lambda 表达式

// Java
Function<Integer, Integer> square = x -> x * x;
list.stream().filter(x -> x > 0).collect(Collectors.toList());
# Python
square = lambda x: x * x

# 配合内置函数使用
filtered = list(filter(lambda x: x > 0, numbers))
# 但更 Pythonic 的写法是列表推导式(见 5.6)
filtered = [x for x in numbers if x > 0]

Python 的 lambda 只能写一行表达式,不能写多行语句。复杂逻辑就老老实实用 def

函数是一等公民

def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

# 函数可以赋值给变量、放进字典
operations = {
    "+": add,
    "-": subtract,
}

result = operations["+"](1, 2)  # 3

Java 8+ 的函数式接口(FunctionConsumer 等)提供了类似能力,但 Python 天然支持,不需要接口包装。

5.6 列表推导式(List Comprehension)

这是 Python 最有特色的语法之一,Java 完全没有对应物:

# 基本形式:[表达式 for 变量 in 可迭代对象]
squares = [x ** 2 for x in range(10)]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

# 带条件过滤
evens = [x for x in range(20) if x % 2 == 0]
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

# 嵌套
matrix = [[1, 2], [3, 4], [5, 6]]
flat = [num for row in matrix for num in row]
# [1, 2, 3, 4, 5, 6]

# 字典推导式
word_lengths = {word: len(word) for word in ["hello", "world"]}
# {'hello': 5, 'world': 5}

# 集合推导式
unique_lengths = {len(word) for word in ["hi", "hello", "hey"]}
# {2, 5, 3}

用 Java Stream 对比:

// Java Stream
List<Integer> squares = IntStream.range(0, 10)
    .map(x -> x * x)
    .boxed()
    .collect(Collectors.toList());

Python 的列表推导式更简洁,但只适合简单的转换/过滤。复杂逻辑还是用普通 for 循环更清晰。

5.7 类与面向对象

类定义

// Java
public class Dog {
    private String name;
    private int age;

    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String bark() {
        return this.name + " says woof!";
    }
}
# Python
class Dog:
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age

    def bark(self) -> str:
        return f"{self.name} says woof!"

dog = Dog("旺财", 3)
print(dog.bark())

关键差异:

维度JavaPython
构造函数与类同名__init__
this 引用隐式显式传 self(第一个参数)
访问控制private / public命名约定(_ 前缀)
继承extends(单继承)括号里写父类(支持多继承)
接口interface + implements无接口,用抽象类或鸭子类型

构造函数:只能有一个 __init__

Python 不支持方法重载,一个类只能有一个 __init__。如果写两个,后面的会覆盖前面的:

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

    def __init__(self, name, age):  # 覆盖了上面那个!
        self.name = name
        self.age = age

# User("Alice")  # TypeError! 只有第二个 __init__ 存在

Java 可以有多个构造函数重载,编译器根据参数类型和数量选择调用哪个。Python 没有编译期类型信息,做不到这种分派。

替代方案一——默认参数(最常用):

class User:
    def __init__(self, name: str, age: int = 0, email: str = None):
        self.name = name
        self.age = age
        self.email = email

User("Alice")                          # 只传名字
User("Alice", 25)                      # 传名字和年龄
User("Alice", 25, "alice@test.com")    # 全传

替代方案二——@classmethod 作为命名构造函数(语义差异大时用):

class User:
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age

    @classmethod
    def from_dict(cls, data: dict) -> "User":
        return cls(data["name"], data["age"])

    @classmethod
    def from_string(cls, s: str) -> "User":
        name, age = s.split(",")
        return cls(name.strip(), int(age.strip()))

    @classmethod
    def anonymous(cls) -> "User":
        return cls("Anonymous", 0)

# 多种创建方式
u1 = User("Alice", 25)
u2 = User.from_dict({"name": "Bob", "age": 30})
u3 = User.from_string("Charlie, 28")
u4 = User.anonymous()

类比 Java:这就是静态工厂方法模式(LocalDate.of()LocalDate.parse()LocalDate.now())。cls 参数指向类本身,子类调用时 cls 是子类。

@classmethod vs @staticmethod 的区别:

@classmethod@staticmethod
第一个参数cls(类本身)
能访问类属性不能(除非硬编码类名)
继承时的行为cls 指向子类和继承无关
Java 类比静态工厂方法静态工具方法

类的字段定义

这是 Java 开发者最容易困惑的地方。Python 的字段分两种,机制完全不同:

实例字段——在 __init__ 中通过 self.xxx 创建,不需要提前"声明":

class User:
    def __init__(self, name: str, age: int):
        self.name = name       # 实例字段,直接创建
        self.age = age         # 不需要像 Java 那样先写 private String name;
        self._id = id(self)    # 也是实例字段

Python 的对象本质上是一个字典(self.__dict__),随时可以往里面加 key。甚至可以在任何方法里动态添加字段:

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

    def load_profile(self):
        self.email = "alice@test.com"  # 调用这个方法后才有 email 字段

u = User("Alice")
# u.email  # AttributeError! 还没调用 load_profile
u.load_profile()
print(u.email)  # 现在有了

类字段(类变量)——在类体中直接定义,所有实例共享,类似 Java 的 static 字段:

class User:
    species = "human"   # 类字段,所有实例共享
    count = 0           # 类字段

    def __init__(self, name: str):
        User.count += 1     # 通过类名访问
        self.name = name    # 实例字段

print(User.species)  # "human"
u = User("Alice")
print(User.count)    # 1

注意可变类型作为类字段的坑:

class Team:
    members = []  # 危险!所有实例共享同一个列表

t1 = Team()
t2 = Team()
t1.members.append("Alice")
print(t2.members)  # ["Alice"]  —— t2 也被影响了!

# 正确做法:放在 __init__ 里
class Team:
    def __init__(self):
        self.members = []  # 每个实例有自己的列表

字段定义对比:

维度JavaPython
实例字段声明类体中 private String name;不需要声明,__init__self.name = x 直接创建
类字段(静态)static int count;类体中直接 count = 0
字段能否动态添加不能,编译期固定能,任何时候 self.xxx = yyy
字段类型编译期确定且不可变运行时可以随时改变类型

dataclass:简化样板代码(Python 3.7+)

如果觉得每次都要在 __init__ 里写赋值太啰嗦,Python 提供了 @dataclass 装饰器:

from dataclasses import dataclass

@dataclass
class User:
    name: str
    age: int
    email: str | None = None

# 自动生成 __init__、__repr__、__eq__ 等
u = User("Alice", 25)
print(u)  # User(name='Alice', age=25, email=None)

类比 Java:这就是 Lombok 的 @Data 或 Java 16+ 的 record——声明字段,自动生成构造函数、toString、equals 等样板代码。

三种方法:实例方法、类方法、静态方法

Python 类中的方法分三种,区别在于"第一个参数绑定的是什么":

class MathTool:
    precision = 2  # 类字段

    def __init__(self, name: str):
        self.name = name  # 实例字段

    # 实例方法:第一个参数是 self(实例本身)
    def describe(self):
        return f"{self.name},精度 {self.precision}"

    # 类方法:第一个参数是 cls(类本身)
    @classmethod
    def set_precision(cls, p: int):
        cls.precision = p  # 修改类字段,影响所有实例

    # 静态方法:没有自动绑定的参数
    @staticmethod
    def add(a, b):
        return a + b
tool = MathTool("计算器")

tool.describe()              # 实例方法:通过实例调用
MathTool.set_precision(4)    # 类方法:通过类调用(也可以通过实例调用)
MathTool.add(1, 2)           # 静态方法:通过类调用(也可以通过实例调用)

用 Java 类比来理解:

PythonJava 类比第一个参数能访问什么
实例方法普通方法self(实例)实例字段 + 类字段
类方法 @classmethod能感知子类的静态方法cls(类)只有类字段
静态方法 @staticmethodstatic 方法什么都不能直接访问

实例方法(最常用)

绑定到实例,通过 self 访问实例的所有属性和方法:

class BankAccount:
    def __init__(self, owner: str, balance: float = 0):
        self.owner = owner
        self.balance = balance

    def deposit(self, amount: float):
        self.balance += amount       # 通过 self 访问实例字段
        return self.balance

    def transfer(self, other: "BankAccount", amount: float):
        self.balance -= amount
        other.deposit(amount)        # 调用另一个实例的方法

a = BankAccount("Alice", 1000)
b = BankAccount("Bob", 500)
a.transfer(b, 200)  # Alice 给 Bob 转 200

Java 中所有非 static 方法都是实例方法,Python 也一样——没有装饰器的方法默认就是实例方法。

类方法(@classmethod

绑定到类而非实例,第一个参数 cls 是类本身。最常见的用途是作为"替代构造函数"(前面已讲过),另一个用途是操作类级别的状态:

class Connection:
    _pool = []        # 类字段:连接池
    _max_size = 10    # 类字段:最大连接数

    def __init__(self, host: str):
        self.host = host
        self.active = True

    @classmethod
    def get_pool_size(cls) -> int:
        return len(cls._pool)

    @classmethod
    def configure(cls, max_size: int):
        cls._max_size = max_size  # 修改类字段

    @classmethod
    def create(cls, host: str) -> "Connection":
        if len(cls._pool) >= cls._max_size:
            raise RuntimeError("连接池已满")
        conn = cls(host)          # cls() 等于调用构造函数
        cls._pool.append(conn)
        return conn

cls 的关键价值在继承时体现——子类调用类方法时,cls 指向子类:

class MySQLConnection(Connection):
    pass

conn = MySQLConnection.create("localhost")  # cls 是 MySQLConnection,不是 Connection
print(type(conn))  # <class 'MySQLConnection'>

如果用 Java 的 static 方法,你做不到这一点——static 方法不知道是哪个子类在调用它。

静态方法(@staticmethod

和类没有任何绑定关系,既不接收 self 也不接收 cls。本质上就是一个"恰好放在类命名空间里的普通函数":

class Validator:
    @staticmethod
    def is_valid_email(email: str) -> bool:
        return "@" in email and "." in email

    @staticmethod
    def is_valid_age(age: int) -> bool:
        return 0 < age < 150

# 调用时不需要创建实例
Validator.is_valid_email("test@example.com")  # True
Validator.is_valid_age(25)                     # True

等价于 Java 的:

public class Validator {
    public static boolean isValidEmail(String email) {
        return email.contains("@") && email.contains(".");
    }
}

什么时候用哪种?

决策很简单:

  • 需要访问或修改实例状态(self.xxx)→ 实例方法
  • 需要访问或修改类状态(cls.xxx),或者需要作为替代构造函数 → 类方法
  • 既不需要实例也不需要类,只是逻辑上属于这个类的工具函数 → 静态方法

实际工程中,实例方法占绝大多数(90%+),类方法主要用于工厂模式和类级配置,静态方法用得最少(很多时候放在类外面作为模块级函数也完全可以)。

继承

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

    def speak(self):
        raise NotImplementedError  # 类似 Java 的抽象方法

class Dog(Animal):  # 括号里写父类
    def speak(self):
        return f"{self.name}: 汪汪!"

class Cat(Animal):
    def speak(self):
        return f"{self.name}: 喵~"

# 多继承(Java 不支持)
class DogCat(Dog, Cat):
    pass

鸭子类型(Duck Typing)

Python 不需要接口声明,只要对象有对应的方法就能用:

# 不需要定义 interface Printable { void print(); }
# 只要有 print_info 方法就行
class Dog:
    def print_info(self):
        print("I'm a dog")

class Car:
    def print_info(self):
        print("I'm a car")

def show(obj):
    obj.print_info()  # 不关心 obj 是什么类型,有这个方法就行

show(Dog())  # OK
show(Car())  # OK

Java 里你必须定义一个接口让两个类都 implements,Python 不需要——“如果它走起来像鸭子、叫起来像鸭子,那它就是鸭子”。

5.8 魔术方法(Dunder Methods)

魔术方法是 __xxx__ 格式的特殊方法,Python 用它们实现"运算符重载"和"协议约定"。

用 Java 类比:Java 中你想让对象能被 System.out.println 打印,就重写 toString();想比较就实现 Comparable 接口。Python 做同样的事,但不靠接口继承,靠的是"你定义了某个魔术方法,解释器就认为你支持某个协议"。

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):       # 对应 a + b
        return Vector(self.x + other.x, self.y + other.y)

    def __repr__(self):             # 对应 print(obj)、调试显示
        return f"Vector({self.x}, {self.y})"

    def __len__(self):              # 对应 len(obj)
        return 2

    def __getitem__(self, index):   # 对应 obj[0]
        return (self.x, self.y)[index]
a = Vector(1, 2)
b = Vector(3, 4)
print(a + b)      # Vector(4, 6)  —— 解释器调用 a.__add__(b)
print(len(a))     # 2             —— 解释器调用 a.__len__()
print(a[0])       # 1             —— 解释器调用 a.__getitem__(0)

与 Java 接口的本质区别

Java 是名义类型(nominal typing):必须显式声明 implements Comparable,编译器才认你。Python 是结构类型(structural typing / duck typing):只要定义了 __len__len() 就能作用于你的对象,不需要声明任何接口。解释器在运行时检查"这个对象有没有 __len__ 方法",有就调用,没有就抛 TypeError

常用魔术方法分类(按协议理解)

协议魔术方法对应的使用方式Java 类比
对象生命周期__init__, __new__, __del__构造、创建、析构构造函数、finalize()
字符串表示__repr__, __str__repr(obj), print(obj)toString()
运算符__add__, __sub__, __mul__, __eq__, __lt__, __hash__+, -, *, ==, <, hash()Comparable, equals(), hashCode()
容器协议__len__, __getitem__, __setitem__, __contains__len(), obj[k], obj[k]=v, inCollection 接口
可调用协议__call__obj()实现 Callable 接口
上下文管理器__enter__, __exit__with obj:AutoCloseable
迭代器协议__iter__, __next__for x in obj:Iterator 接口

__call__:让实例像函数一样被调用

class Retry:
    def __init__(self, max_retries=3):
        self.max_retries = max_retries

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            for i in range(self.max_retries):
                try:
                    return func(*args, **kwargs)
                except Exception:
                    if i == self.max_retries - 1:
                        raise
        return wrapper

@Retry(max_retries=5)
def fetch_data():
    ...

定义了 __call__,实例就变成了可调用对象。这是 Python 装饰器能用类来实现的基础。Java 里最接近的概念是实现 CallableRunnable 接口,但 Python 不需要接口声明,任何定义了 __call__ 的对象都能直接用 () 调用。

综合示例:实现一个"看起来像字典"的缓存

class CacheClient:
    def __init__(self, max_size=100):
        self._store = {}
        self._max_size = max_size

    def __getitem__(self, key):       # 支持 cache["key"]
        return self._store.get(key)

    def __setitem__(self, key, value):  # 支持 cache["key"] = value
        if len(self._store) >= self._max_size:
            self._evict()
        self._store[key] = value

    def __len__(self):                # 支持 len(cache)
        return len(self._store)

    def __contains__(self, key):      # 支持 "key" in cache
        return key in self._store

    def _evict(self):
        oldest = next(iter(self._store))
        del self._store[oldest]
cache = CacheClient(max_size=2)
cache["a"] = 1          # __setitem__
cache["b"] = 2
print("a" in cache)     # __contains__ -> True
print(len(cache))       # __len__ -> 2
cache["c"] = 3          # 触发 _evict,淘汰 "a"
print(cache["a"])       # __getitem__ -> None

这个类没有继承任何接口,但它"看起来像一个字典"——因为它实现了容器协议的魔术方法。这就是 Python 的哲学:协议优于继承,约定优于强制

5.9 双下划线与"可见度控制"详解

2.4 节简要提到了 Python 的命名约定,这里展开讲清楚底层机制。

核心认知:Python 没有真正的访问控制。 它不像 Java 有 private/protected/public 这种编译器强制的可见性。Python 的做法是"约定 + 轻度防护"。

三种命名约定的详细机制

(1)单下划线前缀 _name:君子协定

class Service:
    def __init__(self):
        self._connection = None  # 约定:外部不应直接访问

    def _internal_method(self):  # 约定:内部方法
        pass

解释器不做任何限制,外部代码 obj._connection 完全能访问。类比 Java 中你在注释里写"internal use only"但没加 private

唯一的"硬"效果:from module import * 不会导入以 _ 开头的名字。

(2)双下划线前缀 __name:名称改写(Name Mangling)

class Account:
    def __init__(self, balance):
        self.__balance = balance  # 会被改写

    def get_balance(self):
        return self.__balance     # 类内部正常访问

a = Account(100)
# print(a.__balance)       # AttributeError!
print(a._Account__balance) # 100 —— 改写后的真实名字

解释器做的事情很机械:把 __balance 改写成 _Account__balance。这不是"加密"或"禁止访问",只是让名字变得不那么容易撞上。

设计目的不是封装,而是防止继承时的名字冲突

class Base:
    def __init__(self):
        self.__x = 10  # 改写为 _Base__x

class Child(Base):
    def __init__(self):
        super().__init__()
        self.__x = 20  # 改写为 _Child__x,不会覆盖父类的!

如果没有 name mangling,子类定义同名属性会意外覆盖父类的内部状态。Java 中 private 字段天然对子类不可见,不存在这个问题。Python 没有真正的 private,所以用 name mangling 来"隔离命名空间"。

(3)双下划线前后 __name__:系统保留

这就是魔术方法的命名格式。前后都有双下划线表示"这是 Python 语言/解释器使用的特殊名字",用户不应该自己发明这种格式的名字。

对比 Java 的访问控制

机制JavaPython
强制私有private 字段,编译器拒绝外部访问不存在
约定私有无(要么 private 要么不是)_name
防继承冲突private 天然隔离__name(name mangling)
包级可见default(无修饰符)_name + __all__ 控制导出

实际工程中怎么选?

绝大多数情况用 _name 就够了。Python 社区的共识是"我们都是成年人"(we’re all consenting adults),靠约定而非强制。__name 只在你写框架/基类、确实担心子类会意外覆盖你的内部属性时才用。日常业务代码几乎不需要。

5.10 异常处理

// Java
try {
    int result = 10 / 0;
} catch (ArithmeticException e) {
    System.out.println("除零错误: " + e.getMessage());
} finally {
    System.out.println("清理");
}
# Python
try:
    result = 10 / 0
except ZeroDivisionError as e:
    print(f"除零错误: {e}")
except (ValueError, TypeError) as e:  # 捕获多种异常
    print(f"值或类型错误: {e}")
else:
    print("没有异常时执行")  # Java 没有这个
finally:
    print("清理")
维度JavaPython
捕获关键字catchexcept
异常变量catch (E e)except E as e
无异常分支else
抛出异常throw new E()raise E()
自定义异常extends Exception继承 Exception
检查型异常有(必须 catch 或 throws)无(所有异常都是非检查型)

Python 没有 checked exception,不需要在函数签名上声明 throws。这意味着你不会被迫写一堆 try-catch,但也意味着你需要自己注意哪些函数可能抛异常。

5.11 with 语句(资源管理)

Python 的 with 等价于 Java 的 try-with-resources:

// Java
try (FileReader reader = new FileReader("file.txt")) {
    // 使用 reader
}  // 自动关闭
# Python
with open("file.txt") as f:
    content = f.read()
# 自动关闭,即使发生异常

任何实现了 __enter____exit__ 方法的对象都能用 with(类似 Java 的 AutoCloseable 接口)。

5.12 解构赋值与多重赋值

这些是 Python 特有的简洁语法(* 在赋值中的收集作用也在 5.5 节有讲解):

# 多重赋值
a, b, c = 1, 2, 3

# 交换变量(Java 需要临时变量)
a, b = b, a

# 解构列表
first, *rest = [1, 2, 3, 4, 5]
# first = 1, rest = [2, 3, 4, 5]

head, *middle, tail = [1, 2, 3, 4, 5]
# head = 1, middle = [2, 3, 4], tail = 5

# 解构字典(遍历时)
user = {"name": "Alice", "age": 25}
for key, value in user.items():
    print(f"{key}: {value}")

5.13 常用内置函数

Python 有很多全局内置函数,不需要 import:

len([1, 2, 3])          # 3 —— 长度(Java 里是 .size() / .length)
type(obj)               # 查看类型(类似 instanceof 但返回类型对象)
isinstance(obj, str)    # 类型检查(等价于 Java 的 instanceof)
range(5)                # 生成 0~4 的序列
enumerate(list)         # 遍历时同时获取索引
zip(list1, list2)       # 并行遍历两个列表
map(func, iterable)     # 对每个元素应用函数
filter(func, iterable)  # 过滤
sorted(list)            # 返回排序后的新列表
reversed(list)          # 反转迭代器
any([False, True])      # True —— 任一为真
all([True, True])       # True —— 全部为真
min(1, 2, 3)            # 1
max(1, 2, 3)            # 3
sum([1, 2, 3])          # 6
abs(-5)                 # 5
round(3.14159, 2)       # 3.14
print("hello")          # 输出(Java 的 System.out.println)
input("请输入: ")        # 读取用户输入(Java 的 Scanner)

5.14 None、布尔值与真值判断

None(等价于 Java 的 null)

x = None

# 判断是否为 None,用 is 而不是 ==
if x is None:
    print("空")
if x is not None:
    print("非空")

真值判断(Truthy / Falsy)

Python 中以下值被视为"假"(Falsy),其他一切都是"真":

# 以下都是 Falsy
False, None, 0, 0.0, "", [], {}, set()

# 因此可以直接用对象做条件判断
if not my_list:       # 等价于 if len(my_list) == 0
    print("列表为空")

if name:              # 等价于 if name is not None and name != ""
    print("有名字")

Java 里只有 boolean 能做条件,Python 里任何对象都能当条件用。

5.15 装饰器(Decorator)

装饰器是 Python 特有的语法糖,用于在不修改函数代码的情况下增强函数行为。类比 Java 的 AOP / 注解 + 切面:

import time

# 定义一个计时装饰器
def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        print(f"{func.__name__} 耗时: {time.time() - start:.2f}s")
        return result
    return wrapper

# 使用装饰器(类似 Java 的 @Transactional、@Cacheable)
@timer
def slow_function():
    time.sleep(1)
    return "done"

slow_function()  # 自动打印耗时

@timer 本质上等价于 slow_function = timer(slow_function),就是把函数传给另一个函数,返回一个增强版。


6. 总结:Java → Python 思维转换清单

Java 思维Python 现实
import 是编译期声明import 是运行时赋值语句
变量有固定类型变量只是名字标签,可以指向任何对象
访问控制由编译器强制靠命名约定(_/__),不强制
必须有 main 入口脚本从第一行执行
包是命名空间模块是真正的对象
对象 = new 出来的实例一切皆对象,创建方式多样
项目依赖天然隔离需要 venv 手动隔离
编译期发现错误运行到那一行才报错
方法定义顺序无所谓(编译器处理)名字被求值时必须已存在(运行时顺序执行)
实现接口才能用于特定场景定义魔术方法即支持对应协议(鸭子类型)
private 编译器强制隔离__name 只是 name mangling,仍可访问
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐