当前位置: 首页 > news >正文

学习日志21 python

1 Python中可以作为字典键的数据类型

Python中可以作为字典键的数据类型包括:
- 数字(整数、浮点数)
- 字符串
- 元组
- 布尔值
而列表、字典、集合等可变类型都不能作为字典的键。

在 Python 中,字典的键必须是可哈希(hashable)的数据类型。

具体来说,以下类型可以作为字典的键:

1. 不可变数据类型(可哈希)

  • 整数(int)

    python

    运行

    my_dict = {1: "one", 2: "two"}
    
  • 浮点数(float)

    python

    运行

    my_dict = {3.14: "pi", 2.71: "e"}
    
  • 字符串(str)

    python

    运行

    my_dict = {"name": "Alice", "age": 30}
    
  • 布尔值(bool)

    python

    运行

    my_dict = {True: "yes", False: "no"}
    
  • 元组(tuple)
    但元组内的所有元素也必须是可哈希的:

    python

    运行

    my_dict = {(1, 2): "coordinate", ("a",): "letter"}
    
  • None

    python

    运行

    my_dict = {None: "default"}
    

2. 自定义类的实例(默认可哈希)

自定义类的对象默认是可哈希的,因为它们的哈希值基于内存地址:

python

运行

class Person:passp = Person()
my_dict = {p: "person object"}

3. 不可变集合(frozenset)

python

运行

my_dict = {frozenset([1, 2]): "frozen set"}

不可作为键的类型(不可哈希)

  • 列表(list)
    因为列表是可变的,无法保证哈希值的唯一性。
    ❌ my_dict = {[1, 2]: "invalid"} # 报错

  • 字典(dict)
    同理,字典也是可变的。
    ❌ my_dict = {{1: "a"}: "invalid"} # 报错

  • 集合(set)
    集合是可变的,不可哈希。
    ❌ my_dict = {{1, 2}: "invalid"} # 报错

判断对象是否可哈希

使用 hash() 函数测试:

python

运行

hash("hello")      # 正常返回哈希值 → 可哈希
hash([1, 2])       # 报错 → 不可哈希

为什么要求键必须可哈希?

字典通过哈希表实现,键的哈希值用于快速定位存储位置。如果键是可变的,其哈希值可能改变,导致无法正确找到对应的值。因此,Python 要求键必须是不可变类型,以保证哈希值的稳定性。

2 可哈希是什么?

在 Python 中,可哈希(hashable)是一个对象的属性,用于判断该对象是否可以作为字典的键或集合(set/frozenset)的元素。可哈希性的核心在于对象的哈希值在其生命周期内不可变

1. 什么是哈希值?

  • 哈希值是一个由对象通过 hash() 函数计算得到的整数。

    python

    运行

    print(hash("hello"))  # 输出: 4276290592126914302
    
  • Python 内置的哈希函数会根据对象的内容生成哈希值(对于不可变类型),或根据对象的内存地址生成哈希值(对于自定义对象)。

2. 可哈希对象的条件

一个对象要被视为可哈希,必须满足以下条件:

  1. 不可变类型(如 intstrtuple 等)。
  2. 实现了 __hash__() 方法,返回一个固定的哈希值。
  3. 实现了 __eq__() 方法,用于比较对象是否相等。

3. 常见可哈希类型

类型可哈希性原因
intfloatbool✅ 可哈希数值不可变。
str✅ 可哈希字符串不可变。
tuple✅ 可哈希元组本身不可变,但要求所有元素也必须是可哈希的。
frozenset✅ 可哈希不可变集合。
自定义对象✅ 可哈希默认根据对象的内存地址生成哈希值(除非重写 __hash__)。

4. 常见不可哈希类型

类型不可哈希原因
list列表可变,可通过 append() 等方法修改。
dict字典的键必须是可哈希的,但字典本身可变。
set集合可变,可通过 add() 等方法修改。

5. 为什么字典键和集合元素需要可哈希?

  • 字典和集合的工作原理
    它们使用哈希表来存储数据,通过哈希值快速定位元素。如果键或元素的哈希值可变,会导致数据无法正确定位。
  • 示例

    python

    运行

    my_dict = {[1, 2]: "value"}  # 报错!列表不可哈希
    

