简体   繁体   English

如何使用反射或解析读取Python类中已声明成员的顺序(禁止使用元类替换)?

[英]How to read order of declared members in Python class with use reflection or parsing (metaclass replace is forbidden)?

Important notes to avoid too fast/invalid duplicate tagging - please read it before answer. 重要说明,以避免太快/无效的重复标记-请在回答前阅读它。

Please do not suggest any solution changing original class code - not changing code reflection and parsing is allowed. 不要提出任何更改原始类代码的解决方案 -不允许更改代码反射和解析。

  1. How to read class attributes in the same order as declared? 如何以与声明相同的顺序读取类属性? is solution - it requires to replace meta class in all classes and add overhead - definitely it is not reflection use. 解决方案-需要替换所有类中的元类并增加开销-绝对不是反射用途。

  2. Consider that I can not or do not want change code to scan class members order . 考虑到我不能或不想更改代码来扫描类成员的命令 Classes can have or has unknown metaclasses already - it is not possible to just add metaclass or add performance overhead without reason. 类可以已经具有或具有未知的元类-不可能无缘无故地添加元类或增加性能开销。

  3. Only reflection can be used or parsing files. 只能使用反射或解析文件。


I want to avoid writing parser and read class attributes in order of declaration. 我想避免按声明顺序编写解析器和读取类属性。

How it is possible with use reflection (and simple parsing) in Python? 在Python中使用反射 (和简单解析)怎么可能

Let me give some example: 让我举个例子:

class A(object):
  b = 1
  a = 1
  c = 1

dir(A) give alphabetic order but required is declaration order. dir(A)给出字母顺序,但必须是声明顺序。 How to do it - please help? 怎么做-请帮忙?

You'll have to resort to parsing. 您将不得不求助于解析。 You don't need to write a parser here; 您无需在此处编写解析器; the ast module can do this for you. ast模块可以为您完成此任务。

Parse the source with ast.parse() , then walk the resulting tree: 使用ast.parse()解析源,然后遍历结果树:

class ClassOrder(ast.NodeVisitor):
    identifiers = None
    def visit_ClassDef(self, node):
        self.identifiers = []
        for child in node.body:
            if isinstance(child, ast.Assign):
                for target in child.targets:
                    self.visit(target)
            elif isinstance(child, ast.FunctionDef):
                self.identifiers.append(child.name)
    def visit_Name(self, node):
        if self.identifiers is not None:
            self.identifiers.append(node.id)

tree = ast.parse(sourcecode)
order = ClassOrder()
order.visit(tree)
print order.identifiers

prints out the order of assignments and methods in all class definitions. 在所有类定义中打印出分配和方法的顺序。

Demo: 演示:

>>> sourcecode = '''\
... class A(object):
...   b = 1
...   a = 1
...   c = 1
... '''
>>> tree = ast.parse(sourcecode)
>>> order = ClassOrder()
>>> order.visit(tree)
>>> print order.identifiers
['b', 'a', 'c']
>>> tree = ast.parse(inspect.getsource(ast.NodeVisitor))
>>> order = ClassOrder()
>>> order.visit(tree)
>>> print order.identifiers
['visit', 'generic_visit']

Bundled up as a function, using inspect.getsource() : 使用inspect.getsource()捆绑为一个函数:

import inspect

def get_identifiers(obj):
    source = inspect.getsource(obj)
    tree = ast.parse(source)
    order = ClassOrder()
    order.visit(tree)
    return order.identifiers

inspect.getsource() can handle any object for which the source is available . inspect.getsource()可以处理任何源可用的对象。 For classes, it'll extract just the source block defining that class, if available. 对于类,它将提取定义该类的源代码块(如果有)。

It is alternative inspire by @Martijn Pieters - this production code using different approach in scanning. 它是@Martijn Pieters的替代启发-此生产代码使用不同的扫描方法。

It allow scan __main__ what is problem for inspect.getsource(obj) . 它允许扫描__main__检查inspect.getsource(obj)什么问题。

It collect also types for each name and function - order scan will allow filter more attributes, functions and extract more information. 它还为每个名称和功能收集类型-顺序扫描将允许筛选更多属性,功能并提取更多信息。

import ast
import sys
import codecs
import pprint

class A(object):
  b = 1
  a = 1
  c = 1
  e, f, g = (1, 2, 3)
  z = x = 1
  ((a1, b1), (c1, d1)) = ((1, 2), (1, 2))

class B(A):
  pass

class ClassOrderVisitor(ast.NodeVisitor):
  def __init__(self):
    self.classes = {}

  def __parseTuple(self, astTuple, fields):
    for element in astTuple.elts:
      if isinstance(element, ast.Name):
        fields.append((element.id, element))
      elif isinstance(element, ast.Tuple):
        self.__parseTuple(element, fields)
      else:
        raise NotImplementedError()

  def visit_ClassDef(self, node):
    fields = []
    for field in ast.iter_fields(node):
      # class name
      if field[0] == 'name':
        className = field[1]
        self.classes[className] = fields
      # class body
      elif field[0] == 'body':
        for bodyItem in field[1]:
          if isinstance(bodyItem, ast.Assign):
            for target in bodyItem.targets:
              if isinstance(target, ast.Name):
                fields.append((target.id, target))
              elif isinstance(target, ast.Tuple):
                self.__parseTuple(target, fields)
              else:
                raise NotImplementedError()

          elif isinstance(bodyItem, ast.FunctionDef):
            fields.append((bodyItem.name, bodyItem))

# this file is named ast_parser.py not using inspect.getsource(obj)
# since problem with __main__ scan
def scanOrder(fileName):
  with codecs.open(fileName, encoding = 'utf8') as sourceFile:
    sourceCode = sourceFile.read()
  codeTree = ast.parse(sourceCode, fileName)
  classOrderVisitor = ClassOrderVisitor()
  classOrderVisitor.visit(codeTree)
  return classOrderVisitor.classes

# run
pprint.pprint(scanOrder('ast_parser.py'))
print [x for x in dir(A) if not x.startswith('__') and not x.endswith('__')]

Output: 输出:

{'A': [('b', <_ast.Name object at 0x01375E70>),
       ('a', <_ast.Name object at 0x01375ED0>),
       ('c', <_ast.Name object at 0x01375F30>),
       ('e', <_ast.Name object at 0x01375FB0>),
       ('f', <_ast.Name object at 0x01375FD0>),
       ('g', <_ast.Name object at 0x01375FF0>),
       ('z', <_ast.Name object at 0x0137B0D0>),
       ('x', <_ast.Name object at 0x0137B0F0>),
       ('a1', <_ast.Name object at 0x0137B190>),
       ('b1', <_ast.Name object at 0x0137B1B0>),
       ('c1', <_ast.Name object at 0x0137B1F0>),
       ('d1', <_ast.Name object at 0x0137B210>)],
 'B': [],
 'ClassOrderVisitor': [('__init__', <_ast.FunctionDef object at 0x0137B3D0>),
                       ('__parseTuple',
                        <_ast.FunctionDef object at 0x0137B4F0>),
                       ('visit_ClassDef',
                        <_ast.FunctionDef object at 0x0137BA10>)]}
['a', 'a1', 'b', 'b1', 'c', 'c1', 'd1', 'e', 'f', 'g', 'x', 'z']

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM