社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
私信  •  关注

ShadowRanger

ShadowRanger 最近创建的主题
ShadowRanger 最近回复了
3 年前
回复了 ShadowRanger 创建的主题 » 分析Python的Dict insert运行时间有什么错?

事实上,重播 O(n) 成本,但考虑到它很少见,而且相对便宜(散列是预先计算和缓存的,并且您知道现有元素都不相等,所以可以假设一个完整的桶不相等),这通常不是什么大问题。

所以不,任何给定的操作实际上可能是 O(n) .但很像 list 扩张,这是 摊销 O(1) O(1) 插入;即使没有重新灰化,也可以进行给定的插入 O(n) 如果您设法确保插入的所有项都以相同的哈希值模化基础存储大小,但Python会将内置类型的哈希值最小化)。

它仍然存在的原因 O(1) 摊销是指每个元素(重新)插入的平均数量仍受一个常数乘数的约束,该乘数在大O术语中是可忽略的。你可能是在平均 O(2) O(3) 每次插入,但因为 数字 再烧一次就好了 向下 大小 (完成的工作)在重新灰烬中,它真正做的只是意味着你正在更频繁地进行插入工作,但这并没有增加 每个元素的平均值 以成本为基础 dict 生长。

3 年前
回复了 ShadowRanger 创建的主题 » Python是类在同一个类的定义中的实例

你可以参考 my_class 它的内部很好。你不能提及 我的班 当它被定义时(在任何方法之外,在类定义范围的顶层,或在函数的参数定义中),但是你可以在一个方法中引用它,直到你完成定义后才会被调用 我的班 .这里唯一的问题是基于字符串的注释有点难看,可以绕过自引用限制,这可以通过 __future__ 进口

from __future__ import annotations  # Supported as of 3.7, on by default beginning in 3.11

class my_class:
    def __add__(self, other: my_class) -> my_class:
        if isinstance(other, some_other_class):
            return other + self
        elif isinstance(other, my_class):
            return <special_adding_technique>
        return NotImplemented   # When you can't work with the other type, you're supposed
                                # to return the NotImplemented singleton, not raise
                                # NotImplementedError, where the latter is for completely
                                # unimplemented functionality, usually in an ABC

顺便说一句,Python类是 lowercase lowercase_with_underscores ; 类使用 CapWords ,所以这个类应该命名为 MyClass 遵守PEP8。

3 年前
回复了 ShadowRanger 创建的主题 » 将项目从python文件写入字典

x = len(f.readlines()) 消耗了你的整个文件,所以你接下来的循环就结束了 f 正在迭代耗尽的文件句柄,看不到剩余的行,并且立即存在。

这里不需要预先检查长度(也是你唯一使用的长度) x 试图给它编制索引,这毫无意义;你避开了 TypeError 仅仅是因为循环从未运行过),所以只需忽略这一点,然后使用 enumerate 要边走边获取数字,请执行以下操作:

def readScoresFile(fileAddr):
    dic = {}
    with open(fileAddr, "r") as f:
        for i, line in enumerate(f):  # Let enumerate manage the numbering for you
            dic["score_set{}".format(i)] = line  # If you're on 3.6+, dic[f'score_set{i}'] = line is nicer
    return dic

请注意,这实际上并不会将输入行转换为 list s的 int (你的原始代码也没有)。如果你想这样做,你可以改变:

dic[f'score_set{i}'] = line

致:

dic[f'score_set{i}'] = ast.literal_eval(line)  # Add import ast to top of file

要将该行解释为Python文字,或:

dic[f'score_set{i}'] = json.loads(line)  # Add import json to top of file

将每一行解释为JSON(速度更快,但支持的Python类型更少,一些合法的Python文本不是合法的JSON)。

一般来说,你根本不想使用 .readlines() ; 只需在文件句柄上进行迭代,就可以激活这些行,并避免与文件大小成比例的内存需求。(坦白地说,如果他们在Py3中处理掉它,我会更喜欢,因为 list(f) 如果你真的需要它,它也会得到同样的结果,而且它不会创建一个可见的方法来鼓励你经常做“错误的事情”)。

通过逐行操作,您最终可以存储所有 数据 ,但这比将解析后的数据存储在 dict 所有的字符串数据都来自 列表 .

