提问人:Alexander 提问时间:3/27/2015 最后编辑:MazdakAlexander 更新时间:9/26/2023 访问量:65321
Python 中是否存在可变命名元组?
Existence of mutable named tuple in Python?
问:
任何人都可以修改 namedtuple 或提供替代类以使其适用于可变对象吗?
主要是为了可读性,我想要类似于 namedtuple 的东西来做到这一点:
from Camelot import namedgroup
Point = namedgroup('Point', ['x', 'y'])
p = Point(0, 0)
p.x = 10
>>> p
Point(x=10, y=0)
>>> p.x *= 10
Point(x=100, y=0)
必须可以腌制所得物体。根据命名元组的特征,在构造对象时,表示时输出的顺序必须与参数列表的顺序匹配。
答:
这个问题的答案似乎是否定的。
下面非常接近,但从技术上讲是不可变的。这将创建一个具有更新的 x 值的新实例:namedtuple()
Point = namedtuple('Point', ['x', 'y'])
p = Point(0, 0)
p = p._replace(x=10)
另一方面,您可以使用以下方法创建一个简单的类,该类应该适用于频繁更新类实例属性:__slots__
class Point:
__slots__ = ['x', 'y']
def __init__(self, x, y):
self.x = x
self.y = y
为了补充这个答案,我认为这里很有用,因为当你创建大量类实例时,它是内存效率高的。唯一的缺点是无法创建新的类属性。__slots__
这里有一个相关的线程来说明内存效率 - 字典与对象 - 哪个更有效,为什么?
该线程的答案中引用的内容是一个非常简洁的解释,为什么内存效率更高 - Python 插槽__slots__
根据定义,元组是不可变的。
但是,您可以创建一个字典子类,您可以在其中使用点表示法访问属性;
In [1]: %cpaste
Pasting code; enter '--' alone on the line to stop or use Ctrl-D.
:class AttrDict(dict):
:
: def __getattr__(self, name):
: return self[name]
:
: def __setattr__(self, name, value):
: self[name] = value
:--
In [2]: test = AttrDict()
In [3]: test.a = 1
In [4]: test.b = True
In [5]: test
Out[5]: {'a': 1, 'b': True}
如果您想要与 namedtuples 类似的行为但可变,请尝试 namedlist
请注意,为了可变,它不能是元组。
让我们通过动态类型创建来实现这一点:
import copy
def namedgroup(typename, fieldnames):
def init(self, **kwargs):
attrs = {k: None for k in self._attrs_}
for k in kwargs:
if k in self._attrs_:
attrs[k] = kwargs[k]
else:
raise AttributeError('Invalid Field')
self.__dict__.update(attrs)
def getattribute(self, attr):
if attr.startswith("_") or attr in self._attrs_:
return object.__getattribute__(self, attr)
else:
raise AttributeError('Invalid Field')
def setattr(self, attr, value):
if attr in self._attrs_:
object.__setattr__(self, attr, value)
else:
raise AttributeError('Invalid Field')
def rep(self):
d = ["{}={}".format(v,self.__dict__[v]) for v in self._attrs_]
return self._typename_ + '(' + ', '.join(d) + ')'
def iterate(self):
for x in self._attrs_:
yield self.__dict__[x]
raise StopIteration()
def setitem(self, *args, **kwargs):
return self.__dict__.__setitem__(*args, **kwargs)
def getitem(self, *args, **kwargs):
return self.__dict__.__getitem__(*args, **kwargs)
attrs = {"__init__": init,
"__setattr__": setattr,
"__getattribute__": getattribute,
"_attrs_": copy.deepcopy(fieldnames),
"_typename_": str(typename),
"__str__": rep,
"__repr__": rep,
"__len__": lambda self: len(fieldnames),
"__iter__": iterate,
"__setitem__": setitem,
"__getitem__": getitem,
}
return type(typename, (object,), attrs)
这将检查属性以查看它们是否有效,然后再允许操作继续。
那么这是可腌制的吗?是,当(且仅当)您执行以下操作:
>>> import pickle
>>> Point = namedgroup("Point", ["x", "y"])
>>> p = Point(x=100, y=200)
>>> p2 = pickle.loads(pickle.dumps(p))
>>> p2.x
100
>>> p2.y
200
>>> id(p) != id(p2)
True
该定义必须位于您的命名空间中,并且必须存在足够长的时间才能让 pickle 找到它。因此,如果您将其定义为在包中,它应该可以工作。
Point = namedgroup("Point", ["x", "y"])
如果您执行以下操作,或者将定义设为临时定义(例如,在函数结束时超出范围),则 Pickle 将失败:
some_point = namedgroup("Point", ["x", "y"])
是的,它确实保留了类型创建中列出的字段的顺序。
有一个可变的替代方案 – recordclass。
它可以从 PyPI 安装:collections.namedtuple
pip3 install recordclass
它具有相同的 API 和内存占用,并且支持分配(它也应该更快)。例如:namedtuple
from recordclass import recordclass
Point = recordclass('Point', 'x y')
>>> p = Point(1, 2)
>>> p
Point(x=1, y=2)
>>> print(p.x, p.y)
1 2
>>> p.x += 2; p.y += 3; print(p)
Point(x=3, y=5)
recordclass
(从 0.5 开始)支持类型提示:
from recordclass import recordclass, RecordClass
class Point(RecordClass):
x: int
y: int
>>> Point.__annotations__
{'x':int, 'y':int}
>>> p = Point(1, 2)
>>> p
Point(x=1, y=2)
>>> print(p.x, p.y)
1 2
>>> p.x += 2; p.y += 3; print(p)
Point(x=3, y=5)
有一个更完整的示例(它还包括性能比较)。
Recordclass
库现在提供了另一个变体 -- 工厂函数。它支持类似数据类的 API(有模块级函数 、 而不是 、 、 方法)。recordclass.make_dataclass
update
make
replace
self._update
self._replace
self._asdict
cls._make
from recordclass import dataobject, make_dataclass
Point = make_dataclass('Point', [('x', int), ('y',int)])
Point = make_dataclass('Point', {'x':int, 'y':int})
class Point(dataobject):
x: int
y: int
>>> p = Point(1, 2)
>>> p
Point(x=1, y=2)
>>> p.x = 10; p.y += 3; print(p)
Point(x=10, y=5)
recordclass
并且可以生成类,其实例占用的内存少于基于 -的实例。这对于具有属性值的实例非常重要,因为这些实例不打算具有引用周期。如果您需要创建数百万个实例,它可能有助于减少内存使用量。下面是一个说明性示例。make_dataclass
__slots__
以下是 Python 3 的一个很好的解决方案:使用和抽象基类的最小类;不做花哨的错误检测之类的,但它可以工作,并且表现得很像一个可变的元组(类型检查除外)。__slots__
Sequence
from collections import Sequence
class NamedMutableSequence(Sequence):
__slots__ = ()
def __init__(self, *a, **kw):
slots = self.__slots__
for k in slots:
setattr(self, k, kw.get(k))
if a:
for k, v in zip(slots, a):
setattr(self, k, v)
def __str__(self):
clsname = self.__class__.__name__
values = ', '.join('%s=%r' % (k, getattr(self, k))
for k in self.__slots__)
return '%s(%s)' % (clsname, values)
__repr__ = __str__
def __getitem__(self, item):
return getattr(self, self.__slots__[item])
def __setitem__(self, item, value):
return setattr(self, self.__slots__[item], value)
def __len__(self):
return len(self.__slots__)
class Point(NamedMutableSequence):
__slots__ = ('x', 'y')
例:
>>> p = Point(0, 0)
>>> p.x = 10
>>> p
Point(x=10, y=0)
>>> p.x *= 10
>>> p
Point(x=100, y=0)
如果需要,也可以使用创建类的方法(尽管使用显式类更透明):
def namedgroup(name, members):
if isinstance(members, str):
members = members.split()
members = tuple(members)
return type(name, (NamedMutableSequence,), {'__slots__': members})
例:
>>> Point = namedgroup('Point', ['x', 'y'])
>>> Point(6, 42)
Point(x=6, y=42)
在 Python 2 中,您需要稍微调整它 - 如果您继承自 Sequence
,则该类将有一个__dict__
,并且将停止工作。__slots__
Python 2 中的解决方案是不继承 ,而是继承 。如果需要,您需要将 注册为基类:Sequence
object
isinstance(Point, Sequence) == True
NamedMutableSequence
Sequence
Sequence.register(NamedMutableSequence)
截至 2016 年 1 月 11 日,最新的 namedlist 1.7 通过了 Python 2.7 和 Python 3.5 的所有测试。它是一个纯 python 实现,而 是一个 C 扩展。当然,是否首选 C 扩展取决于您的要求。recordclass
您的测试(另请参阅下面的注释):
from __future__ import print_function
import pickle
import sys
from namedlist import namedlist
Point = namedlist('Point', 'x y')
p = Point(x=1, y=2)
print('1. Mutation of field values')
p.x *= 10
p.y += 10
print('p: {}, {}\n'.format(p.x, p.y))
print('2. String')
print('p: {}\n'.format(p))
print('3. Representation')
print(repr(p), '\n')
print('4. Sizeof')
print('size of p:', sys.getsizeof(p), '\n')
print('5. Access by name of field')
print('p: {}, {}\n'.format(p.x, p.y))
print('6. Access by index')
print('p: {}, {}\n'.format(p[0], p[1]))
print('7. Iterative unpacking')
x, y = p
print('p: {}, {}\n'.format(x, y))
print('8. Iteration')
print('p: {}\n'.format([v for v in p]))
print('9. Ordered Dict')
print('p: {}\n'.format(p._asdict()))
print('10. Inplace replacement (update?)')
p._update(x=100, y=200)
print('p: {}\n'.format(p))
print('11. Pickle and Unpickle')
pickled = pickle.dumps(p)
unpickled = pickle.loads(pickled)
assert p == unpickled
print('Pickled successfully\n')
print('12. Fields\n')
print('p: {}\n'.format(p._fields))
print('13. Slots')
print('p: {}\n'.format(p.__slots__))
Python 2.7 上的输出
1. Mutation of field values p: 10, 12 2. String p: Point(x=10, y=12) 3. Representation Point(x=10, y=12) 4. Sizeof size of p: 64 5. Access by name of field p: 10, 12 6. Access by index p: 10, 12 7. Iterative unpacking p: 10, 12 8. Iteration p: [10, 12] 9. Ordered Dict p: OrderedDict([('x', 10), ('y', 12)]) 10. Inplace replacement (update?) p: Point(x=100, y=200) 11. Pickle and Unpickle Pickled successfully 12. Fields p: ('x', 'y') 13. Slots p: ('x', 'y')
与 Python 3.5 的唯一区别是变小了,大小为 56(Python 2.7 报告为 64)。namedlist
请注意,我已更改您的测试 10 以进行就地替换。有一个方法可以做一个浅拷贝,这对我来说非常有意义,因为标准库中的行为方式相同。更改方法的语义会令人困惑。在我看来,该方法应该用于就地更新。或者也许我没有理解你的测试 10 的意图?namedlist
_replace()
namedtuple
_replace()
_update()
类型。SimpleNamespace 是在 Python 3.3 中引入的,它支持请求的要求。
from types import SimpleNamespace
t = SimpleNamespace(foo='bar')
t.ham = 'spam'
print(t)
namespace(foo='bar', ham='spam')
print(t.foo)
'bar'
import pickle
with open('/tmp/pickle', 'wb') as f:
pickle.dump(t, f)
如果性能并不重要,那么可以使用一个愚蠢的黑客,例如:
from collection import namedtuple
Point = namedtuple('Point', 'x y z')
mutable_z = Point(1,2,[3])
作为此任务的 Pythonic 替代方案,从 Python-3.7 开始,您可以使用 dataclasses
模块,它不仅表现得像一个可变的 ,因为它们使用普通的类定义,它们还支持其他类功能。NamedTuple
来自 PEP-0557:
尽管它们使用非常不同的机制,但数据类可以被认为是“具有默认值的可变命名元组”。由于数据类使用普通的类定义语法,因此您可以自由使用继承、元类、文档字符串、用户定义方法、类工厂和其他 Python 类功能。
提供了一个类装饰器,用于检查具有 PEP 526“变量注释语法”中定义的类型注释的变量的类定义。在本文档中,此类变量称为字段。使用这些字段,装饰器将生成的方法定义添加到类中,以支持实例初始化、repr、比较方法以及“规范”部分中所述的其他可选方法。这样的类称为 Data 类,但该类实际上并没有什么特别之处:装饰器将生成的方法添加到该类中,并返回给定的相同类。
此功能在 PEP-0557 中引入,您可以在提供的文档链接中更详细地了解它。
例:
In [20]: from dataclasses import dataclass
In [21]: @dataclass
...: class InventoryItem:
...: '''Class for keeping track of an item in inventory.'''
...: name: str
...: unit_price: float
...: quantity_on_hand: int = 0
...:
...: def total_cost(self) -> float:
...: return self.unit_price * self.quantity_on_hand
...:
演示:
In [23]: II = InventoryItem('bisc', 2000)
In [24]: II
Out[24]: InventoryItem(name='bisc', unit_price=2000, quantity_on_hand=0)
In [25]: II.name = 'choco'
In [26]: II.name
Out[26]: 'choco'
In [27]:
In [27]: II.unit_price *= 3
In [28]: II.unit_price
Out[28]: 6000
In [29]: II
Out[29]: InventoryItem(name='choco', unit_price=6000, quantity_on_hand=0)
我不敢相信以前没有人这么说过,但在我看来,Python 只是希望你编写自己的简单、可变的类,而不是在你需要“namedtuple
”可变时使用命名元组
。
快速摘要
只需直接跳到下面的方法 5。它简短而切中要害,是迄今为止这些选项中最好的。
各种详细的方法:
方法 1(好):简单、可调用的类__call__()
下面是一个简单的点对象示例:Point
(x, y)
class Point():
def __init__(self, x, y):
self.x = x
self.y = y
def __call__(self):
"""
Make `Point` objects callable. Print their contents when they
are called.
"""
print("Point(x={}, y={})".format(self.x, self.y))
现在使用它:
p1 = Point(1,2)
p1()
p1.x = 7
p1()
p1.y = 8
p1()
下面是输出:
Point(x=1, y=2)
Point(x=7, y=2)
Point(x=7, y=8)
这与 非常相似,只是它是完全可变的,不像 .此外,a 是不可调用的,因此要查看其内容,只需键入不带 OUT 括号的对象实例名称(如下例所示,而不是 as )。请参阅此示例并在此处输出:namedtuple
namedtuple
namedtuple
p2
p2()
>>> from collections import namedtuple
>>> Point2 = namedtuple("Point2", ["x", "y"])
>>> p2 = Point2(1, 2)
>>> p2
Point2(x=1, y=2)
>>> p2()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'Point2' object is not callable
>>> p2.x = 7
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
方法 2(更好):代替__repr__()
__call__()
我刚刚了解到你可以用它来代替 ,以获得更像 的行为。通过定义该方法,您可以定义“对象的'官方'字符串表示形式”(请参阅此处的官方文档)。现在,仅调用就等效于调用方法,并且您将获得与 .这是新类:__repr__()
__call__()
namedtuple
__repr__()
p1
__repr__()
namedtuple
class Point():
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
"""
Obtain the string representation of `Point`, so that just typing
the instance name of an object of this type will call this method
and obtain this string, just like `namedtuple` already does!
"""
return "Point(x={}, y={})".format(self.x, self.y)
现在使用它:
p1 = Point(1,2)
p1
p1.x = 7
p1
p1.y = 8
p1
下面是输出:
Point(x=1, y=2)
Point(x=7, y=2)
Point(x=7, y=8)
方法 3(更好,但使用起来有点笨拙):使其成为返回元组的可调用对象(x, y)
原始海报 (OP) 也希望这样的东西可以工作(请参阅我的答案下方的评论):
x, y = Point(x=1, y=2)
好吧,为了简单起见,让我们让它工作:
x, y = Point(x=1, y=2)()
# OR
p1 = Point(x=1, y=2)
x, y = p1()
当我们在做的时候,让我们也浓缩一下:
self.x = x
self.y = y
...进入这个(我第一次看到这个的来源):
self.x, self.y = x, y
以下是上述所有内容的类定义:
class Point():
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self):
"""
Obtain the string representation of `Point`, so that just typing
the instance name of an object of this type will call this method
and obtain this string, just like `namedtuple` already does!
"""
return "Point(x={}, y={})".format(self.x, self.y)
def __call__(self):
"""
Make the object callable. Return a tuple of the x and y components
of the Point.
"""
return self.x, self.y
以下是一些测试调用:
p1 = Point(1,2)
p1
p1.x = 7
x, y = p1()
x2, y2 = Point(10, 12)()
x
y
x2
y2
这次我不会展示将类定义粘贴到解释器中,但以下是那些带有输出的调用:
>>> p1 = Point(1,2)
>>> p1
Point(x=1, y=2)
>>> p1.x = 7
>>> x, y = p1()
>>> x2, y2 = Point(10, 12)()
>>> x
7
>>> y
2
>>> x2
10
>>> y2
12
方法 4(到目前为止最好,但要编写的代码还有很多):使类也成为迭代器
通过将其转换为迭代器类,我们可以得到以下行为:
x, y = Point(x=1, y=2)
# OR
x, y = Point(1, 2)
# OR
p1 = Point(1, 2)
x, y = p1
让我们去掉这个方法,但要使这个类成为一个迭代器,我们将添加 and 方法。在此处阅读有关这些内容的更多信息:__call__()
__iter__()
__next__()
- https://treyhunner.com/2018/06/how-to-make-an-iterator-in-python/
- 如何构建一个基本的迭代器?
- https://docs.python.org/3/library/exceptions.html#StopIteration
解决方案如下:
class Point():
def __init__(self, x, y):
self.x, self.y = x, y
self._iterator_index = 0
self._num_items = 2 # counting self.x and self.y
def __repr__(self):
"""
Obtain the string representation of `Point`, so that just typing
the instance name of an object of this type will call this method
and obtain this string, just like `namedtuple` already does!
"""
return "Point(x={}, y={})".format(self.x, self.y)
def __iter__(self):
return self
def __next__(self):
self._iterator_index += 1
if self._iterator_index == 1:
return self.x
elif self._iterator_index == 2:
return self.y
else:
raise StopIteration
以下是一些测试调用及其输出:
>>> x, y = Point(x=1, y=2)
>>> x
1
>>> y
2
>>> x, y = Point(3, 4)
>>> x
3
>>> y
4
>>> p1 = Point(5, 6)
>>> x, y = p1
>>> x
5
>>> y
6
>>> p1
Point(x=5, y=6)
方法 5(使用这个)(完美--最好和最干净/最短的方法):使用 generator 关键字使类成为可迭代的类yield
研究这些参考资料:
这是解决方案。它依赖于一个花哨的“迭代生成器”(又名:只是“生成器”)关键字/Python 机制,称为 .yield
基本上,当可迭代对象第一次调用下一个项时,它会调用该方法,并停止并返回第一次调用的内容(在下面的代码中)。下次迭代对象调用下一个项目时,它会从上次中断的地方开始(在本例中为第一个项目之后),并查找下一个 ,停止并返回该调用的内容(在下面的代码中)。每个“return”实际上都会返回一个“generator”对象,它本身就是一个可迭代对象,因此您可以对其进行迭代。对下一个项目的每个新的可迭代调用都会继续此过程,从上次中断的地方开始,紧接在最近调用的 之后,直到不再存在调用,此时迭代结束,可迭代已完全迭代。因此,一旦此可迭代对象调用了两个对象,两个调用都已用完,因此迭代器结束。最终的结果是,像这样的调用可以完美地工作,就像在方法 4 中所做的那样,但要编写的代码要少得多!__iter__()
yield
self.x
yield
yield
yield
self.y
yield
yield
yield
yield
x, y = Point(x=1, y=2)
# OR
x, y = Point(1, 2)
# OR
p1 = Point(1, 2)
x, y = p1
这是解决方案(此解决方案的一部分也可以在上面的 treyhunner.com 参考中找到)。请注意,此解决方案的简短和干净!
只是类定义代码;没有文档字符串,因此您可以真正看到这是多么简短和简单:
class Point():
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self):
return "Point(x={}, y={})".format(self.x, self.y)
def __iter__(self):
yield self.x
yield self.y
使用描述性文档字符串:
class Point():
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self):
"""
Obtain the string representation of `Point`, so that just typing
the instance name of an object of this type will call this method
and obtain this string, just like `namedtuple` already does!
"""
return "Point(x={}, y={})".format(self.x, self.y)
def __iter__(self):
"""
Make this `Point` class an iterable. When used as an iterable, it will
now return `self.x` and `self.y` as the two elements of a list-like,
iterable object, "generated" by the usages of the `yield` "generator"
keyword.
"""
yield self.x
yield self.y
复制并粘贴与上面前面的方法(方法 4)中使用的完全相同的测试代码,您还将获得与上述完全相同的输出!
引用:
- https://docs.python.org/3/library/collections.html#collections.namedtuple
- 方法1:
- 方法2:
- 方法4:
- 方法 5:
- 请参阅方法 4 中的链接,以及:
- [太好了!“
yield
”关键字有什么作用?
- 对象名称前的单下划线和双下划线是什么意思?
我能想到的最优雅的方法不需要第三方库,它允许您使用默认成员变量创建快速模拟类构造函数,而无需繁琐的类型规范。因此,最好粗略地编写一些代码:dataclasses
# copy-paste 3 lines:
from inspect import getargvalues, stack
from types import SimpleNamespace
def DefaultableNS(): return SimpleNamespace(**getargvalues(stack()[1].frame)[3])
# then you can make classes with default fields on the fly in one line, eg:
def Node(value,left=None,right=None): return DefaultableNS()
node=Node(123)
print(node)
#[stdout] namespace(value=123, left=None, right=None)
print(node.value,node.left,node.right) # all fields exist
普通的 SimpleNamespace 更笨拙,它会破坏 DRY:
def Node(value,left=None,right=None):
return SimpleNamespace(value=value,left=left,right=right)
# breaks DRY as you need to repeat the argument names twice
如果您希望能够“现场”创建课程,我发现以下内容非常方便:
class Struct:
def __init__(self, **kw):
self.__dict__.update(**kw)
这让我可以写:
p = Struct(x=0, y=0)
P.x = 10
stats = Struct(count=0, total=0.0)
stats.count += 1
我将分享我对这个问题的解决方案。我需要一种方法来保存属性,以防我的程序崩溃或由于某种原因停止,以便它知道要从输入列表中的哪个位置恢复。根据 @GabrielStaples 的回答:
import pickle, json
class ScanSession:
def __init__(self, input_file: str = None, output_file: str = None,
total_viable_wallets: int = 0, total: float = 0,
report_dict: dict = {}, wallet_addresses: list = [],
token_map: list = [], token_map_file: str = 'data/token.maps.json',
current_batch: int = 0):
self.initialized = time.time()
self.input_file = input_file
self.output_file = output_file
self.total_viable_wallets = total_viable_wallets
self.total = total
self.report_dict = report_dict
self.wallet_addresses = wallet_addresses
self.token_map = token_map
self.token_map_file = token_map_file
self.current_batch = current_batch
@property
def __dict__(self):
"""
Obtain the string representation of `Point`, so that just typing
the instance name of an object of this type will call this method
and obtain this string, just like `namedtuple` already does!
"""
return {'initialized': self.initialized, 'input_file': self.input_file,
'output_file': self.output_file, 'total_viable_wallets': self.total_viable_wallets,
'total': self.total, 'report_dict': self.report_dict,
'wallet_addresses': self.wallet_addresses, 'token_map': self.token_map,
'token_map_file':self.token_map_file, 'current_batch': self.current_batch
}
def load_session(self, session_file):
with open(session_file, 'r') as f:
_session = json.loads(json.dumps(f.read()))
_session = dict(_session)
for key, value in _session.items():
setattr(self, key, value)
def dump_session(self, session_file):
with open(session_file, 'w') as f:
json.dump(self.__dict__, fp=f)
使用它:
session = ScanSession()
session.total += 1
session.__dict__
{'initialized': 1670801774.8050613, 'input_file': None, 'output_file': None, 'total_viable_wallets': 0, 'total': 10, 'report_dict': {}, 'wallet_addresses': [], 'token_map': [], 'token_map_file': 'data/token.maps.json', 'current_batch': 0}
pickle.dumps(session)
b'\x80\x04\x95\xe8\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x0bScanSession\x94\x93\x94)\x81\x94}\x94(\x8c\x0binitialized\x94GA\xd8\xe5\x9a[\xb3\x86 \x8c\ninput_file\x94N\x8c\x0boutput_file\x94N\x8c\x14total_viable_wallets\x94K\x00\x8c\x05total\x94K\n\x8c\x0breport_dict\x94}\x94\x8c\x10wallet_addresses\x94]\x94\x8c\ttoken_map\x94]\x94\x8c\x0etoken_map_file\x94\x8c\x14data/token.maps.json\x94\x8c\rcurrent_batch\x94K\x00ub.'
上一个:意外更改结构的副本而不是结构本身
下一个:不可变类型与可变类型
评论