6. 自定义可哈希类

如果需要让自定义类的对象可哈希,需要重写 __hash__() 和 __eq__() 方法:

python

运行

class Point:def __init__(self, x, y):self.x = xself.y = ydef __hash__(self):return hash((self.x, self.y))  # 使用元组的哈希值def __eq__(self, other):return self.x == other.x and self.y == other.yp1 = Point(1, 2)
p2 = Point(1, 2)
my_set = {p1, p2}  # 由于 __eq__ 判断相等,集合中只会存储一个元素

总结

  • 可哈希 ≈ 不可变:大多数不可变类型都是可哈希的。
  • 用途:可哈希对象可以作为字典的键或集合的元素,用于高效查找和去重。
  • 判断方法:使用 hash() 函数测试,若不报错则可哈希:

    python

    运行

    hash("hello")  # 正常
    hash([1, 2])   # TypeError: unhashable type 'list'

3 file.seek(0)是什么意思?

file.seek(0) 是 Python 文件操作中的一个方法,用于移动文件指针到指定位置。具体来说:

  1. seek() 方法
    用于改变文件当前的位置(即文件指针)。文件指针决定了后续读写操作的起始位置。

  2. 参数 0
    表示将文件指针移动到文件的开头。数值参数代表相对于某个参考点的字节偏移量,0 即文件起始处。

  3. 常见用途

    • 重读文件:当你已经读取完文件内容后,使用 seek(0) 可以回到开头重新读取。
    • **覆盖写入**:在写入文件前调用 seek(0)可以从文件开头开始覆盖原有内容

示例代码

python

运行

# 读取文件
with open("example.txt", "r") as file:first_line = file.readline()  # 读取第一行print(f"第一行: {first_line}")file.seek(0)  # 回到文件开头all_content = file.read()     # 读取整个文件print(f"全文内容: {all_content}")# 写入文件(覆盖模式)
with open("example.txt", "w") as file:file.write("新内容")file.seek(0)                 # 回到开头file.write("覆盖原有内容")    # 从开头覆盖写入

参数说明

  • offset:偏移量(如 0 表示开头)。
  • whence(可选):参考点,默认值为 0(文件开头):
    • 0:从文件开头计算偏移量。
    • 1:从当前位置计算偏移量。
    • 2:从文件末尾计算偏移量(此时 offset 通常为负数)。

例如:

  • file.seek(0, 2):移动到文件末尾。
  • file.seek(-10, 2):从文件末尾向前移动 10 个字节。

题目

在Python中,以下代码片段的功能是?
with open("data.txt", "w") as file:file.write("Hello\nWorld\n")file.seek(0)file.write("Hi")A 创建文件data.txt,最终内容为Hillo\nWorld\n
B 创建文件data.txt,最终内容为Hi
C 创建文件data.txt,最终内容为Hi\nWorld\n
D 抛出io.UnsupportedOperation异常正确答案:A
你的答案:B

代码执行步骤解析

python

运行

with open("data.txt", "w") as file:file.write("Hello\nWorld\n")  # 步骤1:写入内容,指针移到末尾file.seek(0)                 # 步骤2:指针回到文件开头file.write("Hi")              # 步骤3:从开头覆盖写入"Hi"
  1. 第一次写入

    • 写入内容:Hello\nWorld\n(共 12 个字符)。
    • 文件指针位置:移动到末尾(位置 12)。
  2. seek(0)操作

    • 文件指针被移动到文件开头(位置 0)。
    • 注意:此时文件内容并未被清空,只是指针位置改变。
  3. 第二次写入

    • 从位置 0 开始写入Hi,覆盖原内容的前两个字符。
    • 原内容:Hello\nWorld\n
    • 覆盖后:Hillo\nWorld\nHe被替换为Hi)。

为什么答案是 A 而非 B?