3 年前
回复了 ShadowRanger 创建的主题 » Python对变量的重新声明在内部是如何工作的?

通过分解的媒介进行解释:

>>> dis.dis('''greeting = 'hello'
... greeting = f'y{greeting[1:len(greeting)]}'
... ''')
  1           0 LOAD_CONST               0 ('hello')
              2 STORE_NAME               0 (greeting)

  2           4 LOAD_CONST               1 ('y')
              6 LOAD_NAME                0 (greeting)
              8 LOAD_CONST               2 (1)
             10 LOAD_NAME                1 (len)
             12 LOAD_NAME                0 (greeting)
             14 CALL_FUNCTION            1
             16 BUILD_SLICE              2
             18 BINARY_SUBSCR
             20 FORMAT_VALUE             0
             22 BUILD_STRING             2
             24 STORE_NAME               0 (greeting)
             26 LOAD_CONST               3 (None)
             28 RETURN_VALUE

最左边的数字表示特定行字节码的起始位置。第1行非常简单,所以我将解释第2行。

正如您可能注意到的,您的f字符串在编译后无法保存;它变成了一堆原始操作码,将常量段的加载与格式占位符的计算混合在一起,最终导致堆栈顶部出现构成最终字符串的所有片段。当它们都在堆栈上时,它会将所有的片段放在一起,最后用 BUILD_STRING 2 (上面写着“从堆栈中取出最上面的两个值,并将它们组合成一个字符串”)。

greeting 只是一个有约束力的名字。它实际上并不包含一个值,只是对它当前绑定到的任何对象的引用。原始引用被推到堆栈上(使用 LOAD_NAME )完全是在 STORE_NAME 这会弹出堆栈顶部并重新绑定 招呼 .

简而言之,它之所以有效,是因为 招呼 被替换时不再需要;它被用来生成新字符串,然后被丢弃,取而代之的是新字符串。

3 年前
回复了 ShadowRanger 创建的主题 » 如何在向量类中添加不同的向量?python

fill_n 很明显 预定的 Vector ,但在实现时,不会执行类似的操作,因此会出现类型冲突。使其成为具有 @classmethod ,您将得到您想要的结果,而无需进行其他更改:

@classmethod                 # Ensures it passes the class even when called on an instance
def fill_n(cls, size, val):  # Added initial parameter, cls, the actually class called on
    value=[val]*size
    return cls(value)  # Changed from tuple to cls which will be Vector (or a subclass)

一般来说, 全部的 替代构造函数应该是 @类方法 s具有 @staticmethod ,您将不会对子类友好,因为未修饰的方法会忽略 self / cls 它只在类而不是实例上被调用时才起作用(这使得从现有实例的类构造新实例变得更加困难),使用实例方法,您可以 不能 在课堂上说吧。

5 年前
回复了 ShadowRanger 创建的主题 » Python动态循环范围大小

使用 range 将您锁定到特定的迭代次数; range(len(m)) 构造 使用的值 len(m) 范围 s是不变的)。你 这种行为,与您希望的方式相同:

s = '123'
i = int(s)
s = 'abc'

离开 i 123 ,而不是在 int 'abc' (允许 范围(len(m)) m 改变在道德上是对等的,同样是疯狂的)。

修改 list 当你重复它的时候,它是不受欢迎的,特别是当你 insert 列表 ,或删除元素。这太容易出错了;在这种情况下,当 j ,和 插入 i + 1 下一步),同时将其插入 j > i 将由外部循环而不是内部循环看到(在这两种情况下,值 m[j]

一般来说,更安全的解决方案是 从头开始,从现有的 列表 根据需要,以及新的元素到最后。速度也快得多 插入 列表 O(n) 对于 每个 插入 O(m * n) 哪里 是所需的插入次数, n 是输入大小),而 append O(1) (总成本 O(m + n) ).

5 年前
回复了 ShadowRanger 创建的主题 » Python动态循环范围大小

创建 新的 列表:

new = []
tag = None
for line in m:
    if line.startswith('['):
        if tag:
            new.append('Link = ' + tag)
        tag = line
    new.append(line)
5 年前
回复了 ShadowRanger 创建的主题 » 在python中用父类方法重写init

