【问题标题】:How to make type cast for python custom class如何为python自定义类进行类型转换
【发布时间】:2024-01-18 23:43:01
【问题描述】:

考虑一个类;

class temp:
    def __init__(self):
        pass

我把它当作一个对象;

obj = temp()

转成字符串;

strObj = str(obj)

现在,如何将 strObj 转换为 temp 类的对象??

org = temp(strObj)

【问题讨论】:

  • 我不认为你可以做这样的事情,你为什么需要这个?
  • 您需要一个可以从字符串创建对象的构造函数。 def __init__(self, x=""):,如果您想使用多个构造函数,请查看 *.com/questions/682504/…
  • 这是一个实验。我想向 MongoDB 插入一个类实例。但不能直接做,所以我不得不把它转换成一个字符串。现在,我需要把它转换回来。
  • 你正在查看错误的转换,你想要的是 object serialization ,不要使用 str(..)
  • 如果你想从字符串重新创建对象,你应该使用它的repr 而不是str 形式。如果你想把它放在数据库中,为什么不做一个每行是一个实例,每个字段是一个实例属性的表呢?

标签: python class python-2.7 casting


【解决方案1】:

对于那些正在寻找覆盖转换内置函数(例如 int(obj)float(obj)str(obj))的用户,请参阅 Overload int() in Python。您需要在对象上实现__int____float____str__

