Python 中是否存在可变命名元组?

Existence of mutable named tuple in Python?

提问人:Alexander 提问时间:3/27/2015 最后编辑:MazdakAlexander 更新时间:9/26/2023 访问量:65321

问:

任何人都可以修改 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)

必须可以腌制所得物体。根据命名元组的特征,在构造对象时,表示时输出的顺序必须与参数列表的顺序匹配。

python-3.x 可变 namedtuple

评论

4赞 senshin 3/27/2015
另请参阅:stackoverflow.com/q/5131044。有什么理由你不能只使用字典吗?

答:

24赞 kennes 3/27/2015 #1

这个问题的答案似乎是否定的。

下面非常接近,但从技术上讲是不可变的。这将创建一个具有更新的 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__

3赞 Roland Smith 3/31/2015 #2

根据定义,元组是不可变的。

但是,您可以创建一个字典子类,您可以在其中使用点表示法访问属性;

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}
3赞 agomcas 3/31/2015 #3

如果您想要与 namedtuples 类似的行为但可变,请尝试 namedlist

请注意,为了可变,它不能是元组。

3赞 MadMan2064 4/1/2015 #4

让我们通过动态类型创建来实现这一点:

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"])

是的,它确实保留了类型创建中列出的字段的顺序。

166赞 intellimath 4/3/2015 #5

有一个可变的替代方案 – 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_dataclassupdatemakereplaceself._updateself._replaceself._asdictcls._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__

10赞 Antti Haapala -- Слава Україні 4/3/2015 #6

以下是 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 中的解决方案是不继承 ,而是继承 。如果需要,您需要将 注册为基类:Sequenceobjectisinstance(Point, Sequence) == TrueNamedMutableSequenceSequence

Sequence.register(NamedMutableSequence)
26赞 Ali 1/11/2016 #7

截至 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()

75赞 funky-future 10/30/2016 #8

类型。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)
1赞 Srg 10/31/2016 #9

如果性能并不重要,那么可以使用一个愚蠢的黑客,例如:

from collection import namedtuple

Point = namedtuple('Point', 'x y z')
mutable_z = Point(1,2,[3])
73赞 Mazdak 4/17/2018 #10

作为此任务的 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)
2赞 Gabriel Staples 3/23/2021 #11

我不敢相信以前没有人这么说过,但在我看来,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 )。请参阅此示例并在此处输出:namedtuplenamedtuplenamedtuplep2p2()

>>> 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__()

  1. https://treyhunner.com/2018/06/how-to-make-an-iterator-in-python/
  2. 如何构建一个基本的迭代器?
  3. 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

研究这些参考资料:

  1. https://treyhunner.com/2018/06/how-to-make-an-iterator-in-python/
  2. “yield”关键字有什么作用?

这是解决方案。它依赖于一个花哨的“迭代生成器”(又名:只是“生成器”)关键字/Python 机制,称为 .yield

基本上,当可迭代对象第一次调用下一个项时,它会调用该方法,并停止并返回第一次调用的内容(在下面的代码中)。下次迭代对象调用下一个项目时,它会从上次中断的地方开始(在本例中为第一个项目之后),并查找下一个 ,停止并返回该调用的内容(在下面的代码中)。每个“return”实际上都会返回一个“generator”对象,它本身就是一个可迭代对象,因此您可以对其进行迭代。对下一个项目的每个新的可迭代调用都会继续此过程,从上次中断的地方开始,紧接在最近调用的 之后,直到不再存在调用,此时迭代结束,可迭代已完全迭代。因此,一旦此可迭代对象调用了两个对象,两个调用都已用完,因此迭代器结束。最终的结果是,像这样的调用可以完美地工作,就像在方法 4 中所做的那样,但要编写的代码要少得多!__iter__()yieldself.xyieldyieldyieldself.yyieldyieldyieldyield

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)中使用的完全相同的测试代码,您还将获得与上述完全相同的输出!

引用:

  1. https://docs.python.org/3/library/collections.html#collections.namedtuple
  2. 方法1:
    1. __init____call__ 有什么区别?
  3. 方法2:
    1. https://www.tutorialspoint.com/What-does-the-repr-function-do-in-Python-Object-Oriented-Programming
    2. __repr__方法的目的是什么?
    3. https://docs.python.org/3/reference/datamodel.html#object.__repr__
  4. 方法4:
    1. [太好了!https://treyhunner.com/2018/06/how-to-make-an-iterator-in-python/
    2. 如何构建一个基本的迭代器?
    3. https://docs.python.org/3/library/exceptions.html#StopIteration
  5. 方法 5:
    1. 请参阅方法 4 中的链接,以及:
    2. [太好了!yield”关键字有什么作用?
  6. 对象名称前的单下划线和双下划线是什么意思?
0赞 Rian Rizvi 1/16/2022 #12

我能想到的最优雅的方法不需要第三方库,它允许您使用默认成员变量创建快速模拟类构造函数,而无需繁琐的类型规范。因此,最好粗略地编写一些代码: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
1赞 Igor Gatis 9/18/2022 #13

如果您希望能够“现场”创建课程,我发现以下内容非常方便:

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
0赞 Chev_603 12/12/2022 #14

我将分享我对这个问题的解决方案。我需要一种方法来保存属性,以防我的程序崩溃或由于某种原因停止,以便它知道要从输入列表中的哪个位置恢复。根据 @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.'