super().with_two_legs('Human') 事实上 Animal with_two_legs ,但它通过了 Human 作为 cls ,不是 动物 . super() 使代理对象仅用于帮助方法查找,它不会更改传递的内容(它仍然是相同的 self cls公司 它起源于)。在这种情况下, 超级() 什么都没用,因为 不覆盖 有两条腿 ,所以:

super().with_two_legs('Human')

意思是“呼叫 有两条腿 一等以上 在定义它的层次结构中”,以及:

cls.with_two_legs('Human')

意思是“呼叫 有两条腿 在层次结构中的第一个类上 cls公司 这就是它的定义”。只要下面没有课 动物 定义它,它们做同样的事情。

这意味着你的代码在 return cls(name_full, 2) ,因为 cls公司 仍然 ,以及您的 Human.__init__ 不带任何争论 自己 . 即使你想让它起作用(例如添加两个你忽略的可选参数),这也会导致无限循环,如 人类__ 打电话 Animal.with_two_legs ,它反过来试图构造 ,呼叫 人类__ 再一次。

您要做的并不是一个好主意;根据其性质,备用构造函数依赖于类的核心构造函数/初始值设定项。如果尝试生成依赖于备用构造函数的核心构造函数/初始值设定项,则创建了循环依赖项。

在这种情况下,我建议避免使用备用构造函数,而是显式地提供 legs 始终计数,或使用中间值 TwoLeggedAnimal 类执行备用构造函数的任务。如果你想重用代码,第二个选项就意味着你的“从名称生成完整名称的非常长的代码”可以进入 双腿动物 __init__ ;在第一个选项中,您只需编写 staticmethod 这就排除了代码的因素,所以这两种代码都可以使用 有两条腿 以及其他需要使用它的构造函数。

类层次结构类似于:

class Animal:
    def __init__(self, name, legs):
        self.legs = legs
        print(name)

class TwoLeggedAnimal(Animal)
    def __init__(self, name):
        # extremely long code to generate name_full from name
        name_full = name
        super().__init__(name_full, 2)

class Human(TwoLeggedAnimal):
    def __init__(self):
        super().__init__('Human')

相反,通用代码方法类似于:

class Animal:
    def __init__(self, name, legs):
        self.legs = legs
        print(name)

    @staticmethod
    def _make_two_legged_name(basename):
        # extremely long code to generate name_full from name
        return name_full

    @classmethod 
    def with_two_legs(cls, name):
        return cls(cls._make_two_legged_name(name), 2)

class Human(Animal):
    def __init__(self):
        super().__init__(self._make_two_legged_name('Human'), 2)

旁注:即使你处理递归,你所要做的也不会起作用,因为 __初始__ 制作 新实例,它初始化现有实例。所以即使你打电话 super()。有两条腿(“人类”) 它以某种方式工作,它生成并返回一个完全不同的实例,但对 自己 接收人 __初始__ 这就是真正被创造出来的东西。你能做的最好的事情是:

def __init__(self):
    self_template = super().with_two_legs('Human')
    # Cheaty way to copy all attributes from self_template to self, assuming no use
    # of __slots__
    vars(self).update(vars(self_template))

无法在中调用备用构造函数 __初始__ 换个衣服 自己 含蓄地。我能想到的唯一办法是,在不创建helper方法和保留备用构造函数的情况下,使用 __new__ 而不是 __初始__ (这样您就可以返回一个由另一个构造函数创建的实例),并使用备用构造函数来显式调用顶级类的 __新的__ 要避免循环调用依赖项:

class Animal:
    def __new__(cls, name, legs):  # Use __new__ instead of __init__
        self = super().__new__(cls)  # Constructs base object
        self.legs = legs
        print(name)
        return self  # Returns initialized object
    @classmethod
    def with_two_legs(cls, name):
        # extremely long code to generate name_full from name
        name_full = name
        return Animal.__new__(cls, name_full, 2)  # Explicitly call Animal's __new__ using correct subclass

class Human(Animal):
    def __new__(cls):
        return super().with_two_legs('Human')  # Return result of alternate constructor
6 年前
回复了 ShadowRanger 创建的主题 » python tempfile读写

临时文件仍然是文件;它们有一个指向文件中当前位置的“指针”。对于新编写的文件,指针位于最后一次写入的末尾,因此如果 write 没有 seek ing,你从文件的末尾读,什么也得不到。只需添加:

