使具有不可选取字段的对象可选取的正确方法是什么?

Cha*_*ker 6 python pickle dill

对我来说,我所做的就是检测不可选取的内容并将其放入字符串中(我想我也可以将其删除,但随后它会错误地告诉我该字段不存在,但我宁愿让它存在但成为字符串) )。但我想知道是否有一种不那么老套、更正式的方式来做到这一点。

我当前使用的代码:

def make_args_pickable(args: Namespace) -> Namespace:
    """
    Returns a copy of the args namespace but with unpickable objects as strings.

    note: implementation not tested against deep copying.
    ref:
        - /sf/ask/4908983481/
    """
    pickable_args = argparse.Namespace()
    # - go through fields in args, if they are not pickable make it a string else leave as it
    # The vars() function returns the __dict__ attribute of the given object.
    for field in vars(args):
        field_val: Any = getattr(args, field)
        if not dill.pickles(field_val):
            field_val: str = str(field_val)
        setattr(pickable_args, field, field_val)
    return pickable_args
Run Code Online (Sandbox Code Playgroud)

背景:我认为我这样做主要是为了删除我随身携带的烦人的张量板对象(但.tb由于wandb/权重和偏差,我认为我不再需要该字段)。这并不是很重要,但背景总是好的。

有关的:


编辑:

由于我决定放弃 dill - 因为有时它无法恢复类/对象(可能是因为它无法保存他们的代码或其他东西) - 我决定只使用pickle(这似乎是在 PyTorch 中完成的推荐方法)。

那么,检查没有莳萝或官方泡菜的可采摘物的官方(可能是优化的)方法是什么?

这是最好的吗:

def is_picklable(obj):
  try:
    pickle.dumps(obj)

  except pickle.PicklingError:
    return False
  return True
Run Code Online (Sandbox Code Playgroud)

因此当前的解决方案:

def make_args_pickable(args: Namespace) -> Namespace:
    """
    Returns a copy of the args namespace but with unpickable objects as strings.

    note: implementation not tested against deep copying.
    ref:
        - /sf/ask/4908983481/
    """
    pickable_args = argparse.Namespace()
    # - go through fields in args, if they are not pickable make it a string else leave as it
    # The vars() function returns the __dict__ attribute of the given object.
    for field in vars(args):
        field_val: Any = getattr(args, field)
        # - if current field value is not pickable, make it pickable by casting to string
        if not dill.pickles(field_val):
            field_val: str = str(field_val)
        elif not is_picklable(field_val):
            field_val: str = str(field_val)
        # - after this line the invariant is that it should be pickable, so set it in the new args obj
        setattr(pickable_args, field, field_val)
    return pickable_args


def make_opts_pickable(opts):
    """ Makes a namespace pickable """
    return make_args_pickable(opts)


def is_picklable(obj: Any) -> bool:
    """
    Checks if somehting is pickable.

    Ref:
        - /sf/ask/4908983481/
    """
    import pickle
    try:
        pickle.dumps(obj)
    except pickle.PicklingError:
        return False
    return True
Run Code Online (Sandbox Code Playgroud)

注意:我想要“官方”/测试的原因之一是因为我在 try catch 上停止了 pycharm:如何在已处理的异常上停止 PyCharm 的中断/停止/停止功能(即仅在 python 未处理的异常上中断)?这不是我想要的......我希望它只在未处理的异常上停止。

syt*_*ech 2

使具有不可选取字段的对象可选取的正确方法是什么?

我相信这个问题的答案属于您链接的问题 - Python - 如何使这个不可腌制的对象可腌制?。我为该问题添加了一个新答案__reduce__,解释了如何以正确的方式使不可腌制的对象可腌制,而不使用.

那么,检查没有莳萝或官方泡菜的可采摘物的官方(可能是优化的)方法是什么?

