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

学习日志29 python

1 __slots__

在 Python 中,__slots__ 是一个特殊的类属性,用于显式指定类的实例可以拥有的属性名称,从而限制实例动态添加其他属性,并带来一定的性能优化。

主要作用和特点:

  1. 限制属性添加
    当类定义了 __slots__ 后,实例只能拥有 __slots__ 中列出的属性,无法动态添加其他属性。例如:

    class Person:__slots__ = ('name', 'age')  # 只允许这两个属性def __init__(self, name, age):self.name = nameself.age = agep = Person("Alice", 30)
    p.gender = "female"  # 报错:AttributeError,因为 gender 不在 __slots__ 中
    
  2. 优化内存占用
    Python 中,默认情况下类的实例会用一个字典(__dict__)存储属性,字典会占用额外内存。而定义 __slots__ 后,实例会使用固定大小的数组存储属性,不再创建 __dict__,从而节省内存,尤其在创建大量实例时效果明显。

  3. 提升属性访问速度
    由于属性存储在数组中而非字典,访问 __slots__ 中定义的属性时,速度会比普通属性更快。

注意事项:

  • __slots__ 中未列出的属性,实例无法添加,但类本身仍可添加属性。
  • 如果子类未定义 __slots__,则子类实例可以拥有 __dict__,并能动态添加属性(不受父类 __slots__ 限制)。
  • 若子类也定义了 __slots__,则其允许的属性是自身 __slots__ 与父类 __slots__ 的合集。
  • __slots__ 中可以包含特殊属性,如 __dict__(允许动态添加属性)或 __weakref__(允许实例被弱引用),例如:

    python

    运行

    class Person:__slots__ = ('name', 'age', '__dict__')  # 允许动态添加其他属性
    

适用场景:

  • 需要创建大量实例(如数据处理中的对象),希望减少内存占用时。
  • 希望限制类的属性,避免误添加无关属性时。
  • 对属性访问速度有较高要求的场景。

总之,__slots__ 是一个用于优化性能和约束类属性的实用工具,但并非所有类都需要使用,通常在对内存和性能有明确要求时才考虑使用。

当子类定义了__slots__时,子类实例允许的属性是父类的__slots__加上子类的__slots__的并集。

当子类定义了 __slots__ 时,子类实例允许拥有的属性是父类 __slots__ 与子类 __slots__ 的并集

这意味着:

  • 子类实例可以使用自身 __slots__ 中定义的属性
  • 也可以使用父类 __slots__ 中定义的属性
  • 但不能使用两者都未定义的属性

示例如下:

class Parent:__slots__ = ('a', 'b')  # 父类允许的属性class Child(Parent):__slots__ = ('c', 'd')  # 子类允许的属性# 子类实例可以使用父类和子类的 slots 属性
child = Child()
child.a = 1  # 父类 slots 中的属性,允许
child.c = 3  # 子类 slots 中的属性,允许# 但不能使用两者都未定义的属性
child.e = 5  # 报错:AttributeError: 'Child' object has no attribute 'e'

这种设计既保留了父类的属性约束,又允许子类扩展自己需要的属性,同时继续维持 __slots__ 带来的内存和性能优势。

需要注意的是:如果子类未定义 __slots__,则子类实例会自动拥有 __dict__,可以动态添加任何属性(不受父类 __slots__ 限制)。

2 Python 中没有真正的「私有变量」

这种说法并不完全准确。在 Python 中,以双下划线 __ 开头的变量确实会触发「名称修饰」(name mangling)机制,但这并非严格意义上的「私有变量」,也不是必须通过定义同名变量才能在子类中访问。

具体说明:

  1. 名称修饰机制
    当类中定义 __xxx 形式的变量时,Python 会在内部将其重命名为 _类名__xxx,以避免子类意外覆盖父类的变量。例如:

    class Parent:def __init__(self):self.__value = 10  # 实际会被重命名为 _Parent__valuep = Parent()
    print(p._Parent__value)  # 输出:10(可以通过这种方式访问)
    print(p.__value)  # 报错:AttributeError(直接访问会失败)
    
  2. 并非真正的私有
    这种机制更多是一种「命名约定」,用于提示开发者该变量应被视为内部实现,而非强制禁止访问。通过 _类名__变量名 的形式,依然可以在类外部或子类中访问。

  3. 继承中的访问方式
    子类不需要定义同名的双下划线变量,而是可以通过父类的名称修饰后的变量名访问:

    class Child(Parent):def get_parent_value(self):return self._Parent__value  # 直接访问父类被修饰的变量c = Child()
    print(c.get_parent_value())  # 输出:10(无需定义同名__value)
    
  4. 与单下划线 _ 的区别

    • 单下划线 _xxx 是一种约定,表示「建议私有」,但不会触发名称修饰,子类可以直接访问。
    • 双下划线 __xxx 会触发名称修饰,目的是避免子类中的同名变量与父类冲突。