tmp.seek(0)

之后 你会在接下来的时间里收到你写的东西 read / readlines .

如果目标仅仅是使数据对其他按名称打开文件的程序可见,例如 nano 在注释掉的代码中,可以跳过 寻求 ,但您确实需要确保数据从缓冲区刷新到磁盘,因此在 ,您将添加:

tmp.flush()
5 年前
回复了 ShadowRanger 创建的主题 » 在python中,如何使用不可变类型对copy执行浅更新?(结构共享)

namedtuple 支持此行为 the ._replace method (尽管前面有下划线,但这是一个公共方法,它的前缀只是下划线,以避免与用户定义的字段冲突),而且它实际上是不可变的(覆盖 __setattr__ 并不是完全不变的)。

from collections import namedtuple

Foo = namedtuple('Foo', 'year album')

foo1 = Foo(1996, 'Album1')
foo2 = foo1._replace(year=1997)

如果不需要与较旧版本的Python兼容,并且希望有一种简单的方法向类中添加其他行为, the newer typing.NamedTuple 是一种更灵活的方法来命名命名元组。

5 年前
回复了 ShadowRanger 创建的主题 » 多线程python访问似乎是同步的

问题是写操作是被缓冲的,所以gil实际上并没有被释放(它只在缓冲区实际被写出来时才被释放,通常只有在缓冲区已满或文件显式地 flush ED或 close d)由于每个线程所做的工作非常少,它们永远不会运行足够长的时间来释放gil,因为超时,并且永远不会真正写入磁盘,它们永远不会因为开始阻塞系统调用而释放gil。

如果你成功了 脸红 对于每一行(或者使缓冲区足够小,以至于在完成所有的 write s),您将看到预期的交错。一种方法是改变:

with open(temp_filename, mode='a') as writer:

致:

with open(temp_filename, mode='a', buffering=1) as writer:

在哪里? buffering=1 表示缓存线。

6 年前
回复了 ShadowRanger 创建的主题 » 将egcd公式转换为python

a / b 在python 3中是“真除法”,结果是不截断浮点除法,即使两个操作数都是 int S.

修复,要么使用 // 相反(这是楼层划分):

q = a // b

use divmod 要同时执行除法和余数运算,请替换这两行:

q = a / b
r = a % b

只有:

q, r = divmod(a, b)
6 年前
回复了 ShadowRanger 创建的主题 » python在创建新对象时设置属性__

你必须明确地绕过你自己的班级 __setattr__ 打电话给 super 或根 object 第二组 . 所以你会改变:

obj.customAttr = someVal

到:

object.__setattr__(obj, 'customAttr', someVal)