可picklable 的对象在文档中定义如下:

  • NoneTrue, 和False
  • 整数、浮点数、复数
  • 字符串、字节、字节数组
  • 仅包含可腌制对象的元组、列表、集合和字典
  • 在模块顶层定义的函数(使用def, not lambda
  • 在模块顶层定义的内置函数
  • 在模块顶层定义的类
  • 此类的实例,其字典或调用getstate ()的结果是可挑选的(有关详细信息,请参阅“挑选类实例”部分)。

棘手的部分是(1)了解如何定义函数/类(您可能可以使用inspect该模块)和(2)递归对象,根据上述规则进行检查。

对此有很多注意事项,例如 pickle协议版本、对象是否是扩展类型(例如在 numpy 等 C 扩展中定义)或“用户定义”类的实例。使用__slots__还会影响对象是否可腌制(因为__slots__意味着没有__dict__),但可以使用 进行腌制__getstate__。某些对象还可以使用自定义函数注册以进行酸洗。因此,您需要知道这种情况是否也发生过。

从技术上讲,您可以在 Python 中实现一个函数来检查所有这些,但相比之下,它会相当慢。最简单的(也可能pickle是最高效的,如在 C 中实现的)方法是简单地尝试 pickle 您想要检查的对象。

我用 PyCharm 对各种东西进行了测试...它不会用这种方法停止。关键是您必须预见到几乎所有类型的异常(请参阅文档中的脚注 3 )。警告是可选的,它们主要是对该问题的上下文的解释。

def is_picklable(obj: Any) -> bool:
    try:
        pickle.dumps(obj)
        return True
    except (pickle.PicklingError, pickle.PickleError, AttributeError, ImportError):
        # https://docs.python.org/3/library/pickle.html#what-can-be-pickled-and-unpickled
        return False
    except RecursionError:
        warnings.warn(
            f"Could not determine if object of type {type(obj)!r} is picklable"
            "due to a RecursionError that was supressed. "
            "Setting a higher recursion limit MAY allow this object to be pickled"
        )
        return False
    except Exception as e:
        # https://docs.python.org/3/library/pickle.html#id9
        warnings.warn(
            f"An error occurred while attempting to pickle"
            f"object of type {type(obj)!r}. Assuming it's unpicklable. The exception was {e}"
        )
        return False
Run Code Online (Sandbox Code Playgroud)

使用我上面链接的其他答案中的示例,您可以通过实现__getstate____setstate__(或子类化并添加它们,或制作包装器类)调整您的make_args_pickable...来使您的对象可挑选。

class Unpicklable:
    """
    A simple marker class so we can distinguish when a deserialized object
    is a string because it was originally unpicklable 
    (and not simply a string to begin with)
    """
    def __init__(self, obj_str: str):
        self.obj_str = obj_str

    def __str__(self):
        return self.obj_str

    def __repr__(self):
        return f'Unpicklable(obj_str={self.obj_str!r})'


class PicklableNamespace(Namespace):
    def __getstate__(self):
        """For serialization"""

        # always make a copy so you don't accidentally modify state
        state = self.__dict__.copy()

        # Any unpicklables will be converted to a ``Unpicklable`` object 
        # with its str format stored in the object
        for key, val in state.items():
            if not is_picklable(val):
                state[key] = Unpicklable(str(val))
        return state
    def __setstate__(self, state):
        self.__dict__.update(state)  # or leave unimplemented
Run Code Online (Sandbox Code Playgroud)

在实际操作中,我将腌制一个其属性包含文件句柄(通常不可腌制)的名称空间,然后加载腌制数据。

# Normally file handles are not picklable
p = PicklableNamespace(f=open('test.txt'))

data = pickle.dumps(p)
del p

loaded_p = pickle.loads(data)
# PicklableNamespace(f=Unpicklable(obj_str="<_io.TextIOWrapper name='test.txt' mode='r' encoding='cp1252'>"))
Run Code Online (Sandbox Code Playgroud)