python是否与其他方法保持注解的顺序?

6ioyuze2  于 2021-08-25  发布在  Java
关注(0)|答案(2)|浏览(260)

考虑下面的类:

@dataclass
class Point:
   id: int

   x: int
   y: int
   @property
   def distance_from_zero(self): return (self.x**2 + self.y**2)**0.5

   color: tuple #RGB or something...

我知道我可以从 __annotations__ 变量,或从 dataclasses.fields 井然有序。我也知道任何对象的常规方法都可以用 dir 或者使用 __dict__ 方法。
但我所追求的是能够以正确的顺序给我两者的东西,在上面的例子中,它会是这样的:

>>>get_all_fields(Point)
['id', 'x', 'y', 'distance_from_zero', 'color']

我唯一能想到的就是使用 inspect 模块读取实际代码并以某种方式找到顺序。但这听起来真的很恶心。

ctzwtxfj

ctzwtxfj1#

您声明希望避免使用检查,但据我所知,这将是唯一干净的解决方案。其他任何东西都注定是不准确、不可靠或麻烦的。
看这里,使用 inspect.getsource 还有一点习俗 ast 节点漫游器:

import ast
import inspect

class AttributeVisitor(ast.NodeVisitor):
    def visit_ClassDef(self, node):
        self.attributes = []
        for statement in node.body:
            if isinstance(statement, ast.AnnAssign):
                self.attributes.append(statement.target.id)
            elif isinstance(statement, ast.FunctionDef):
                # only consider properties
                if statement.decorator_list:
                    if "property" in [d.id for d in statement.decorator_list]:
                        self.attributes.append(statement.name)
            else:
                print(f"Skipping {statement=}")

# parse the source code of "Point", so we don't have to write a parser ourselves

tree = ast.parse(inspect.getsource(Point), '<string>')

# create a visitor and run it over the tree line by line

visitor = AttributeVisitor()
visitor.visit(tree)

# print result, should be ['id', 'x', 'y', 'distance_from_zero', 'color']

print(visitor.attributes)

使用此解决方案意味着您不必更改 Point 为了得到你想要的东西,你可以用任何方式上课。

mxg2im7a

mxg2im7a2#

这是迄今为止我发现的最好的解决方法,其思想是在创建类时 __annotations__ 对象将开始逐个填充,因此一个选项是在创建类期间跟踪属性。它并不完美,因为它迫使您使用替代的装饰器而不是属性,而且它也不能对函数方法执行同样的操作(但我现在不关心这一点)。在我的实现中,您还必须装饰整个类,以便附加一个 classmethod 它实际上输出命令。

import inspect
def ordered_property( f ):
    if isinstance(f, type):
        @classmethod
        def list_columns( cls ):
            if not list_columns.initiated:
                for annotation in cls.__annotations__:
                    if annotation not in cls.__columns__:
                        cls.__columns__.append(annotation)
                list_columns.initiated = True
            return cls.__columns__

        list_columns.initiated = False
        f.list_columns = list_columns
        return f
    else:
        #Two stacks from the start, is the class object that's being constructed.
        class_locals = inspect.stack()[1].frame.f_locals
        class_locals.setdefault('__columns__', [])

        for annotation in class_locals['__annotations__']:
            if annotation not in class_locals['__columns__']:
                class_locals['__columns__'].append(annotation)

        class_locals['__columns__'].append(f.__name__)
        return property(f)

问题中的例子必须改为:

@dataclass
@ordered_property
class Point:
   id: int

   x: int
   y: int
   @ordered_property
   def distance_from_zero(self): return (self.x**2 + self.y**2)**0.5

   color: tuple #RGB or something...

最后,输出是这样的:

>>>Point.list_columns()
['id', 'x', 'y', 'distance_from_zero', 'color']

(我不会把它标记为答案,因为它有一个脚印,并且不考虑类中的可调用方法)

相关问题