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

167

有人能修改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)

转换出的对象必须是可被pickle序列化的。并且与命名元组的特性相符,当表示为字符串时,其顺序必须与构建对象时参数列表的顺序相匹配。


4
另请参阅:https://dev59.com/XW435IYBdhLWcg3w7kwK/。你为什么不能只使用字典呢? - senshin
2
@senshin 感谢提供链接。我不想使用字典,因为它所指出的原因。该回复还链接到http://code.activestate.com/recipes/52308-the-simple-but-handy-collector-of-a-bunch-of-named/?in=user-97991,这与我想要的非常接近。 - Alexander
1
namedtuple不同的是,似乎您无需能够通过索引引用属性,即p[0]p[1]将是分别引用xy的替代方式,对吗? - martineau
1
理想情况下,是的,可以像普通元组一样按位置索引,并且可以像元组一样解包。这个ActiveState的代码示例很接近,但我认为它使用的是普通字典而不是有序字典。http://code.activestate.com/recipes/500261/ - Alexander
11
可变的命名元组称为类。 - gbtimmon
显示剩余5条评论
14个回答

1

如果性能并不重要,可以使用一个愚蠢的技巧,例如:

from collection import namedtuple

Point = namedtuple('Point', 'x y z')
mutable_z = Point(1,2,[3])

1
这个答案解释得不太好。如果你不理解列表的可变性,它看起来很混乱。--- 在这个例子中...要重新分配z,你必须调用mutable_z.z.pop(0)然后mutable_z.z.append(new_value)。如果你做错了,你最终会得到多于1个元素,并且你的程序会表现出意外的行为。 - byxor
2
@byxor,或者你可以直接这样做:mutable_z.z[0] = newValue。正如所述,这确实是一种hack方法。 - Srg
哦,是啊,我很惊讶自己错过了更明显的重新分配方法。 - byxor
如果我需要一个快速的数据类,我通常会使用列表作为可变属性的成员 :) 它可以工作,但肯定不是一个优美的解决方案。 - hochl

1

如果您想要能够“现场”创建类,我认为以下内容非常方便:

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

我将分享我的解决方案。我需要一种方法,在程序崩溃或由于某些原因停止时保存属性,以便它知道应该从输入列表的哪个位置恢复。基于@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.'

0
我能想到的最优雅的方法不需要第三方库,可以让你创建一个快速的模拟类构造器,并使用默认成员变量,而无需 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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接