总结:

  • Python 中没有真正的「私有变量」,双下划线变量的限制是通过名称修饰实现的,而非语法级别的禁止访问。
  • 子类继承时,无需定义同名双下划线变量,只需通过 _父类名__变量名 的形式即可访问父类的双下划线变量。
  • 实际开发中,更推荐使用单下划线 _xxx 作为「私有变量」的约定,除非确实需要避免子类变量冲突,才使用双下划线。

也就是说:

关于 Python 中以下划线开头的变量(尤其是双下划线变量)的正确理解可以总结为:

  1. Python 中没有严格意义上的「私有变量」
    与其他语言(如 Java、C++)的 private 关键字不同,Python 中不存在真正禁止外部访问的私有变量,一切都是「约定优于配置」的设计思路。

  2. 单下划线 _xxx:约定性私有

    • 仅作为一种编程约定,表明该变量 / 方法是类的内部实现,建议外部不要直接访问 / 修改。
    • 不会触发任何语法级别的限制,外部仍可直接访问(如 obj._xxx)。
    • 子类可以直接继承并访问父类的 _xxx 变量。
  3. 双下划线 __xxx:名称修饰(name mangling)

    • 会触发 Python 的名称修饰机制,变量会被自动重命名为 _类名__xxx(如 __value 在 Parent 类中会变为 _Parent__value)。
    • 目的不是「禁止访问」,而是避免子类中定义同名变量时发生意外冲突
    • 外部或子类仍可通过修饰后的名称访问(如 obj._Parent__value),但不建议这么做(破坏封装性)。
  4. 继承中的规则

    • 子类不会自动继承父类的双下划线变量的「原始名称」(因为已被修饰)。
    • 子类若要访问父类的 __xxx 变量,需使用修饰后的名称(如 self._Parent__xxx),而非重新定义同名的 __xxx
    • 若子类定义了自己的 __xxx,会被修饰为 _Child__xxx,与父类的 _Parent__xxx 完全隔离,互不影响。

核心结论:

Python 中以下划线开头的变量是「封装意图的表达」,而非强制的访问限制:

  • 单下划线是「温和建议」:请把它当私有变量对待。
  • 双下划线是「强提醒」:除了类自身,其他地方访问需谨慎(可能破坏设计)。

这种设计体现了 Python 的「信任开发者」理念 —— 不强行限制,而是通过约定让代码更易读、更易维护。

3 代码的执行顺序分析

执行以下程序,输出结果为()
def outer():def inner():print('inner',end=" ")print('outer',end = " ")return inner
outer()A inner outer
B inner
C outer
D outer inner正确答案:C

这段代码的执行逻辑其实很清晰,我们一步步拆解来看:

  1. 首先定义了一个outer()函数,它内部又定义了inner()函数
  2. outer()函数中,先执行print('outer', end=" "),这会输出 "outer"
  3. 然后outer()函数返回inner函数本身(注意:这里返回的是函数对象,并没有调用它)
  4. 最后执行outer(),但没有对返回的inner函数做任何操作

关键点在于:outer()函数返回了inner函数,但我们没有调用这个返回的inner函数

所以整个程序只会执行:

  • outer()函数内部的print('outer', end=" ")

不会执行inner()函数里的打印语句,因为inner()从未被调用过(调用函数需要加括号,如inner())。

如果想让inner()也执行,需要这样写:

python

运行

def outer():def inner():print('inner', end=" ")print('outer', end=" ")return inner# 先调用outer()得到inner函数,再调用它
func = outer()
func()  # 这才会执行inner()

这时才会输出 "outer inner"

因此原程序的正确输出是 "outer",对应选项 C。