不太普遍的方法(不适用于 __slots__ 是直接分配给 __dict__ 使用 dict 操作:

obj.__dict__['customAttr'] = someVal  # Equivalently: vars(obj)['customAttr'] = someVal

第一种方法是 the newly __slots__ -ed uuid.UUID 现在使用; before it became __slots__ -ed, it used the second approach . 在这两种情况下都需要这样做,因为他们使用相同的 第二组 使类型尽可能不可变的诀窍(不必麻烦地进行子类化 tuple , a la typing.NamedTuple )。

6 年前
回复了 ShadowRanger 创建的主题 » python:从文件中读取最后的'n'行[重复]

在评论者的要求下发布答案 my answer to a similar question 使用相同的技术来改变文件的最后一行,而不仅仅是得到它。

对于一个大文件, mmap 是最好的办法。改善现有的 MMAP 答:这个版本在Windows和Linux之间是可移植的,并且应该运行得更快(尽管在32位Python上,如果文件在GB范围内,如果不做一些修改,它将无法工作,请参阅 other answer for hints on handling this, and for modifying to work on Python 2 )

import io  # Gets consistent version of open for both Py2.7 and Py3.x
import itertools
import mmap

def skip_back_lines(mm, numlines, startidx):
    '''Factored out to simplify handling of n and offset'''
    for _ in itertools.repeat(None, numlines):
        startidx = mm.rfind(b'\n', 0, startidx)
        if startidx < 0:
            break
    return startidx

def tail(f, n, offset=0):
    # Reopen file in binary mode
    with io.open(f.name, 'rb') as binf, mmap.mmap(binf.fileno(), 0, access=mmap.ACCESS_READ) as mm:
        # len(mm) - 1 handles files ending w/newline by getting the prior line
        startofline = skip_back_lines(mm, offset, len(mm) - 1)
        if startofline < 0:
            return []  # Offset lines consumed whole file, nothing to return
            # If using a generator function (yield-ing, see below),
            # this should be a plain return, no empty list

        endoflines = startofline + 1  # Slice end to omit offset lines

        # Find start of lines to capture (add 1 to move from newline to beginning of following line)
        startofline = skip_back_lines(mm, n, startofline) + 1

        # Passing True to splitlines makes it return the list of lines without
        # removing the trailing newline (if any), so list mimics f.readlines()
        return mm[startofline:endoflines].splitlines(True)
        # If Windows style \r\n newlines need to be normalized to \n, and input
        # is ASCII compatible, can normalize newlines with:
        # return mm[startofline:endoflines].replace(os.linesep.encode('ascii'), b'\n').splitlines(True)

这假定尾线的数量足够小,你可以安全地将它们全部读入内存中;也可以使这成为一个生成器函数,并通过替换最后一行来手动读取一行。

        mm.seek(startofline)
        # Call mm.readline n times, or until EOF, whichever comes first
        # Python 3.2 and earlier:
        for line in itertools.islice(iter(mm.readline, b''), n):
            yield line

        # 3.3+:
        yield from itertools.islice(iter(mm.readline, b''), n)

最后,以二进制模式读取(必须使用 MMAP )所以它给了 str 行(py2)和 bytes 线条(PY3);如果需要 unicode (PY2)或 STR (py3),可以调整迭代方法来为您解码和/或修复换行符:

        lines = itertools.islice(iter(mm.readline, b''), n)
        if f.encoding:  # Decode if the passed file was opened with a specific encoding
            lines = (line.decode(f.encoding) for line in lines)
        if 'b' not in f.mode:  # Fix line breaks if passed file opened in text mode
            lines = (line.replace(os.linesep, '\n') for line in lines)
        # Python 3.2 and earlier:
        for line in lines:
            yield line
        # 3.3+:
        yield from lines

注意:我在一台无法访问python进行测试的机器上输入这些内容。请告诉我,如果我打字什么,这是足够的相似。 my other answer 那我 认为 它应该可以工作,但是调整(例如处理 offset )可能会导致微妙的错误。如果有任何错误,请在评论中告诉我。

6 年前
回复了 ShadowRanger 创建的主题 » 使用python 3中的换行符将字符串写入csv

根据您的评论,您正在接受的数据实际上不包括回车或换行,它包括表示 逃逸 对于回车和换行(所以它实际上有一个反斜杠, r 反斜杠, n 在数据中)。它已经在你想要的形式中了,所以你不需要涉及 csv 模块中,只需解释转义到正确的值,然后直接写入数据。

这是相对简单的使用 unicode-escape 编解码器(也处理ASCII转义):

import codecs  # Needed for text->text decoding

# ... retrieve data here, store to res ...

# Converts backslash followed by r to carriage return, by n to newline,
# and so on for other escapes
decoded = codecs.decode(res, 'unicode-escape')

# newline='' means don't perform line ending conversions, so you keep \r\n
# on all systems, no adding, no removing characters
# You may want to explicitly specify an encoding like UTF-8, rather than
# relying on the system default, so your code is portable across locales
with open(title, 'w', newline='') as f:
    f.write(decoded)

如果收到的字符串实际上是用引号括起来的(所以 print(repr(s)) 包括两端的引号),它们可能被解释为JSON字符串。在这种情况下,只需更换 import 创造 decoded 用:

import json


decoded = json.loads(res)
6 年前
回复了 ShadowRanger 创建的主题 » python-为什么不可变的对象不占用相同的内存

因为广义实习 全部的 解释器中不可变的对象是复杂的,并且添加了大量的代码,这些代码很少能保存任何有价值的东西。

也就是说,你的代码 使用 tuple 在cpython参考解释器上。这是一个实现细节,所以每个解释器都可以在这里做出自己的决定,我猜微丝盒并没有选择这样做(可能是为了让解释器足够简单,能够在较弱的硬件上运行)。

看起来Micropython执行缓存 int 常量,但不适用于 元组 S; 元组 s很难处理(至少在最初,cpython没有在主a s t阶段执行此操作,它只是对生成的字节代码运行一个窥视孔优化器来转换 LOAD_CONST 其次是 BUILD_TUPLE 仅使用 装入常数 结果到 装入常数 结果的 元组 )并且所涉及的额外工作可能被认为是不值得的。

6 年前
回复了 ShadowRanger 创建的主题 » python高效的字典中的并行列表排序

zip 将键放在一起,根据相关项对键函数进行排序,然后 拉链 再次恢复原始表单:

sorted_value_groups = sorted(zip(*unsorted_my_dict.values()), key=lambda _, it=iter(unsorted_my_dict['key_three']): next(it))
sorted_values = zip(*sorted_value_groups)
sorted_my_dict = {k: list(newvals) for k, newvals in zip(unsorted_my_dict, sorted_values)}

一点也不干净,我主要是为了好玩才贴的。一个班轮是:

sorted_my_dict = {k: list(newvals) for k, newvals in zip(unsorted_my_dict, zip(*sorted(zip(*unsorted_my_dict.values()), key=lambda _, it=iter(unsorted_my_dict['key_three']): next(it))))}

这是因为,当 dict 迭代顺序不保证在3.7之前,对于未修改的订单,该顺序保证可重复。 双关语 . 同样, key 函数从开始到结束都是按顺序执行的,所以通过重复迭代来提取键是安全的。我们只需分离所有值,按索引对它们进行分组,按索引键对组进行排序,按键对它们进行重新分组,然后将它们重新附加到原始键上。

输出完全按照要求进行(原始键的顺序保留在cpython 3.6或任何python 3.7或更高版本上):

sorted_my_dict = {
   'key_one': [1,6,3,2],
   'key_two': [4,1,7,9],
   'key_three': [1,2,3,4]
}
6 年前
回复了 ShadowRanger 创建的主题 » 没有in的python列表理解

让我们把它分成几行:

vocab = [           # line0
         x          # line1
         for        # line2
         x, count   # line3
         in
         c.items()
         if
         count>=2]  # line7

tuple c.items() 由一个键组成, x (数的东西)和 count (看到该键的次数)。

在每个循环中,您可以想象下一个循环 元组 被提取,然后解包,这样就不需要在索引中使用单个值 0 1 ,您可以通过名字来引用它们; anontuple[0] 变成 X , anontuple[1] 变成 计数 .

这个 count>=2 行然后过滤结果;如果 计数 小于 2 ,我们停止处理此项目,并拉下一个项目。

平原 X 最左边是要生成的项;当过滤检查通过时,我们推送相应的 X 在结果中 list 未修改的

转换为常规循环时,将如下所示(与listcomp行匹配的行):

vocab = []                  # line0
for x, count in c.items():  # lines 2-5
    if count >= 2:          # lines 6-7
        vocab.append(x)     # line1

如果解包对您来说很困惑,您可以将其想象为:

vocab = []              # line0
for item in c.items():  # lines 2, 4 and 5
    x = item[0]         # line3
    count = item[1]     # line3
    if count >= 2:      # line 6-7
        vocab.append(x) # line1
6 年前
回复了 ShadowRanger 创建的主题 » 修改python字典的值

AS Francisco notes A dict 理解将有助于创造新的 双关语 更换钥匙。您也可以修改 双关语 到位,或者使用显式循环:

for k, v in my_dicts.items():
    if v == 'true':
        my_dicts[k] = True

或通过使用 双关语 理解和更新原文 双关语 结果是:

my_dicts.update({k: True if v == 'true' else v for k, v in my_dicts.items()})

第一种方法一般来说可能更快,因为它避免了 双关语 ,并且甚至不会尝试更新密钥,除非该值是 "true" 要替换的字符串。

很明显,这是 正常地 修改不安全 双关语 当你重复它的时候,如果你担心第一个循环的话,你会得到原谅。但在这种情况下,它是好的;突变的危险 双关语 当您迭代时,它来自于添加或删除键,但是由于您设置的每个键都已经存在于 双关语 ,您只更新现有密钥的值(这是安全/合法的),而不更改密钥集本身(这会咬到您)。