II. Python数据类型与容器:Java工程师的快速通道

kayokoi 发布于 29 天前 47 次阅读


Java工程师以其对强类型系统和丰富的集合框架(Collections Framework)的熟练掌握而自豪。转向Python时,你会发现Python在数据类型和容器方面既有相似之处,也展现出独特的动态特性和简洁的“Pythonic”风格。本章将引导你快速理解它们,并与你的Java经验进行对照。

2.0 对象的基石:可变 (Mutable) 与 不可变 (Immutable)

这个概念对于Java开发者来说并不陌生。Java中的 String 就是典型的不可变对象,而 StringBuilder 则是可变的。理解Python中的可变性同样至关重要。

  • 不可变对象 (Immutable Objects):

    • 定义: 一旦创建,其内部状态(值)就不能改变。任何试图修改不可变对象的操作,实际上都会创建一个新的对象。
    • Python中的常见不可变类型: 数字 (int, float, bool), str (字符串), tuple (元组), frozenset
    • Java类比: 与Java的 String, Integer, Double 等包装类以及原始数据类型类似。当你执行 String s = "a"; s = s + "b"; 时,s 实际上指向了一个新的字符串对象 "ab"
    • Python示例

      x = 10
      print(f"x = {x}, 初始id(x) = {id(x)}") # id是对象在内存中的唯一标识
      x = x + 5
      print(f"x = {x}, 修改后id(x) = {id(x)}") # id已改变,x指向了新的对象
      
      s = "hello"
      print(f"s = '{s}', 初始id(s) = {id(s)}")
      s = s + " world"
      print(f"s = '{s}', 修改后id(s) = {id(s)}") # id已改变,s指向新的字符串对象
      
  • 可变对象 (Mutable Objects):

    • 定义: 创建后,其内部状态可以被修改,而对象的身份标识 (id) 保持不变。
    • Python中的常见可变类型: list (列表), dict (字典), set (集合), bytearray
    • Java类比: 类似于Java的 StringBuilder, ArrayList, HashMap 等。对这些对象的操作是“原地”修改的。
    • Python示例

      my_list = [1, 2, 3]
      print(f"my_list = {my_list}, 初始id(my_list) = {id(my_list)}")
      my_list.append(4) # 原地修改列表
      print(f"my_list = {my_list}, 修改后id(my_list) = {id(my_list)}") # id未变
      
      my_dict = {'name': 'Alice'}
      my_dict['age'] = 30 # 原地修改字典
      print(f"my_dict = {my_dict}, id(my_dict) = {id(my_dict)}") # id未变
      

Java工程师重点关注:

  1. 参数传递: Python中函数参数传递也是“传对象引用”(类似于Java传递对象的引用副本)。
    • 如果传递的是不可变对象 (如数字、字符串、元组),函数内部即使“修改”了它(实际上是创建新对象并让局部变量指向它),原对象不受影响。
    • 如果传递的是可变对象 (如列表、字典),函数内部的修改会直接反映到原始对象上,这与Java中修改传入的 ArrayList 实例行为一致。
  2. 字典的键: Python字典的键必须是不可变(且可哈希)的。这与Java HashMap 的键需要正确实现 hashCode()equals(),并且其哈希码在作为键期间不应改变的原则相通。
  3. 意外共享: 与Java一样,当多个变量引用同一个可变对象时,一个地方的修改会影响所有引用,需小心副作用。