关键知识点拆解:

  1. 函数的定义与调用

    • 定义函数:def 函数名(): ... 只是声明一个函数,此时函数内部的代码不会执行
    • 调用函数:必须通过 函数名()(加括号)才能执行函数内部的代码。

    例如:

    python

    运行

    def hello():print("你好")  # 仅定义,不执行hello()  # 加括号调用,才会执行打印
    
  2. 嵌套函数

    • 在一个函数内部定义另一个函数(如 inner() 定义在 outer() 内部),称为嵌套函数。
    • 内部函数(inner)的作用域仅限于外部函数(outer)内部,外部无法直接调用 inner()
  3. 函数可以作为返回值

    • Python 中,函数是「一等公民」,可以像普通变量一样被返回。
    • 代码中 return inner 表示返回 inner 这个函数对象本身(注意没有加括号,不是调用),而不是返回 inner() 的执行结果。

原代码的执行顺序分析:

python

运行

def outer():def inner():  # 步骤1:定义inner函数(不执行内部代码)print('inner', end=" ")print('outer', end=" ")  # 步骤3:执行outer内部的打印return inner  # 步骤4:返回inner函数对象(不执行inner)outer()  # 步骤2:调用outer函数(触发outer内部代码执行)

执行步骤:

  1. 先定义 outer 函数(不执行内部代码)。
  2. 执行 outer()(调用 outer),此时进入 outer 内部:
    • 首先定义 inner 函数(仅定义,不执行 print('inner'))。
    • 执行 print('outer', end=" "),输出 outer
    • 返回 inner 函数对象(但没有对这个返回值做任何操作,比如调用)。
  3. 整个程序结束,inner 函数从未被调用,因此 print('inner') 从未执行。

如何判断执行顺序?

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!记住一个核心原则:只有加了括号的函数(如 func())才会被执行,单纯的函数名(如 func)只是一个对象,不会触发执行

在这个例子中:

  • outer() 加了括号 → 执行 outer 内部代码(包括打印 outer)。
  • inner 没加括号(只是 return inner)→ 不执行 inner 内部代码。

因此最终只输出 outer

4 混淆了列表的「加法运算」和「元素级乘法运算」的区别


在Python3中,下列程序结果为( )
res = lambda x: x+x
print(res([1, 2, 3, 4]))A [1, 2, 3, 4]
B [2, 4, 6, 8]
C [1, 2, 3, 4, 1, 2, 3, 4]
D [1, 2, 3, 4, 2, 4, 6, 8]正确答案:C
你的答案:B

你之所以会做错,是因为混淆了列表的「加法运算」和「元素级乘法运算」的区别。

在 Python 中,lambda x: x + x 对于列表来说,x + x 表示的是列表拼接将两个相同的列表连接起来),而不是对列表中的每个元素进行乘法运算。

所以当 x = [1, 2, 3, 4] 时,x + x 的结果是 [1, 2, 3, 4, 1, 2, 3, 4],对应选项 C。

如果要实现选项 B(即对列表中的每个元素乘以 2,得到 [2, 4, 6, 8]),可以使用列表推导式或 map() 函数,代码如下:

# 方法1:使用列表推导式
res = lambda x: [i * 2 for i in x]
print(res([1, 2, 3, 4]))  # 输出 [2, 4, 6, 8]# 方法2:使用map()函数
res = lambda x: list(map(lambda i: i * 2, x))
print(res([1, 2, 3, 4]))  # 输出 [2, 4, 6, 8]

这两种方法的核心都是遍历列表中的每个元素,并对单个元素执行 *2 操作,而不是对整个列表进行拼接。

5 iter()函数是什么?

在 Python 中,iter() 是一个内置函数,用于创建一个迭代器对象(iterator)。迭代器是一种可以逐个访问集合元素的对象,它遵循迭代协议(即包含 __next__() 方法,用于返回下一个元素,当没有元素时会抛出 StopIteration 异常)。

iter() 的基本用法

iter() 函数有两种常用调用方式:

  1. iter(可迭代对象)
    接收一个「可迭代对象」(如列表、字符串、字典、集合等),返回对应的迭代器。
    示例:

    python

    运行

    # 对列表创建迭代器
    my_list = [1, 2, 3]
    it = iter(my_list)  # 创建迭代器print(next(it))  # 输出 1(调用迭代器的 __next__() 方法)
    print(next(it))  # 输出 2
    print(next(it))  # 输出 3
    print(next(it))  # 抛出 StopIteration 异常(没有更多元素)
    
  2. iter(函数, 哨兵值)
    接收一个函数和一个「哨兵值」(sentinel),返回一个迭代器。每次调用迭代器的 __next__() 时,会执行该函数,直到函数返回值等于哨兵值时停止。
    示例(读取文件直到遇到空行):

    python

    运行

    with open("file.txt", "r") as f:# 每次调用 f.readline(),直到返回空字符串 "" 时停止for line in iter(f.readline, ""):print(line.strip())
    

