【问题标题】:getting the class path or name space of a class in python even if it is nested在python中获取类的类路径或名称空间,即使它是嵌套的
【发布时间】:2010-12-17 06:23:17
【问题描述】:

我目前正在用 Python 编写一个可以序列化用户定义类的序列化模块。为此,我需要获取对象的完整名称空间并将其写入文件。然后我可以使用该字符串重新创建对象。

例如假设我们在一个名为A.py的文件中有以下类结构

class B:
    class C:
        pass

现在假设my_klass_string 是字符串"A::B::C"

klasses = my_klass_string.split("::")
if globals().has_key(klasses[0]):   
    klass = globals()[klasses[0]]
else:
    raise TypeError, "No class defined: %s} " % klasses[0]
if len(klasses) > 1:
    for klass_string in klasses:
        if klass.__dict__.has_key(klass_string):
            klass = klass.__dict__[klass_string]
        else:
            raise TypeError, "No class defined: %s} " % klass_string            
klass_obj = klass.__new__(klass)

我可以创建类 C 的实例,即使它位于模块 A 中的类 B 下。 以上代码相当于调用eval(klass_obj = A.B.C.__new__(A.B.C))

注意: 我在这里使用__new__(),因为我正在重构一个序列化对象并且我不想初始化该对象,因为我不知道该类的__init__ 方法采用哪些参数。我想在不调用 init 的情况下创建对象,然后再为其分配属性。

我可以通过任何方式从字符串创建 A.B.C 类的对象。我该如何走另一条路?即使类是嵌套的,如何从该类的实例获取描述类的完整路径的字符串?

【问题讨论】:

  • +1 提出一个有趣且解释清楚的问题。

标签: python serialization introspection


【解决方案1】:

你不能得到“给定一个实例的类的完整路径” 类”,因为在 Python 中没有这样的东西。对于 例如,以您的示例为基础:

>>> class B(object):
...     class C(object):
...             pass
... 
>>> D = B.C
>>> x = D()
>>> isinstance(x, B.C)
True

x 的“类路径”应该是什么? D 还是 B.C?两者都是 同样有效,因此 Python 没有给你任何告诉你的方法 来自另一个。

确实,即使是 Python 的 pickle 模块也无法对对象 x 进行酸洗:

>>> import pickle
>>> t = open('/tmp/x.pickle', 'w+b')
>>> pickle.dump(x, t)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.6/pickle.py", line 1362, in dump
    Pickler(file, protocol).dump(obj)
  ...
  File "/usr/lib/python2.6/pickle.py", line 748, in save_global
   (obj, module, name))
  pickle.PicklingError: Can't pickle <class '__main__.C'>: it's not found as __main__.C

所以,总的来说,除了添加属性,我没有其他选择 到你所有的类(比如_class_path),你的序列化代码会查找它 将类名记录成序列化格式:

class A(object):
  _class_path = 'mymodule.A'
  class B(object):
    _class_path = 'mymodule.A.B'
    ...

您甚至可以使用 some metaclass magic 自动执行此操作(但也可以阅读 the same SO post 中的其他 cmets,了解如果您执行上述 D=B.C 可能适用的警告)。

也就是说,如果您可以将序列化代码限制为 (1) 个实例 新式类,以及 (2) 这些类在 模块的顶层,然后你可以复制 pickle 所做的 (函数save_global 在 Python 的 pickle.py 中的第 730--768 行 2.6)。

想法是每个新样式类都定义属性__name____module__,它们是扩展为类名的字符串(如 在源代码中找到)和模块名称(如在 sys.modules);通过保存这些,您可以稍后导入模块并 获取类的实例:

__import__(module_name)
class_obj = getattr(sys.modules[module_name], class_name)

【讨论】:

  • +1:边界情况的出色突出显示可能会引起很多麻烦。
  • 我相信这最好地回答了我的问题,我知道我正在尝试做的事情是多么愚蠢,但这仍然是实现我的目标的最简单方法。如果我使用像 JSON 或 XML 这样的序列化程序,我将不得不编写一个复杂的翻译器来获取 JSON 数据并在 ruby​​ 端构建对象,然后再以这种方式编组它们,我可以进行海峡加载已经有对象并且它们转储他们再次。 Metaclass 魔术链接有很大帮助,因为它基本上描述了确定嵌套类路径的唯一方法。
【解决方案2】:

你不能,以任何合理的非疯狂方式。我猜你可以找到类名和模块,然后为每个类名验证它是否存在于模块中,如果不存在,则以分层方式遍历模块中确实存在的所有类,直到找到它。

但是由于没有理由拥有这样的类层次结构,所以这不是问题。 :-)

另外,我知道你现在不想在你的工作中听到这个,但是:

跨平台序列化是一个有趣的主题,但是像这样使用对象不太可能很有用,因为目标系统必须安装完全相同的对象层次结构。因此,您必须拥有两个用两种完全等效的不同语言编写的系统。这几乎是不可能的,而且可能不值得麻烦。