Python基本类型速览:与Java的亲切对话

  • int (整数):

    • Python特色: Python的整数可以表示任意大小,仅受限于内存。无需像Java那样区分 byte, short, int, long,也无需显式使用 BigInteger。 Python

      very_large_number = 1234567890123456789012345678901234567890
      print(very_large_number * 2) # 自动处理大数运算
      
    • Java对比: 告别整数溢出(除非内存耗尽)和繁琐的 BigInteger 用法,这是一个巨大的便利。
  • float (浮点数):

    • 通常是双精度浮点数,类似Java的 double
  • bool (布尔型):

    • 值为 TrueFalse (首字母大写)。
    • Java对比: Java中使用 truefalse (全小写)。
  • str (字符串):

    • 不可变性: Python的 str 和Java的 String 一样,都是不可变的。
    • 定义方式:
      • 可以用单引号 '...'、双引号 "..." 或三引号 '''...''' / """..."""。单双引号等效。
      • 三引号非常适合定义多行字符串,会保留其中的换行和空格,比Java用 + 连接多行字符串或 \n 转义方便得多。 

        multi_line_str = """This is a
        multi-line string in Python.
        Very convenient!"""
        print(multi_line_str)
        
    • 无独立 char 类型: Python没有Java中的 char 类型。单个字符也被视为长度为1的字符串。
    • Java对比: 引号使用更灵活。Java中双引号定义字符串,单引号定义字符。
  • None (空值对象):

    • Python中的特殊空值对象,表示“没有值”或“空”。
    • Java对比: 类似于Java中的 null 引用。

核心容器:从Java集合到Pythonic序列与映射

Java工程师对 List, Map, Set 等集合接口及其实现类非常熟悉。Python提供了类似的内置容器,但通常语法更简洁,操作更灵活。

1. list (列表) - Python版的 ArrayList
  • Python定义与特性:

    • 创建: my_list = [1, "a", True, 3.14]empty_list = []another_list = list().
    • 特性: 有序序列,元素可变,允许重复,可存储混合数据类型
  • Java近似等价物: java.util.ArrayList.

    • Java对比:
      • 混合类型: Java的 ArrayList 通常通过泛型 ArrayList<Type> 指定统一类型。Python列表天生支持混合类型,更灵活,但也牺牲了编译时类型安全。
      • 初始化: Python的 [] 字面量语法比 new ArrayList<>() 更简洁。
  • 关键Pythonic操作 (Java工程师请注意):

    • 索引/切片 (Slicing): 这是Python序列操作的一大亮点!
      • my_list[i]: 获取元素 (同Java list.get(i)).
      • my_list[start:stop:step]: 获取子列表。比Java list.subList() 更强大,支持步长 step,负索引(从后往前数),且省略参数有默认行为。

        numbers = [0, 1, 2, 3, 4, 5, 6]
        print(numbers[1:4])    # 输出: [1, 2, 3] (类似 subList(1, 4))
        print(numbers[:3])     # 输出: [0, 1, 2] (从头到索引3之前)
        print(numbers[3:])     # 输出: [3, 4, 5, 6] (从索引3到末尾)
        print(numbers[::2])    # 输出: [0, 2, 4, 6] (步长为2,取偶数索引元素)
        print(numbers[::-1])   # 输出: [6, 5, 4, 3, 2, 1, 0] (列表反转!)
        
    • 修改:
      • append(element): 末尾添加 (同Java add(element)).
      • extend(iterable): 末尾逐个添加另一序列的元素 (同Java addAll(collection)).
      • insert(index, element): 指定位置插入 (同Java add(index, element)).
      • pop(index?): 移除并返回指定索引元素 (默认最后一个)。类似Java remove(index),但Python pop() 直接返回值。
      • remove(value): 删除第一个匹配的值。类似Java remove(Object),但Java返回布尔值。
    • 排序:
      • my_list.sort(key=None, reverse=False): 原地排序key 参数可接收一个函数(通常是 lambda 表达式),用于指定排序依据,非常灵活。 Python

        # 按字符串长度排序
        words = ["banana", "pie", "apple", "kiwi"]
        words.sort(key=len) 
        print(words) # 输出: ['pie', 'kiwi', 'apple', 'banana']
        # Java中需要自定义Comparator或使用Stream API
        
    • 列表推导式 (List Comprehensions): Pythonic精髓!用非常简洁的语法创建列表,通常可替代Java中的for循环或Stream API的 map/filter 操作。 

      # Python: 获取0-9的平方值
      squares = [x*x for x in range(10)] 
      print(squares) # 输出: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
      
      # Java (使用Stream API):
      // List<Integer> squares = IntStream.range(0, 10)
      //                               .map(x -> x * x)
      //                               .boxed()
      //                               .collect(Collectors.toList());
      
