python从入门到精通-第0章: 思维模式碰撞

25 阅读11分钟

第0章: 思维模式碰撞

在写第一行 Python 代码之前,先把你脑子里 Java/Kotlin 的假设摆出来。 最大的障碍不是语法,而是用 Java 的思维写 Python。

0.1 编译型 vs 解释型: 运行机制本质差异

Java/Kotlin 对比

Java/Kotlin 的运行流程:

  1. 源代码 → 编译器 → 字节码(.class)
  2. 字节码 → JVM → 解释执行 + JIT编译 → 机器码
// Java: 编译时类型检查,运行时JVM执行
public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello");
    }
}
// javac Hello.java → java Hello
// Kotlin: 编译为JVM字节码(或JS/Native)
fun main() {
    println("Hello")
}
// kotlinc Hello.kt → kotlin HelloKt

Python 实现

Python 的运行流程:

  1. 源代码 → Python解释器 → 字节码(.pyc)
  2. 字节码 → CPython虚拟机 → 解释执行(无JIT,3.13实验性JIT除外)
# Python: 运行时类型检查,解释执行
def hello():
    print("Hello")

if __name__ == "__main__":
    hello()
# python hello.py — 无编译步骤

核心差异

维度Java/KotlinPython
类型检查编译时运行时
编译步骤必须可选(.pyc缓存)
执行方式JVM字节码CPython字节码
JIT编译HotSpot JIT成熟3.13实验性JIT
启动速度较慢(JVM预热)
运行速度快(JIT优化后)慢(纯解释)

何时使用

  • Java/Kotlin: 大型长期项目、性能敏感、团队协作
  • Python: 快速原型、数据处理、脚本自动化、AI/ML

0.2 静态类型 vs 动态强类型: 不是"没类型"

Java/Kotlin 对比

// Java: 变量有类型,编译器强制检查
String name = "Alice";     // name 永远是 String
// name = 42;              // 编译错误!
List<Integer> nums = new ArrayList<>();  // 泛型约束元素类型
// Kotlin: 类型推断但仍然是静态类型
val name = "Alice"         // 推断为 String
// name = 42               // 编译错误!
val nums: List<Int> = listOf(1, 2, 3)    // 泛型约束

Python 实现

# Python: 变量没有类型,值有类型
name = "Alice"    # name 绑定到 str 对象
name = 42         # name 重新绑定到 int 对象 — 完全合法!

# Python 是强类型:不会隐式转换
# "hello" + 42    # TypeError! 必须显式转换
result = "hello" + str(42)  # "hello42"

# 查看类型
print(type(name))        # <class 'int'>
print(isinstance(name, int))  # True

核心差异

  • Java/Kotlin: 变量有类型,编译器在编译时检查
  • Python: 变量只是名字标签,可以贴到任何对象上。但 Python 是强类型——不会像 JavaScript 那样隐式转换

常见陷阱

# 陷阱: 以为 Python 是弱类型
# "5" + 3     # TypeError,不会变成 "53" 或 8
# "5" * 3     # 但这个合法!→ "555"(字符串重复)

# 陷阱: 以为 isinstance 可以替代类型注解
def add(a, b):
    return a + b

add(1, 2)       # 3
add("a", "b")   # "ab"
add([1], [2])   # [1, 2]
# 同一个函数,不同类型,不同行为 — 鸭子类型

0.3 鸭子类型 vs 接口/抽象类

Java/Kotlin 对比

// Java: 必须显式声明实现接口
public interface Drawable {
    void draw();
}

public class Circle implements Drawable {
    @Override
    public void draw() {
        System.out.println("Drawing circle");
    }
}

public void render(Drawable d) {
    d.draw();  // 编译器保证 d 有 draw() 方法
}
// Kotlin: 同样需要显式声明
interface Drawable {
    fun draw()
}

class Circle : Drawable {
    override fun draw() = println("Drawing circle")
}

fun render(d: Drawable) {
    d.draw()
}

Python 实现

# Python: 不关心"你是什么",只关心"你能做什么"
class Circle:
    def draw(self):
        print("Drawing circle")

class Square:
    def draw(self):
        print("Drawing square")

class Text:
    def draw(self):
        print("Drawing text")

def render(obj):
    obj.draw()  # 只要 obj 有 draw() 方法就行,不需要任何接口声明

render(Circle())  # Drawing circle
render(Square())  # Drawing square
render(Text())    # Drawing text
# 甚至连类都不需要:
class AnonymousDrawer:
    pass

