按照代码中的顺序获取类的方法

Posted

技术标签:

【中文标题】按照代码中的顺序获取类的方法【英文标题】:Get method of a class in the order that it was in the code 【发布时间】:2017-09-01 21:07:37 【问题描述】:

这段代码:

import inspect

class Obj():

    def c(self):
        return 1

    def b(self):
        return 2

    def a(self):
        return 3

o = Obj()

for name, value in inspect.getmembers(o, inspect.ismethod):
    print str(value())+" "+name

打印:

3 a
2 b
1 c

因为inspect.getmembers 在按名称排序的(名称,值)对列表中返回对象的所有成员,正如您在https://docs.python.org/2/library/inspect.html#inspect.getmembers 中所读到的那样

但我想以与成员在代码中写入的顺序相同的顺序获取该列表,换句话说,输出将是:

1 c
2 b
3 a

有什么办法吗?

谢谢

【问题讨论】:

继承呢?如果o的方法定义在两个不同的类中,它们应该以什么顺序出现? How to read class attributes in the same order as declared?的可能重复 这是哪个 Python 版本? Python 2 和 3 有非常不同的解决方案(甚至小版本也有差异)。 【参考方案1】:

cpython 中,代码被编译为 VM 的字节码。并且这些函数有一个__code__ 属性,它是一个代码对象。代码对象有一个co_firstlineno 属性,这是Python 源代码中的第一行。 (详见inspect模块。)

如果您知道您的方法都在源代码中,并且您知道您正在使用 cpython,那么您可以将其用作排序键。但是,如果您不知道这些事情,就会显得非常不稳定。

members = [ (name,meth) for name, meth in inspect.getmembers(o, inspect.ismethod)]

members = sorted(members, key=lambda t: t[1].__code__.co_firstlineno)

print '\n'.join(m[0] for m in members)

【讨论】:

这几乎可以工作,直到你引入装饰器。然后co_firstlineno 来自与装饰函数定义的位置完全无关的地方。 是的。如果您可以控制来源,@Wintro's 可能是一个更好的答案。【参考方案2】:

创建对象时,它的所有属性都包含在对象的另一个专用属性中,称为__dict__,顾名思义,它只是一个普通的 Python 无序字典,因此不能保证它们存储在以相同的方式添加它们。当使用 getmembers() 检索 __dict__ 中的值时,Python 会在打印字典时自动重新组织字典,以使其具有一定的逻辑意义。

为了解决这个问题,必须采取措施将常规 Python 字典 __dict__ 转换为某种有序字典。

这可以通过多种方式完成,为简单起见,我假设您使用的是 Python 3。

使用collections 包,您可以获得OrderedDict,这正是我们处理此类问题所需的技术。准备这个有序字典以在需要存储有序成员的类的元类中使用,复制成员,最后在想要打印出所述成员时访问这个新的OrderedDict

这可以在this Stack Overflow answer 中看到。

【讨论】:

Python 2.7 没有__prepare__ 糟糕,我想打出 3。感谢您的收看。【参考方案3】:

没有。类成员没有排序。他们被收集到一本字典里,立即失去了秩序。您可以诉诸诸如解析源代码之类的技巧,但它很容易中断。首先,源无法获得。

[编辑:似乎python3在创建类时允许更大的灵活性,使得customize the way class members are gathered成为可能,如果你只在python3上,那可能是一个更好的方法]

如果更改代码不是问题,您可以使用装饰器:

import inspect

def track_order(fn):
    fn.order = track_order.idx
    track_order.idx += 1
    return fn
track_order.idx = 0

class Obj(object):
    @track_order
    def c(self):
        return 1

    @track_order
    def b(self):
        return 2

    @track_order
    def a(self):
        return 3

o = Obj()

methods = sorted((item
                  for item in inspect.getmembers(o, inspect.ismethod)),
                 key=lambda item: item[1].order)

for name, value in methods:
    print str(value())+" "+name

装饰器将idx 属性添加到所有通过它的方法。 这利用了python具有一流功能的事实。

$ python test.py
1 c
2 b
3 a

注意:这是 Django 用来跟踪表单和模型字段顺序的方法。只是,它们不需要装饰器,因为字段的类具有内置的实例化顺序属性(它被命名为creation_counter)。

【讨论】:

实际上有相当多的 nice solution 在使用元类的欺骗目标中,__prepare__ 可用于 Python 3。在 3.6 中,成员将自动排序! @juanpa.arrivillaga> 确实不错的解决方案。对于纯 python3 的代码可能要好得多。我在顶部添加了一条评论,但是对于我们这些仍然必须保持与遗留代码的兼容性的人,我会给出我的答案:)【参考方案4】:

嗯,这很hacky,但基本上我直接检查源并使用re 查找方法名称。不过,这个解决方案非常脆弱,而且它不处理继承,但也许它对你有用。假设我已将您的类定义保存在名为 test.py 的文件中:

>>> import test
>>> import re
>>> findmethods = re.compile(r"    def (.+)\(")
>>> findmethods.findall(inspect.getsource(test.Obj))
['c', 'b', 'a']
>>>

【讨论】:

如果你担心正则表达式失败,你可以import astnames = [node.name for node in ast.parse(inspect.getsource(test.Obj)).body[0].body if isinstance(node, ast.FunctionDef)]。但是,仍然取决于具有可访问源代码的类,这不是确定的事情。如光谱所示。 @Kevin 是的,当然,任何需要直接检查源代码的解决方案都注定会变得非常糟糕并且充满潜在的陷阱。毫无疑问!但这是 ast 的一个很好的用途,使它不那么脆弱!

以上是关于按照代码中的顺序获取类的方法的主要内容,如果未能解决你的问题,请参考以下文章

js 把对象按照属性名的字母顺序进行排列

java做题笔记

unittest执行顺序,使用unittest.main()按照test开头,由0-9,A-Z,a-z的顺序执行; 可使用TestSuite类的addTest方法改变执行顺序;

java类加载和对象初始化

Java面试

Java 基础:继承中的执行顺序