选项 B(最终内容为Hi)的错误原因
当使用"w"模式打开文件时,文件会在打开瞬间被清空。但后续的写入操作是按指针位置进行的。

  • 若第二次写入后立即关闭文件,内容为Hillo\nWorld\n(覆盖前两个字符)。
  • 若要完全覆盖为Hi,需在seek(0)后追加truncate()操作清空文件:

    python

    运行

    file.seek(0)
    file.truncate()  # 清空文件
    file.write("Hi")  # 写入新内容
    

常见误区

  1. 认为seek(0)会清空文件
    seek(0)仅移动指针,不改变文件内容。只有truncate()或重新打开文件会清空内容。

  2. 忽略覆盖写入的特性
    写入操作从当前指针位置开始,直接覆盖原有字符,而非插入或替换整行。

验证方法

运行以下代码可验证结果:

python

运行

with open("test.txt", "w") as f:f.write("Hello\nWorld\n")f.seek(0)f.write("Hi")with open("test.txt", "r") as f:print(f.read())  # 输出:Hillo\nWorld\n

结论

正确答案为 A,因为第二次写入从文件开头覆盖了前两个字符,最终内容为:

plaintext

Hillo
World

4 关于python的作用域

关于python的作用域,以下描述正确的是()A 在函数内部创建的变量属于该函数的作用域,并且只能在该函数内部使用B python的作用域规则的查找顺序LEGB,Builtion,Local(函数),GLOBALC global关键字可以在函数内部创建一个全局变量D 函数外部创建的变量的作用,函数内部可以直接使用正确答案:ACD

正确选项解析

A. 在函数内部创建的变量属于该函数的作用域,并且只能在该函数内部使用

正确。函数内部定义的变量(局部变量)仅在函数内部可见。

python

运行

def test():x = 10  # 局部变量print(x)  # 正确:函数内部可访问test()
print(x)  # 错误:NameError,外部无法访问
C. global 关键字可以在函数内部创建一个全局变量

正确global 声明可将函数内的变量绑定到全局作用域。

python

运行

def create_global():global y  # 声明y为全局变量y = 20    # 创建全局变量ycreate_global()
print(y)  # 输出:20(全局作用域已存在y)
D. 函数外部创建的变量的作用,函数内部可以直接使用

正确。函数内部可直接读取全局变量,但不能直接修改(需用 global 声明)。

python

运行

z = 30def read_global():print(z)  # 正确:读取全局变量read_global()  # 输出:30

错误选项解析

B. Python 的作用域规则的查找顺序 LEGB,Builtion,Local(函数),GLOBAL

错误。正确的 LEGB 顺序为:

  1. Local(局部作用域,函数内)
  2. Enclosing(闭包作用域,嵌套函数的外层)
  3. Global(全局作用域)
  4. Built-in(内置作用域)

选项 B 混淆了顺序,且拼写错误(Builtion 应为 Built-in)。

总结

正确答案:A、C、D

  • A:局部变量仅在函数内可见。
  • Cglobal 可在函数内创建 / 修改全局变量。
  • D:函数可直接读取全局变量。
  • B:LEGB 顺序错误。

5 python好像大部分都是左闭右开吗?(指的是range)

在 Python 中,并非所有涉及范围的操作都是左闭右开,不是所有的都是左闭右开!但左闭右开是一种非常常见的设计模式,尤其在序列相关操作中。以下是具体分析:

1. 左闭右开的典型场景

这些操作遵循 “包含起始值,不包含结束值” 的规则,是 Python 中最常见的情况:

  • range() 函数
    如 range(1, 5) 生成 [1, 2, 3, 4],包含 1 但不包含 5,这是左闭右开的核心体现。

  • 序列切片(列表、字符串、元组等)
    例如:

    python

    运行

    s = "abcdef"
    print(s[1:4])  # 输出 "bcd"(包含索引1,不包含索引4)
    

    切片 [start:end] 始终包含 start 对应的元素,不包含 end 对应的元素。

  • 某些库的区间操作
    如 pandas 中对 DataFrame 的切片、numpy 数组的索引等,大多延续了 Python 原生的左闭右开规则。

2. 不遵循左闭右开的场景