例如,您将无法使用 Python 标准库中的任何对象,因为 Ruby 中不存在这些对象。最终结果是您必须创建自己的对象层次结构,最终只使用字符串和数字等基本类型。在这种情况下,您的对象刚刚成为基本原语的包含,然后您也可以使用 JSON 或 XML 序列化所有内容。

【讨论】:

  • 如果我必须选择定义系统的两端,那么我不会这样做。但唉,我必须弥合设计不佳的引擎和编辑器之间的差距。这个系统 DOSE 有这样的类层次结构。事实上,我认为它的一部分深入到了 4 个级别。所以你的答案是从__module__ 名称开始,然后循环往下,直到找到正确的类来构建树?听起来它可以工作。感觉就像一个黑客。你知道任何有助于限制迭代次数的循环技巧或类型测试技巧吗?
  • 但要执行您现在尝试执行的操作,您执行 必须能够定义系统的两端。如果你不能,那么你不能像这样序列化对象。那么问题来了:为什么要序列化对象,而不是仅仅序列化数据?
  • 因为那些对象保存数据。我有一个系统,可以从 ruby​​ Marshal 文件中读取数据,并且数据包含在基本结构中,以修改这些文件中的数据,我需要能够读取 ruby​​ marshal。在 min 的一个朋友的帮助下,我试图用 python 编写一个格式的阅读器,我几乎脱离了。但是一些晦涩的错误阻止了我可靠地读取文件,有些文件可以正常工作,有些文件由于奇怪的原因没有失败。所以我决定写一个真正能正确读写格式的ruby程序,并将其用作翻译器。
  • 这并不能回答我为什么要序列化对象的问题。一方面,您可以将数据导出为某种合理的格式,例如 JSON 或 XML,然后在另一方面读取。这要简单得多,而且更有可能实际工作。在这种情况下,如果您拥有的任何对象在另一侧没有表示,它将失败。如果你在 Python 端有一个 datetime 对象,那么在 Ruby 端你必须有一个名为“datetime”的模块和一个名为“datetime”的类,它与 Python 的 datetime.datetime 具有完全相同的属性,或者不起作用。你有这个吗?
  • @Ryex:既然您将可以序列化的数据限制为基本类型,为什么您不使用普通的健全的序列化格式,例如 JSON 或 XML?我现在第三次问这个问题。很抱歉这么说,但我相信你走错了路,你应该减少损失,重新开始。使用 JSON,它非常适合这一点,并且在 Python 和 Ruby 中也很容易使用。我认为 Python 在 stdlib 中支持 2.6 和 2.7,当然也有 Ruby 模块。
【解决方案3】:

我目前正在用 Python 编写一个序列化模块,可以序列化用户定义的类。

不要。标准库已经包含一个。实际上,根据您的计算方式,它至少包括两个(pickleshelve)。

【讨论】:

  • 是的,谢谢,这很好知道,特别是感觉它没有帮助。我很了解 python 的内部序列化模块,但我选择编写我的一个,因为我需要它跨语言工作。我在 ruby​​ 方面取得了成功,现在正在研究 python。如果也许您可以回答我的问题,请编辑您的答案,那将是很棒的。谢谢你的时间。
  • 我知道我们一直在坚持这一点,但是已经有众所周知的跨主机、跨语言序列化格式免费提供(甚至内置到标准库)编写器/解析器。它们被称为 XML、JSON 等。您能否至少简单解释一下为什么它们不适合您的需求?
【解决方案4】:

两种方法可以做到这一点。

解决方案 1

第一个通过垃圾收集器。

B -> __dict__ -> C

这是代码:

>>> class B(object):
    class C(object):
        pass

>>> gc.get_referrers(B.C) # last element in the list
[<attribute '__dict__' of 'C' objects>, <attribute '__weakref__' of 'C' objects>, (<class '__main__.C'>, <type 'object'>), {'__dict__': <attribute '__dict__' of 'B' objects>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'B' objects>, 'C': <class '__main__.C'>, '__doc__': None}] 

>>> gc.get_referrers(gc.get_referrers(B.C)[-1]) # first element in this list
[<class '__main__.B'>, [<attribute '__dict__' of 'C' objects>, <attribute '__weakref__' of 'C' objects>, (<class '__main__.C'>, <type 'object'>), {'__dict__': <attribute '__dict__' of 'B' objects>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'B' objects>, 'C': <class '__main__.C'>, '__doc__': None}]]

>>> gc.get_referrers(gc.get_referrers(B.C)[-1])[0]
<class '__main__.B'>

算法:

  1. 搜索具有与 C 相同的__module__ 的类字典
  2. 上课,使用'C'属性
  3. 如果这个类是嵌套的。 1.递归地做

解决方案 2

使用源文件。使用检查来获取类的行并向上扫描以查找嵌套它的新类。

注意:我不知道在 python 2 中没有干净的方法,但 python 3 提供了一个。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-08
    相关资源
    最近更新 更多