d = AnonymousDrawer()
d.draw = lambda: print("Drawing anonymously")
render(d)  # Drawing anonymously

核心差异

  • Java/Kotlin: 显式契约 — "我声明我是什么"
  • Python: 隐式契约 — "我只要能做就行"
  • Python 3.8+ 的 Protocol 提供了静态检查版本的鸭子类型(见第8章)

常见陷阱

# 陷阱: 试图用 isinstance 检查类型来模拟接口
# 不 Pythonic!
def render(obj):
    if isinstance(obj, Circle):
        obj.draw()
    elif isinstance(obj, Square):
        obj.draw()
    # 每加一种类型就要改这里 — 违反开闭原则

# 正确: 直接调用,让 Python 的异常告诉你问题
def render(obj):
    obj.draw()  # 如果 obj 没有 draw(),会抛出 AttributeError

0.4 EAFP vs LBYL: 异常处理哲学

Java/Kotlin 对比

// Java: LBYL (Look Before You Leap) — 先检查再操作
Map<String, Integer> map = new HashMap<>();
String key = "name";

// 先检查 key 是否存在
if (map.containsKey(key)) {
    Integer value = map.get(key);
    System.out.println(value);
} else {
    System.out.println("Key not found");
}
// Kotlin: 更简洁但仍然是 LBYL
val map = mapOf<String, Int>()
val value = map["name"]  // 返回 null
if (value != null) {
    println(value)
} else {
    println("Key not found")
}
// 或者用 Elvis 运算符
val result = map["name"] ?: "default"

Python 实现

# Python: EAFP (Easier to Ask Forgiveness than Permission)
# 先操作,出了异常再处理
my_dict = {}

try:
    value = my_dict["name"]
    print(value)
except KeyError:
    print("Key not found")

# 更 Pythonic: 用 dict.get() 提供默认值
value = my_dict.get("name", "default")
print(value)

# EAFP 的经典场景: 文件操作
import os

# LBYL 风格(不推荐)
if os.path.exists("data.txt"):
    with open("data.txt") as f:
        data = f.read()
# 问题: 检查和操作之间文件可能被删除(竞态条件)

# EAFP 风格(推荐)
try:
    with open("data.txt") as f:
        data = f.read()
except FileNotFoundError:
    data = ""

核心差异

  • Java: LBYL — 先检查条件,再执行操作。if (obj != null), if (list.contains())
  • Python: EAFP — 先尝试操作,用 try/except 处理异常。更快(无重复检查),更安全(无竞态条件)

何时使用

场景推荐风格
文件/资源操作EAFP(避免竞态条件)
字典访问dict.get() 或 EAFP
类型检查EAFP(try操作,except TypeError)
业务逻辑前置条件LBYL(if 检查更清晰)

0.5 一切皆对象: 函数、类、模块都是一等公民

Java/Kotlin 对比

// Java: 方法不是对象(Java 8 Lambda 是特殊对象)
public class Example {
    public void greet() {
        System.out.println("Hello");
    }

    public void run() {
        // 无法直接传递 greet 方法
        // 需要用方法引用或 Lambda 包装
        Runnable r = this::greet;  // 方法引用
        new Thread(r).start();
    }
}
// Class 对象存在,但功能有限
Class<String> clazz = String.class;
// Kotlin: 函数是一等公民,但类和函数的地位仍不同
fun greet(name: String) = "Hello, $name"

// 函数可以作为参数
fun run(action: () -> Unit) {
    action()
}
run { greet("World") }

// 但类本身不能像值一样传递
// 反射需要 KClass
val clazz = String::class

Python 实现

# Python: 一切皆对象,包括函数、类、模块、甚至类型本身

# 1. 函数是对象
def greet(name):
    return f"Hello, {name}"

print(type(greet))        # <class 'function'>
print(greet.__name__)     # 'greet'
print(id(greet))          # 内存地址

# 函数可以赋值给变量
say_hello = greet
print(say_hello("World"))  # Hello, World

# 函数可以存入数据结构
actions = [greet, len, print]
for action in actions:
    print(action)  # <function greet>, <built-in function len>, ...

# 函数可以作为参数和返回值
def apply(func, value):
    return func(value)

result = apply(greet, "Python")  # Hello, Python

def make_multiplier(n):
    def multiplier(x):
        return x * n
    return multiplier

double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5))   # 10
print(triple(5))   # 15

# 2. 类是对象
class MyClass:
    pass

print(type(MyClass))      # <class 'type'> — 类本身是 type 的实例!
print(MyClass.__name__)   # 'MyClass'
MyClass.alias = MyClass   # 类可以赋值给变量

# 3. 类型本身是对象
print(type(int))      # <class 'type'>
print(type(type))     # <class 'type'> — type 是它自己的类型
print(isinstance(int, type))  # True

# 4. 模块是对象
import os
print(type(os))       # <class 'module'>
print(os.__name__)    # 'os'

核心差异

  • Java: 基本类型(int, boolean)不是对象,方法不是对象,类通过反射访问
  • Kotlin: 函数是一等公民,但基本类型仍有特化
  • Python: 一切皆对象,没有例外。intstrtype、函数、类、模块都是对象

0.6 GIL: 为什么 Python 多线程和你想的不一样

Java/Kotlin 对比

// Java: 线程直接映射到操作系统线程
// 4核CPU上,4个线程真正并行执行
public class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public static void main(String[] args) throws InterruptedException {
        Counter c = new Counter();
        Thread[] threads = new Thread[4];
        for (int i = 0; i < 4; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1_000_000; j++) {
                    c.increment();
                }
            });
            threads[i].start();
        }
        for (Thread t : threads) t.join();
        System.out.println(c.count);  // 4000000
    }
}
// Kotlin: 协程是轻量级线程,但底层仍可并行
import kotlinx.coroutines.*

fun main() = runBlocking {
    val deferred = (1..4).map {
        async(Dispatchers.Default) {
            // 在不同线程上并行执行
            heavyComputation()
        }
    }
    deferred.awaitAll()  // 等待全部完成
}

Python 实现

import threading
import multiprocessing
import time

def cpu_bound_task(n):
    """CPU密集型任务"""
    count = 0
    for i in range(n):
        count += i
    return count

# === threading: CPU密集型无效 ===
def test_threading():
    start = time.time()
    threads = []
    for _ in range(4):
        t = threading.Thread(target=lambda: cpu_bound_task(10_000_000))
        threads.append(t)
        t.start()
    for t in threads:
        t.join()
    elapsed = time.time() - start
    print(f"threading (4 threads): {elapsed:.2f}s")
    # 结果: 和单线程差不多甚至更慢!GIL阻止并行

# === multiprocessing: CPU密集型有效 ===
def test_multiprocessing():
    start = time.time()
    processes = []
    for _ in range(4):
        p = multiprocessing.Process(target=lambda: cpu_bound_task(10_000_000))
        processes.append(p)
        p.start()
    for p in processes:
        p.join()
    elapsed = time.time() - start
    print(f"multiprocessing (4 processes): {elapsed:.2f}s")
    # 结果: 接近4倍加速!

if __name__ == "__main__":
    # 单线程基准
    start = time.time()
    cpu_bound_task(10_000_000)
    print(f"single thread: {time.time() - start:.2f}s")

    test_threading()
    test_multiprocessing()

核心差异

维度Java/KotlinPython (CPython)
线程模型1线程=1OS线程1线程=1OS线程,但GIL限制同时执行
CPU并行多线程有效多线程无效,需multiprocessing
I/O并行多线程有效多线程有效(I/O时释放GIL)
内存共享线程共享堆内存进程间不共享(需IPC)
启动开销线程轻量进程重量(fork/clone)

常见陷阱

# 陷阱: 用 threading 做 CPU 密集型任务
import threading

def count(n):
    total = 0
    for i in range(n):
        total += i
    return total

# 这不会更快!GIL 让线程串行执行
threads = [threading.Thread(target=count, args=(10_000_000,)) for _ in range(4)]
for t in threads: t.start()
for t in threads: t.join()

# 正确: 用 multiprocessing 或 concurrent.futures.ProcessPoolExecutor
from concurrent.futures import ProcessPoolExecutor

with ProcessPoolExecutor(max_workers=4) as executor:
    results = list(executor.map(count, [10_000_000] * 4))

0.7 隐式契约 vs 显式契约: 访问控制

Java/Kotlin 对比

// Java: 严格的访问控制修饰符
public class User {
    private String name;           // 仅本类
    protected int age;             // 本类 + 子类 + 同包
    String email;                  // 同包(默认)
    public String getId() {        // 所有类
        return id;
    }
}
// 编译器强制执行,违反则编译错误
// Kotlin: 类似,但默认 public
class User {
    private var name: String = ""     // 仅本类
    protected var age: Int = 0        // 本类 + 子类
    internal var email: String = ""   // 同模块
    public val id: String = ""        // 所有
    var address: String = ""          // 默认 public
}

Python 实现

class User:
    def __init__(self, name, age, email):
        self.name = name          # 公开 — 约定:视为公开API
        self._age = age           # 保护 — 约定:内部使用,不要外部访问
        self.__email = email      # 私有 — 约定:强烈不建议外部访问

    def get_info(self):
        return f"{self.name}, {self._age}"

# Python 不会阻止你访问任何属性
user = User("Alice", 30, "alice@example.com")

print(user.name)       # Alice — 公开,正常访问
print(user._age)       # 30 — 可以访问,但约定不应该
# print(user.__email)  # AttributeError! 但这不是真正的私有...

# Name mangling: __email 被改名为 _User__email
print(user._User__email)  # alice@example.com — 可以访问!只是名字变了

# dir() 可以看到所有属性
print(dir(user))
# [..., '_User__email', '__class__', '__dict__', '_age', 'name', ...]

核心差异

  • Java/Kotlin: 编译器强制访问控制,违反则编译错误
  • Python: 没有真正的访问控制,靠命名约定:
    • name — 公开
    • _name — 保护(约定内部使用)
    • __name — 私有(触发 name mangling,但可绕过)
  • Python 的哲学: "We are all consenting adults here"

何时使用

  • _ 前缀: 内部实现细节,API 稳定性不保证
  • __ 前缀: 避免子类命名冲突,不是为了"隐藏"
  • 不要试图在 Python 中实现 Java 式的封装 — 接受 Python 的文化

0.8 self vs this: 显式接收者

Java/Kotlin 对比

// Java: this 是隐式的
public class Point {
    private int x;
    private int y;

    public Point(int x, int y) {
        this.x = x;    // this 指向当前对象,隐式可用
        this.y = y;
    }

    public double distanceTo(Point other) {
        int dx = this.x - other.x;  // this 可省略
        int dy = y - other.y;       // 省略 this
        return Math.sqrt(dx * dx + dy * dy);
    }
}
// Kotlin: this 可以省略
class Point(val x: Int, val y: Int) {
    fun distanceTo(other: Point): Double {
        val dx = this.x - other.x  // this 可省略
        val dy = y - other.y       // 省略 this
        return Math.sqrt((dx * dx + dy * dy).toDouble())
    }
}

Python 实现

class Point:
    def __init__(self, x, y):
        self.x = x    # self 必须显式写出!
        self.y = y

    def distance_to(self, other):
        dx = self.x - other.x  # self 必须显式写出!
        dy = self.y - other.y
        return (dx ** 2 + dy ** 2) ** 0.5

# self 不是关键字,只是约定
class Point:
    def __init__(this, x, y):   # 用 this 代替 self 也合法(但不推荐)
        this.x = x
        this.y = y

    def distance_to(this, other):
        dx = this.x - other.x
        dy = this.y - other.y
        return (dx ** 2 + dy ** 2) ** 0.5

# 调用时,self 由 Python 自动传入
p1 = Point(3, 4)
p2 = Point(0, 0)
print(p1.distance_to(p2))  # 5.0
# 等价于: Point.distance_to(p1, p2)

核心差异

  • Java/Kotlin: this 是隐式的,编译器自动处理
  • Python: 第一个参数必须显式声明(通常叫 self),调用时自动传入
  • 为什么?因为 Python 的方法本质上就是普通函数,self 让方法调用的机制变得透明

常见陷阱

# 陷阱: 忘记写 self
class Bad:
    def __init__(self, name):
        name = name  # 这只是局部变量赋值,不是实例属性!

b = Bad("Alice")
# print(b.name)  # AttributeError!

# 正确:
class Good:
    def __init__(self, name):
        self.name = name  # self.name 才是实例属性

本章总结: Java/Kotlin → Python 的 8 个思维转换

#Java/Kotlin 思维Python 思维
1编译时发现错误运行时发现错误(用测试和类型注解弥补)
2变量有类型变量无类型,值有类型
3必须声明接口鸭子类型,有方法就行
4先检查再操作 (LBYL)先操作再处理异常 (EAFP)
5基本类型和对象分离一切皆对象
6多线程=并行GIL限制,CPU密集用多进程
7private/protected 保护_/__ 约定,成年人之间的信任
8this 隐式self 显式

记住: 不要试图在 Python 中复制 Java 的模式。接受 Python 的方式,用 Python 的方式思考。 后续章节会反复回到这些核心差异。