以下情况不适用左闭右开,需要特别注意:

  • 字符串方法 str.find() 的返回值
    该方法返回子串首次出现的起始索引,若查找范围为 str.find(sub, start, end),则 end 是 “包含” 的(即查找范围是 [start, end])。例如:

    python

    运行

    s = "abcabc"
    print(s.find("ab", 0, 3))  # 输出 0(在索引0-3范围内查找,包含索引3)
    
  • 循环中的比较条件
    手动编写的循环条件可能灵活定义范围,例如:

    python

    运行

    for i in [1, 2, 3, 4, 5]:if 2 <= i <= 4:  # 这里是闭区间 [2,4]print(i)  # 输出 2,3,4
    
  • 数学计算相关函数
    如 numpy.linspace(start, stop, num) 生成的序列 包含 stop(闭区间),例如:

    python

    运行

    import numpy as np
    print(np.linspace(1, 5, 3))  # 输出 [1. 3. 5.](包含1和5)
    

3. 为什么左闭右开更常见?

Python 中大量操作采用左闭右开,主要是为了:

  • 计算方便:区间长度直接为 end - start(如 range(1,5) 长度为 4)。
  • 避免重叠:多个连续区间可无缝衔接(如 [0:2] 和 [2:4] 刚好覆盖 [0:4])。
  • 一致性:切片、range 等操作规则统一,降低学习成本。

总结

Python 中 左闭右开是主流设计(如 range、切片),但并非所有场景都遵循,需根据具体操作或函数的定义判断。记住核心场景的规则,同时对特殊情况(如 str.find()numpy.linspace)保持留意,即可避免混淆。

6 Python装饰器是不是类似于java里面的方法重写?

Python 装饰器和 Java 方法重写在功能上有部分相似性(如增强原有方法),但本质上是两种不同的机制,应用场景和实现方式差异较大。以下是详细对比:

1. 核心概念对比

Python 装饰器
  • 本质:是一种高阶函数,接收一个函数作为参数,并返回一个新的函数(通常包装原函数)。
  • 目的:在不修改原函数代码的前提下,为其添加额外功能(如日志、权限校验、性能统计等)。
  • 语法:使用 @decorator 语法糖简化调用。

示例

python

运行

def log_decorator(func):def wrapper(*args, **kwargs):print(f"调用函数: {func.__name__}")result = func(*args, **kwargs)print(f"函数返回: {result}")return resultreturn wrapper@log_decorator
def add(a, b):return a + badd(1, 2)  # 输出:调用函数: add → 函数返回: 3
Java 方法重写(Override)
  • 本质:是面向对象编程的特性,子类通过重写父类的方法来改变其行为。
  • 目的:实现多态,允许子类根据自身需求定制父类方法的实现。
  • 语法:使用 @Override 注解(可选,但推荐)。

示例

java

运行

class Animal {public void speak() {System.out.println("动物发出声音");}
}class Dog extends Animal {@Overridepublic void speak() {System.out.println("汪汪汪");}
}Animal dog = new Dog();
dog.speak();  // 输出:汪汪汪

2. 相似点

  • 增强功能
    装饰器和方法重写都可以改变原有方法的行为,但装饰器是通过包装实现,而方法重写是通过继承和覆盖实现。
  • 不修改原代码
    两者都不需要修改原方法的源代码(符合开闭原则)。

3. 关键区别

特性Python 装饰器Java 方法重写
实现机制函数式编程(高阶函数)面向对象编程(继承与多态)
作用对象可以装饰任何函数或类方法只能重写父类的虚方法(非 final、非 private)
调用方式静态绑定(编译时确定)动态绑定(运行时根据对象类型确定)
灵活性可动态组合多个装饰器,随时启用 / 禁用必须通过继承关系实现,结构固定
典型场景日志、缓存、权限校验、事务管理等横切关注点定制子类行为、实现多态

4. 装饰器在 Java 中的等价实现

Java 中没有直接等同于 Python 装饰器的语法,但可以通过以下方式实现类似功能:

(1)包装器模式(Wrapper Pattern)

通过组合而非继承来增强对象功能,类似 Python 装饰器的思路。

示例

java

运行