2. tuple (元组) - 不可变的固定序列
  • Python定义与特性:
    • 创建: my_tuple = (1, "a", True)empty_tuple = ()single_item_tuple = (1,) (注意单个元素元组末尾的逗号!)。甚至 val = 1, 2, 3 也会自动创建元组。
    • 特性: 有序序列,元素不可变,允许重复,可存储混合类型。
  • Java近似等价物: Java没有直接的内置元组类型。
    • Java对比: 可以视为轻量级的、不可修改的 List (如 Collections.unmodifiableList(Arrays.asList(...)))。在Java中,类似场景可能需要定义一个简单的POJO/Record类,或者使用数组。
  • Pythonic用途 (Java工程师可借鉴):
    • 函数返回多个值: Python函数可以自然地返回一个元组,调用时可以直接解包到多个变量,非常方便。 Python

      def get_coordinates():
          return 10, 20 # 自动打包成元组 (10, 20)
      
      x, y = get_coordinates() # 自动解包
      print(f"x={x}, y={y}") # 输出: x=10, y=20
      # Java中通常返回一个对象或数组
      
    • 作为字典的键: 因为元组及其元素若都不可变,则是可哈希的。
    • 格式化字符串参数保证数据不被修改的序列。
    • 操作上支持索引和切片,但没有修改自身内容的方法。
3. dict (字典) - Python版的 HashMap/LinkedHashMap
  • Python定义与特性:
    • 创建: my_dict = {'key1': 'value1', 'name': 'Alice'}empty_dict = {}another_dict = dict().
    • 特性: 存储键值对。键唯一且不可变。值任意。Python 3.7+ 版本保证插入顺序有序。可变类型。
  • Java近似等价物: java.util.HashMap (通常无序), java.util.LinkedHashMap (保持插入顺序,更接近现代Python字典)。
  • 关键Pythonic操作 (Java工程师请注意):
    • 访问/修改:
      • my_dict[key]: 获取值。若key不存在则抛出 KeyError (类似Java直接访问不存在的数组索引)。
      • my_dict.get(key, default_value=None): 安全获取。若key不存在则返回 default_value (默认是 None),不报错。这比Java map.get(key) (找不到返回 null) 后再判断 null 更优雅。
      • my_dict[key] = value: 新增或修改。
    • 迭代: Python迭代字典非常方便。 

      my_info = {'name': 'Bob', 'age': 25}
      # 遍历键 (类似Java map.keySet())
      for k in my_info.keys(): # 或者直接 for k in my_info:
          print(k)
      # 遍历值 (类似Java map.values())
      for v in my_info.values():
          print(v)
      # 遍历键值对 (Pythonic!)
      for key, value in my_info.items(): # 类似Java map.entrySet() 但更简洁
          print(f"{key}: {value}")
      
    • 字典推导式 (Dictionary Comprehensions): 快速创建字典。 

      # Python: 创建一个数字及其平方的字典
      squares_dict = {x: x*x for x in range(5)}
      print(squares_dict) # 输出: {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
      # Java中需要循环和put操作
      
4. set (集合) - Python版的 HashSet/LinkedHashSet
  • Python定义与特性:
    • 创建: my_set = {1, 2, "hello", 2} (重复元素自动忽略) 或 empty_set = set() (注意: {} 创建的是空字典!)。
    • 特性: 无序 (通常,迭代顺序不保证,但CPython 3.7+实现上可能有序),元素唯一且必须为不可变类型。集合本身可变。
  • Java近似等价物: java.util.HashSet (无序), java.util.LinkedHashSet (插入有序)。
  • 关键Pythonic操作 (Java工程师请注意):
    • 用途: 快速去重、高效的成员测试 (in 操作符)。
    • 数学集合运算: Python直接使用操作符进行集合运算,非常直观! 

      set_a = {1, 2, 3, 4}
      set_b = {3, 4, 5, 6}
      print(set_a & set_b) # 交集 (intersection): {3, 4}
      print(set_a | set_b) # 并集 (union): {1, 2, 3, 4, 5, 6}
      print(set_a - set_b) # 差集 (difference): {1, 2}
      print(set_a ^ set_b) # 对称差集 (symmetric_difference): {1, 2, 5, 6}
      # Java中需要调用 retainAll(), addAll(), removeAll() 等方法
      
    • 集合推导式 (Set Comprehensions)

      unique_chars = {char for char in "hello world"}
      print(unique_chars) # 输出: {'h', 'e', 'l', 'o', ' ', 'w', 'r', 'd'} (顺序不定)
      

str (字符串) 的Pythonic特色操作

Python字符串功能强大且易用。

  • 格式化 (Formatting):
    • f-string (Python 3.6+): name = "Python"; print(f"Hello, {name} {3 + 4}!")
      • Java对比: 强烈推荐!比Java的 String.format()+ 拼接在可读性和简洁性上通常有巨大优势。
    • str.format(): "Hello, {} {}".format(name, version) (功能强大,f-string出现前的首选)。
  • 常用方法:
    • Python的字符串方法非常丰富 (lower, upper, strip, split, join, startswith, endswith, replace, find 等)。
    • join(iterable_of_strings): 非常有用的方法,用指定字符串连接一个字符串序列。 

      words = ["Python", "is", "fun"]
      sentence = " ".join(words)
      print(sentence) # 输出: "Python is fun"
      # Java中类似功能可能用 String.join() 或 StringBuilder
      
    • 切片 (Slicing): 如同列表,字符串也支持强大的切片操作。my_string[::-1] 快速反转字符串。

Python容器的“通用”操作哲学

Python倾向于使用内置函数和操作符统一处理不同类型的容器,体现了其“鸭子类型”的哲学("如果它走路像鸭子,叫声像鸭子,那么它就是一只鸭子")。

  • len(container): 获取长度 (vs. Java .size(), .length())。
  • in / not in: 成员测试 (统一,字典默认查键)。
  • +: 合并序列 (str, list, tuple)。
  • *: 重复序列 (str, list, tuple)。
    • 注意: 对包含可变对象的列表进行 * 复制时,是浅拷贝,Java开发者对此应不陌生(类似 Collections.nCopies 返回的list如果包含同一个可变对象引用)。
  • enumerate(iterable, start=0): 同时获取索引和值,避免手动管理索引。 

    # Python
    for index, item in enumerate(["a", "b", "c"]):
        print(index, item)
    # Java (传统方式)
    // for (int i = 0; i < list.size(); i++) {
    //     System.out.println(i + " " + list.get(i));
    // }
    
  • zip(iter1, iter2, ...): 并行迭代多个序列。
  • min(), max(), sum(), sorted(iterable): 通用函数,sorted() 返回新列表,不修改原对象 (vs. list.sort() 原地排序)。

切片 (Slicing) 再强调:Java开发者的高效工具

切片是Python序列类型 (字符串、列表、元组) 的一个核心优势,它提供了一种极其简洁和强大的方式来提取子序列。

  • 语法: sequence[start:stop:step]
    • start: 起始索引(包含),默认为0。
    • stop: 结束索引(不包含),默认为序列长度。
    • step: 步长,默认为1。可以是负数,表示反向。

对于习惯了Java通过循环或 subList/substring (且要注意其行为) 等方法操作子序列的开发者来说,Python的切片会让你感到耳目一新,并能显著提高代码的简洁性和可读性。务必熟练掌握!


Java工程师上手小结 

  • Python的数据类型如 int (任意精度)、str (灵活定义) 提供了很多便利。
  • list, dict, set 在功能上对应Java的 ArrayList, HashMap, HashSet,但Python的字面量创建、推导式、以及更直接的操作符 (如集合运算) 使其更为简洁。
  • 切片推导式 是Pythonic编程的核心,能大幅减少样板代码。
  • Python的动态类型和容器的混合类型能力是双刃剑:灵活性高,但牺牲了编译时类型检查。
  • 利用Python的通用函数 (len, sorted等) 和操作符 (in) 可以写出更统一的代码。