核心作用

  • 将可迭代对象转换为迭代器,支持通过 next() 函数逐个获取元素,实现「惰性访问」(需要时才生成下一个元素,节省内存)。
  • 迭代器只能遍历一次,遍历结束后需重新创建迭代器才能再次遍历。

注意点

  • 迭代器和可迭代对象的区别:
    可迭代对象(如列表)是「可以被迭代的对象」,但本身不是迭代器;迭代器是「正在迭代的状态对象」,保存了当前遍历的位置。
  • 所有迭代器都是可迭代对象,但并非所有可迭代对象都是迭代器(如列表是可迭代对象,但不是迭代器)。

通过 iter() 函数,Python 可以统一处理各种可迭代对象的遍历逻辑,是实现 for 循环的底层基础(for 循环本质上就是通过迭代器逐个获取元素)。

6 Python 中字典的迭代默认行为

字典在迭代时,默认遍历的是它的「键(key)」,而不是值(value)

下面代码执行后的结果是(      )
#!/usr/bin/python3
dict={'a': '1', 'b': 2, 'c': '3'}
it = iter(dict)
for x in it:print (x, end=" ")
A a b c
B 1 2 3
C a': '1', 'b': 2, 'c': '3'
D a': '1' 'b': 2 'c': '3'正确答案:A
你的答案:B#我不知道这种字典键值对是迭代的什么,键还是值

具体来看这段代码:

python

运行

dict={'a': '1', 'b': 2, 'c': '3'}
it = iter(dict)  # 创建字典的迭代器,迭代对象是键
for x in it:print(x, end=" ")  # 逐个打印键

  • 当我们用 iter(dict) 创建字典的迭代器时,迭代器会指向字典的键('a''b''c')。
  • 因此 for 循环遍历的是这些键,最终输出 a b c,对应选项 A。

如果想迭代字典的值(得到选项 B 的结果),需要显式使用字典的 .values() 方法,代码如下:

python

运行

dict={'a': '1', 'b': 2, 'c': '3'}
it = iter(dict.values())  # 迭代值
for x in it:print(x, end=" ")  # 输出:1 2 3

此外,若想同时迭代键和值,可以用 .items() 方法:

python

运行

for k, v in dict.items():print(k, v, end="; ")  # 输出:a 1; b 2; c 3; 

总结:字典默认迭代键,迭代值用 .values(),迭代键值对用 .items()

http://www.dtcms.com/a/317118.html

相关文章:

  • 猫头虎AI分享:Claude Opus 新版 4.1 在 SWE-bench Verified 上准确率达到了 74.5%,在多文件代码重构方面表现突出
  • 力扣经典算法篇-44-组合总和(回溯问题)
  • Qt菜单栏与工具栏实战
  • Linux86 sheel流程控制前瞻4 判断vsftpd服务启动,如果启动,打印端口号,进程id
  • 基于FFmpeg和HLS的大文件分片传输方案
  • SRS简介及简单demo
  • 豆包新模型与PromptPilot工具深度测评:AI应用开发的全流程突破
  • 神经网络搭建对CIFAR10数据集分类
  • 生成式AI如何颠覆我们的工作和生活
  • 深度学习(pytorch版)前言:环境安装和书籍框架介绍
  • 【Canvas与三角形】黑底回环金片三角形
  • 如何解决网页视频课程进度条禁止拖动?
  • DHCP 服务器与DNS服务器
  • QML开发:QML中的基本元素
  • JAVA高级编程第六章
  • 深入解析Java NIO在高并发场景下的性能优化实践指南
  • Kubernetes服务发现、名称解析和工作负载
  • 如何根据枚举值,快速方便显示对应枚举含义 js
  • 大疆无人机连接Jetson主板
  • hive专题面试总结2
  • 疯狂星期四文案网第31天运营日记
  • GitHub Spark公共预览版上线
  • Sourcetree GIT 可视化工具安装全攻略
  • Maven补充
  • 【Linux内核系列】:信号(上)
  • HTML应用指南:利用GET请求获取全国OPPO官方授权体验店门店位置信息
  • nflsoi 8.6 题解
  • 【JavaEE】(8) 网络原理 HTTP/HTTPS
  • 使用MatterJs物理2D引擎实现重力和鼠标交互等功能,有点击事件(盒子堆叠效果)
  • GaussDB 数据库架构师修炼(六)-3 集群工具管理-主备倒换