interface Component {void operation();
}class ConcreteComponent implements Component {@Overridepublic void operation() {System.out.println("执行基本操作");}
}class LoggingDecorator implements Component {private Component component;public LoggingDecorator(Component component) {this.component = component;}@Overridepublic void operation() {System.out.println("开始操作前记录日志");component.operation();System.out.println("操作完成后记录日志");}
}// 使用
Component decorated = new LoggingDecorator(new ConcreteComponent());
decorated.operation();
(2)AOP(面向切面编程)

Java 的 Spring 框架通过 AOP 实现横切关注点(如日志、事务),功能类似 Python 装饰器。

示例

java

运行

@Aspect
@Component
public class LoggingAspect {@Before("execution(* com.example.service.*.*(..))")public void beforeAdvice(JoinPoint joinPoint) {System.out.println("方法调用前: " + joinPoint.getSignature().getName());}
}

总结

  • Python 装饰器更灵活,可动态增强任意函数 / 方法,适用于横切关注点。
  • Java 方法重写是面向对象的核心特性,用于子类定制父类行为,实现多态。
  • 若 Java 需要类似装饰器的功能,可使用包装器模式或 AOP。

两者虽然都能增强功能,但设计思想和应用场景不同,选择时需根据语言特性和需求来决定。

7 嵌套循环和分析

题目

res = 0
for i in range(1, 4):for j in range(1, 4):for k in range(1, 4):if i != j and i != k and j != k:res += 1
print(res)在Python3中,三层循环后res的结果为:A 6
B 12
C 3
D 5正确答案:A
你的答案:C

正确答案是 A. 6。让我详细解释为什么:

代码分析

python

运行

res = 0
for i in range(1, 4):      # i取值1,2,3for j in range(1, 4):  # j取值1,2,3for k in range(1, 4):  # k取值1,2,3if i != j and i != k and j != k:  # 三个数互不相等res += 1  # 符合条件则计数+1
print(res)

这段代码的核心逻辑是:找出所有由 1、2、3 组成的互不重复的三元组,并统计其数量。

关键步骤解析

  1. 三层循环的所有可能组合
    每个变量 ijk 都能取 1, 2, 3,总共有 3×3×3 = 27 种组合。

  2. 筛选条件
    要求 i ≠ j ≠ k,即三个数必须互不相同。这实际上是数学中的排列问题:从 3 个不同元素中选 3 个进行排列,排列数为 3! = 3×2×1 = 6

  3. 符合条件的组合
    具体列出所有符合条件的三元组:

    plaintext

    (1, 2, 3)
    (1, 3, 2)
    (2, 1, 3)
    (2, 3, 1)
    (3, 1, 2)
    (3, 2, 1)
    

    共 6 种

常见误区

  • 选项 C(3)的错误原因
    可能误将循环次数(每个变量的取值范围是 3)直接作为结果,但题目要求的是符合条件的组合数量。

  • 选项 B(12)的错误原因
    可能忽略了条件 i ≠ j ≠ k,直接计算 3×3×3 = 27,或错误地应用了排列组合公式(如 3×4 = 12)。

验证方法

运行代码或手动枚举所有符合条件的组合,都可确认结果为 6

答案:A. 6

Python中列表推导式的执行顺序

在 Python 中,列表推导式(List Comprehension)的执行顺序与嵌套循环的逻辑一致,但语法上是从左到右书写的。理解其执行顺序对于正确编写和解读复杂的列表推导式至关重要。

1. 基本结构与执行顺序

列表推导式的通用语法为:

python

运行

[表达式 for 变量1 in 可迭代对象1 for 变量2 in 可迭代对象2 ... if 条件]

执行顺序

  1. 最外层循环:从左到右第一个 for 语句。
  2. 内层循环:后续的 for 语句依次嵌套。
  3. 条件判断if 语句在所有循环的最内层执行,过滤不符合条件的元素。
  4. 表达式求值:满足条件时,计算表达式并添加到结果列表中。

2. 示例分析

示例 1:单层循环

python

运行

result = [x**2 for x in range(5)]
# 等价于:
result = []
for x in range(5):result.append(x**2)

执行顺序:遍历 range(5),对每个 x 计算 x**2,结果为 [0, 1, 4, 9, 16]

示例 2:双层循环

python

运行

result = [x + y for x in [1, 2] for y in [10, 20]]
# 等价于:
result = []
for x in [1, 2]:for y in [10, 20]:result.append(x + y)

执行顺序

  1. 外层循环 x 取 1,内层循环 y 依次取 10, 20,生成 [11, 21]
  2. 外层循环 x 取 2,内层循环 y 依次取 10, 20,生成 [12, 22]
    最终结果:[11, 21, 12, 22]
示例 3:带条件判断的双层循环

python

运行

result = [x + y for x in [1, 2] for y in [10, 20] if x != y]
# 等价于:
result = []
for x in [1, 2]:for y in [10, 20]:if x != y:  # 实际上所有x和y都不相等result.append(x + y)

执行顺序:循环嵌套与示例 2 相同,但每次循环都会检查 if 条件,仅当条件为真时才执行表达式。

3. 多层循环的执行顺序

对于更复杂的多层循环,执行顺序依然是从左到右的嵌套关系。例如:

python

运行

result = [f"{i},{j},{k}" for i in "ab" for j in "xy" if j != 'x' for k in "12" if k == '2']

等价嵌套循环

python

运行

result = []
for i in "ab":for j in "xy":if j != 'x':  # 仅保留j='y'for k in "12":if k == '2':  # 仅保留k='2'result.append(f"{i},{j},{k}")

执行结果

  • i='a'j='y'k='2' → "a,y,2"
  • i='b'j='y'k='2' → "b,y,2"
    最终结果:["a,y,2", "b,y,2"]

4. 关键点总结

  1. 嵌套顺序:列表推导式中的 for 语句从左到右对应嵌套循环的外层到内层
  2. 条件过滤if 语句在其所在循环层的最内层执行,过滤不符合条件的元素。
  3. 表达式计算:仅当所有 for 循环和 if 条件都满足时,才计算表达式并添加到结果列表。

理解这种执行顺序有助于正确编写复杂的列表推导式,避免逻辑错误。

http://www.lryc.cn/news/604172.html

相关文章:

  • 集成电路学习:什么是WDT看门狗定时器
  • 简历美容院:如何把“打杂经历“包装成“核心项目“?
  • 系统优化与性能调教
  • USB Type-C PD协议一文通
  • QFutureWatcher 收不到 finished 信号-QFutureWatcher 与对象生命周期
  • 02-Breakout靶机攻略
  • linux命令ps的实际应用
  • ubuntu18.04制作raid0
  • Springboot+vue智能家居商城的设计与实现
  • python使用ffmpeg录制rtmp/m3u8推流视频并按ctrl+c实现优雅退出
  • Apache Ignite 的分布式队列(IgniteQueue)和分布式集合(IgniteSet)的介绍
  • windows下Docker安装路径、存储路径修改
  • Element Plus常见基础组件(一)
  • 网络协议——MPLS(多协议标签转发)
  • Day23-二叉树的层序遍历(广度优先搜素)
  • 基于dcmtk的dicom工具 第九章 以json文件或sqlite为数据源的worklist服务(附工程源码)
  • Mqttnet的MqttClientTlsOptions.CertificateValidationHandler详解
  • SQL 怎么学?
  • SQLAlchemy 全方位指南:从入门到精通
  • Linux初学者在CentOS 7虚拟机中rpm、yum、dnf的操作练习
  • PCIE4.0/5.0/DDR4/DDR5使用以及布局布线规则-集萃
  • 14、distance_object_model_3d算子
  • 粒子群优化算法(Particle Swarm Optimization, PSO) 求解二维 Rastrigin 函数最小值问题
  • 三相四桥臂SVPWM控制及电机模型
  • Excel制作滑珠图、哑铃图
  • CSRF漏洞原理及利用
  • 子数组和 问题汇总
  • Mysql缓冲池和LRU
  • Accessibility Insights for Windows 使用教程
  • Adv. Sci. 前沿:非零高斯曲率3D结构可逆转换!液晶弹性体多级形变新策略