Python 基础入门:Java 开发者视角
文章目录
面向有 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 包管理
| 概念 | Java | Python |
|---|---|---|
| 包管理工具 | Maven / Gradle | pip / poetry / uv |
| 依赖声明文件 | pom.xml / build.gradle | requirements.txt / pyproject.toml |
| 中央仓库 | Maven Central | PyPI |
| 安装依赖 | mvn dependency:resolve | pip install -r requirements.txt |
| 安装单个包 | 在 pom.xml 添加 dependency | pip 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 解释
| Java | Python | |
|---|---|---|
| 流程 | .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__" - 如果文件被别人 import(
import 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 完全不同:
| 概念 | Java | Python |
|---|---|---|
| 最小代码单元 | 一个 .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 的"入口文件"。它有三个作用:
- 标识目录为 package:没有这个文件,Python 不认为这个目录是包(Python 3.3+ 有 namespace package 可以省略,但建议始终加上)
- 包被 import 时执行:
import agents时,实际执行的是agents/__init__.py里的顶层代码 - 控制对外暴露的接口:可以在里面做统一导出,简化外部使用
# 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 package | Python 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
这一行做了三件事:
- 查找:找到
os.py文件 - 执行:运行里面的所有顶层代码(函数定义、变量赋值等)
- 赋值:把执行结果打包成一个模块对象,赋值给变量
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 解释器从上到下逐行执行这个文件里的顶层代码。执行完之后,VERSION、helper、MyClass 这些名字就变成了模块对象的属性,你就能通过 utils.VERSION、utils.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]]
常用操作对比:
| 操作 | Java | Python |
|---|---|---|
| 创建 | new ArrayList<>() | [] 或 list() |
| 添加 | .add(item) | .append(item) |
| 获取 | .get(index) | [index] |
| 长度 | .size() | len(list) |
| 切片 | 无原生支持 | list[1:3] |
| 判空 | .isEmpty() | not list 或 len(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 类似
常用字符串方法对比
| 操作 | Java | Python |
|---|---|---|
| 分割 | 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+ 的函数式接口(Function、Consumer 等)提供了类似能力,但 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())
关键差异:
| 维度 | Java | Python |
|---|---|---|
| 构造函数 | 与类同名 | __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 = [] # 每个实例有自己的列表
字段定义对比:
| 维度 | Java | Python |
|---|---|---|
| 实例字段声明 | 类体中 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 类比来理解:
| Python | Java 类比 | 第一个参数 | 能访问什么 |
|---|---|---|---|
| 实例方法 | 普通方法 | self(实例) | 实例字段 + 类字段 |
类方法 @classmethod | 能感知子类的静态方法 | cls(类) | 只有类字段 |
静态方法 @staticmethod | static 方法 | 无 | 什么都不能直接访问 |
实例方法(最常用)
绑定到实例,通过 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, in | Collection 接口 |
| 可调用协议 | __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 里最接近的概念是实现 Callable 或 Runnable 接口,但 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 的访问控制
| 机制 | Java | Python |
|---|---|---|
| 强制私有 | 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("清理")
| 维度 | Java | Python |
|---|---|---|
| 捕获关键字 | catch | except |
| 异常变量 | 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,仍可访问 |
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)