【讨论】:

    【解决方案2】:

    要回答这个问题,一种方法是“滥用”__repr__eval()。我们先来看看__repr__ 文档(重点:我的):

    由 repr() 内置函数调用以计算“官方” 对象的字符串表示。 如果可能的话,这应该 看起来像一个有效的 Python 表达式,可用于重新创建 具有相同值的对象(给定适当的环境)。 如果这个 是不可能的,格式为 <...> 的字符串 应该退回。返回值必须是字符串对象。如果一个 类定义了__repr__() 但不是__str__(),那么__repr__() 也是 当该实例的“非正式”字符串表示时使用 类是必需的。

    这通常用于调试,因此重要的是 表示信息丰富且明确。

    考虑到这一点,我们知道建议从__repr__ 返回一个可以与eval() 一起使用的字符串。值“应该看起来像一个有效的 Python 表达式”这一声明暗示了这一点。

    示例

    这是一个使用它的例子。该示例还覆盖了__eq__,但只是为了方便打印。为了完整起见,我们还为实例添加了一个值。

    该示例创建了一个新实例。然后使用__repr__ 将值转换为字符串(通过使用repr() 函数。接下来将该字符串值传递给eval()eval() 将评估字符串并返回结果。结果将是同一个类并存储在second_instance。我们还打印出id(),以可视化我们确实有两个不同的实例。最后我们证明first_instance == second_instance确实是True

    class MyClass:
    
        def __init__(self, value):
            self.value = value
    
        def __eq__(self, other):
            return isinstance(self, MyClass) and self.value == other.value
    
        def __repr__(self):
            return '%s(%r)' % (self.__class__.__name__, self.value)
    
    
    first_instance = MyClass(123)
    print('First instance: repr=%r, id=%d' % (first_instance, id(first_instance)))
    
    stringified = repr(first_instance)
    print('Stringified: %r' % stringified)
    
    second_instance = eval(stringified)  # !!! DANGEROUS (see below) !!!
    print('Second instance: repr=%r, id=%d' % (second_instance, id(second_instance)))
    
    print('First == Second: %r' % (first_instance == second_instance))
    

    什么时候可以这样做?

    如果进入eval() 的所有内容都在您的控制之下,这是100% 可以接受的!这意味着:

    • eval() 的调用范围由您控制
    • 评估字符串中的任何位置都不应包含来自外部来源的数据。外部来源包括:
      • 数据库值
      • 用户输入
      • 从磁盘读取数据
      • ...基本上任何 I/O

    牢记所有这些并保证项目 I/O 的未来绝不会以eval() 调用告终,这几乎是不可能的。因此,我强烈建议在重要的生产代码中避免这样做,因为它会带来令人讨厌的安全漏洞。

    对于不在生产环境中运行的代码,这是绝对可以接受的。例如单元测试、个人实用程序脚本等。但应始终考虑风险。

    为什么这很危险?

    • 传递给eval() 的代码在调用它的Python 进程中执行,具有相同的权限。示例:您从多个用户可以访问的数据库中读取一个值,并且您 eval() 它。在这种情况下,另一个用户可能会通过数据库注入代码,并且该代码将以您的用户身份运行
    • 当值来自外部来源时使用 eval() 会打开代码注入的可能性。
    • 不保证repr() 将返回有效的Python 表达式。这只是文档的建议。因此,使用__repr__ 调用eval 很容易出现运行时错误。
    • 在上面的例子中,调用eval() 的作用域需要“知道”类MyClass(它必须被导入)。它只查找名称。因此,如果纯粹偶然地在作用域中存在相同的名称,但指向另一个对象,您将无意中调用其他对象,并且可能会遇到奇怪的错误。当然,这是一个极端情况。

    更安全的选择

    使用众多可用的序列化选项之一。最流行和最简单的使用方法是将对象转换为 JSON 字符串/从 JSON 字符串转换。上面的例子可以像这样变得安全:

    import json
    
    
    class MyClass:
    
        @staticmethod
        def from_json(document):
            data = json.loads(document)
            instance = MyClass(data['value'])
            return instance
    
        def __init__(self, value):
            self.value = value
    
        def __eq__(self, other):
            return isinstance(self, MyClass) and self.value == other.value
    
        def __repr__(self):
            return '%s(%r)' % (self.__class__.__name__, self.value)
    
        def to_json(self):
            data = {
                'value': self.value
            }
            return json.dumps(data)
    
    
    first_instance = MyClass(123)
    print('First instance: repr=%r, id=%d' % (first_instance, id(first_instance)))
    
    stringified = first_instance.to_json()
    print('Stringified: %r' % stringified)
    
    second_instance = MyClass.from_json(stringified)
    print('Second instance: repr=%r, id=%d' % (second_instance, id(second_instance)))
    
    print('First == Second: %r' % (first_instance == second_instance))
    

    这只是稍微困难一点,但更安全。

    同样的方法可以用于其他序列化方法。流行的格式有:

    • XML
    • YAML
    • ini/cfg files
    • pickle(请注意,这使用字节而不是文本作为序列化介质)。
    • MessagePack(注意这里使用字节而不是文本作为序列化介质)。
    • 自定义实现
    • ...

    【讨论】:

      【解决方案3】:

      正如 Anand 在 cmets 中所指出的,您正在寻找的是对象序列化和反序列化。实现此目的的一种方法是通过 pickle(或 cPickle)模块:

      >>> import pickle
      
      >>> class Example():
      ...     def __init__(self, x):
      ...             self.x = x
      ...
      >>> a = Example('foo')
      >>> astr = pickle.dumps(a) # (i__main__\nExample\np0\n(dp1\nS'x'\np2\nS'foo'\np3\nsb.
      >>> b = pickle.loads(astr)
      >>> b
      <__main__.Example instance at 0x101c89ea8>
      >>> b.x
      'foo'
      

      但是请注意,使用 pickle 模块的一个问题是处理实现版本。正如 Python 文档中所建议的那样,如果您希望未腌制的实例自动处理实现版本控制,您可能需要添加版本实例属性并添加自定义 __setstate__ 实现:https://docs.python.org/2/library/pickle.html#object.setstate。否则,序列化时的对象版本将与您在反序列化时获得的版本完全相同,而不管对对象本身所做的代码更改。

      【讨论】:

      • 感谢您的简化!
      【解决方案4】:

      如果根据传递的参数类型定义类构造函数会怎样。此外,如果您想允许在不传递参数的情况下创建对象,则可以将参数放在 *args 中。在下面的代码中,构造函数检查参数的数量和类型,并根据它写入self.attr 不同的值。 类似的东西:

      class temp():
          def __init__(self, *args):
              if len(args) > 0:
                  if isinstance(args[0],str):
                      self.attr = args[0]
                  else:
                      self.attr = 'Not a string'
              else:
                  self.attr = 'Not a string'
          def __str__(self):
              return self.attr
      
      obj = temp()
      obj.attr = 'Hello World'
      strObj = str(obj)
      org = temp(strObj)
      

      【讨论】:

      • 能否请您简要解释一下解决方案以及为什么它解决了问题中的问题?
      • 我试图通过解释一个想法来增强帖子。有意义吗?
      【解决方案5】:

      您可以创建一个专门的类来利用 json 库和内置方法来处理转换。

      示例演员班:

      import json
      
      class Cast(object):
      
        def __init__(self):
          super(Cast, self).__init__()
      
        @staticmethod
        def _to(clazz, obj):
          if (clazz == str):
            return json.dumps(obj, default=lambda obj: obj.__dict__)
          else:
            newinstance = clazz()
            if (isinstance(obj, str)):
              jsonObj = json.loads(obj)
              sharedKeys = jsonObj.keys()
              for objKey in sharedKeys:
                setattr(newinstance, objKey, jsonObj[objKey])
            else:
              # Test/Convert for other standard or custom types
              pass
            return newinstance
      

      代码说明:

      • Cast._to 方法用于将您的自定义对象转换为所需的类。使用流控处理各种情况。

      • 在此示例中,如果转换为 str 类,它将使用 json 转储将对象转换为 json 字符串。

      • 当转换为字符串以外的任何内容时,使用内置的 isinstance 方法来处理指定对象将如何通过流控制进行转换。

      • 本例中,对于第一个else块,假设对象为json字符串。

      • json 加载方法然后将其转换为表示 clazz 对象的 dict。提取键,这将是与 clazz 对象的任何实例相同的键。
        键用于从 json 对象中提取值,并使用内置的 setattr 将它们传递给新的类实例。

      • 关于 Cast._to 的重要注意事项。要使第一个 else 块起作用,要转换到的自定义“clazz”必须具有要么无参数构造函数所有的默认值论据。 这是一种类似于 Java 的方法,它总是有一个默认的无 args 构造函数,除非在这种情况下,只要 args 具有默认值,您就可以使用带 args 的构造函数。

      测试

      class temp(object):
      
        def __init__(self, attr1=None, attr2=None, attr3=None):
          super(temp, self).__init__()
          self.attr1 = attr1
          self.attr2 = attr2
          self.attr3 = attr3
          self.attr4 = None
      
      def __main__():
        obj = temp()
        strObj = Cast._to(str, obj)
        org = Cast._from(strObj, temp)
        print(type(org))
        print(org.attr2)
      
        obj2 = temp("OdinsBeard", "Gungnir")
        obj2.attr4 = "Fenrir"
        strObj2 = Cast._to(str, obj2)
        org2 = Cast._from(strObj2, temp)
        print(type(org2))
        print(org2.attr2)
      
      
      __main__()
      

      输出:

      ma​​in.temp'>

      ma​​in.temp'>
      冈尼尔

      其他选项:

      如果您希望自定义对象在调用 str() 和从数组打印时自动成为 json 字符串,则需要分别覆盖自定义类的 __str__ 和 __repr__ 方法。

        def __str__(self):
          return json.dumps(self, default=lambda obj: obj.__dict__)
      
        def __repr__(self):
          return json.dumps(self, default=lambda obj: obj.__dict__)
      

      测试

      使用与上面第一个测试相同的设置,但使用新添加的 __str__ 和 __repr__ 方法,输出如下。

        print(str(obj))
        print(str(obj2))
      
      #Output Below:
      {"attr4": null, "attr2": null, "attr3": null, "attr1": null}
      {"attr4": "Fenrir", "attr2": "Gungnir", "attr3": null, "attr1": "OdinsBeard"}
      

      【讨论】:

        【解决方案6】:

        我认为您正在寻找的是类的构造函数。只需定义一个构造函数来检查传递的参数是否为字符串。然后再做一些您可能想做的更改,以从字符串创建对象。

        class obj():
           def __init__(self, attribs*):
               if len(attribs)==1 and type(attribs[0])=='str':
                   #your string manipulations go here
                   self.content = attribs[0].upper()
        

        【讨论】: