【问题标题】:Return the entire nested, generated code of a dynamic Python class返回动态 Python 类的整个嵌套生成的代码
【发布时间】:2021-08-17 04:11:41
【问题描述】:

在 Python 2.7 中给定一个这样的装饰类:

import attr

@attr.s(slots=True, frozen=True)
class User(object):
    name = attr.ib(
        validator=attr.validators.instance_of(str),
        converter=lambda _name: str(_name).upper(),
    )
    id = attr.ib(validator=attr.validators.instance_of(int))


print(User("bob", 123))
# User(name='BOB', id=123)

如何获取所有生成的类代码,这样我理论上可以将输出复制到一个新类中。我能做的最好的就是:

import inspect
print(inspect.getsource(User.__init__))

输出:

def __init__(self, name, id):
    _setattr = _cached_setattr.__get__(self, self.__class__)
    _setattr('name', __attr_converter_name(name))
    _setattr('id', id)
    if _config._run_validators is True:
        __attr_validator_name(self, __attr_name, self.name)
        __attr_validator_id(self, __attr_id, self.id)

然后我需要_cached_setattr__attr_validator_name__attr_validator_id 的嵌套源等等。是否有一种方法或技术可以递归地遍历类并在每个方法上执行inspect.getsource 之类的操作,直到打印出可以独立存在的类定义?

【问题讨论】:

  • 您能否详细说明为什么需要这样做?
  • 你为什么假设 attr 生成你正在寻找的那种源代码?
  • “我需要证明使用静态源代码(在验证中带有调整和快捷方式)比在“热”代码中使用属性糖更快。” - 然后自己写。 attr 不符合您的要求。
  • 与其尝试从attr 输出生成独立的类定义,不如比较 使用attr 的类和带有样板的类的性能手动写的?您将能够测试您的假设,即 attr 会减慢速度,如果您是对的,您将能够向利益相关者提供具体的性能数字,而不是告诉他们“哇,看看所有这些生成的代码,它是必须慢一点,对吧?”
  • 我同意@user2357112supportsMonica。我开始深入研究attr.s 实现,在方法调用期间使用的这些变量在评估__init__ 方法期间作为全局变量传入。它们不直接包含在生成的类中。请参阅_compile_and_eval 的来源。测试性能的最简单方法是对类的手工制作版本进行 A/B 测试,并将其与 attrs 生成的版本进行比较。

标签: python python-2.7 class python-decorators dynamically-generated


【解决方案1】:

来自每个人的精彩的 cmets。谢谢。

与其尝试从 attr 输出生成独立的类定义,为什么不直接比较使用 attr 的类和手动编写样板的类的性能?

在 cmets 中回复,但可能是瓶颈的实际实体再次具有类似于 20 个这些属性 的东西,带有验证器和转换器(糟糕)甚至更多的装饰器 - 所有这些都加起来而不是易于为 A/B 重写。首先,我需要证明在 A/B 获得批准之前,我什至可以做得更好。

没有“类代码”。每次编译一个代码 sn-p 时,都会附上源代码。 User 的来源是你在 OP 中写的。 User.__init__ 的来源还是你写的。

是和不是。我没有写init@attr 写了,但我再次提示验证器(instance_of 是句法糖),但@attr 按摩。 p>

如果您想一目了然地查看 User 中发生的一切,import dis; dis.dis(User)

好电话。这是具有两个属性的那个小类的反汇编。是的,最重要的部分被全局缓存,但只在__init__

Disassembly of __init__:
  2           0 LOAD_GLOBAL              0 (__attr_converter_name)

完全拆解:

Disassembly of __eq__:
  2           0 LOAD_FAST                1 (other)
              3 LOAD_ATTR                0 (__class__)
              6 LOAD_FAST                0 (self)
              9 LOAD_ATTR                0 (__class__)
             12 COMPARE_OP               9 (is not)
             15 POP_JUMP_IF_FALSE       22
  3          18 LOAD_GLOBAL              1 (NotImplemented)
             21 RETURN_VALUE        
  5     >>   22 LOAD_FAST                0 (self)
             25 LOAD_ATTR                2 (name)
  6          28 LOAD_FAST                0 (self)
             31 LOAD_ATTR                3 (id)
             34 BUILD_TUPLE              2
  8          37 LOAD_FAST                1 (other)
             40 LOAD_ATTR                2 (name)
  9          43 LOAD_FAST                1 (other)
             46 LOAD_ATTR                3 (id)
             49 BUILD_TUPLE              2
             52 COMPARE_OP               2 (==)
             55 RETURN_VALUE        
Disassembly of __ge__:
1825           0 LOAD_FAST                1 (other)
              3 LOAD_ATTR                0 (__class__)
              6 LOAD_FAST                0 (self)
              9 LOAD_ATTR                0 (__class__)
             12 COMPARE_OP               8 (is)
             15 POP_JUMP_IF_FALSE       40
1826          18 LOAD_DEREF               0 (attrs_to_tuple)
             21 LOAD_FAST                0 (self)
             24 CALL_FUNCTION            1
             27 LOAD_DEREF               0 (attrs_to_tuple)
             30 LOAD_FAST                1 (other)
             33 CALL_FUNCTION            1
             36 COMPARE_OP               5 (>=)
             39 RETURN_VALUE        
1828     >>   40 LOAD_GLOBAL              1 (NotImplemented)
             43 RETURN_VALUE        
Disassembly of __getstate__:
912           0 LOAD_GLOBAL              0 (tuple)
              3 LOAD_CLOSURE             0 (self)
              6 BUILD_TUPLE              1
              9 LOAD_CONST               1 (<code object <genexpr> at 0x7f27325e72b0, file "/usr/local/lib/python2.7/dist-packages/attr/_make.py", line 912>)
             12 MAKE_CLOSURE             0
             15 LOAD_DEREF               1 (state_attr_names)
             18 GET_ITER            
             19 CALL_FUNCTION            1
             22 CALL_FUNCTION            1
             25 RETURN_VALUE        
Disassembly of __gt__:
1816           0 LOAD_FAST                1 (other)
              3 LOAD_ATTR                0 (__class__)
              6 LOAD_FAST                0 (self)
              9 LOAD_ATTR                0 (__class__)
             12 COMPARE_OP               8 (is)
             15 POP_JUMP_IF_FALSE       40
1817          18 LOAD_DEREF               0 (attrs_to_tuple)
             21 LOAD_FAST                0 (self)
             24 CALL_FUNCTION            1
             27 LOAD_DEREF               0 (attrs_to_tuple)
             30 LOAD_FAST                1 (other)
             33 CALL_FUNCTION            1
             36 COMPARE_OP               4 (>)
             39 RETURN_VALUE        
1819     >>   40 LOAD_GLOBAL              1 (NotImplemented)
             43 RETURN_VALUE        
Disassembly of __init__:
  2           0 LOAD_GLOBAL              0 (__attr_converter_name)
              3 LOAD_FAST                1 (name)
              6 CALL_FUNCTION            1
              9 LOAD_FAST                0 (self)
             12 STORE_ATTR               1 (name)
  3          15 LOAD_FAST                2 (id)
             18 LOAD_FAST                0 (self)
             21 STORE_ATTR               2 (id)
  4          24 LOAD_GLOBAL              3 (_config)
             27 LOAD_ATTR                4 (_run_validators)
             30 LOAD_GLOBAL              5 (True)
             33 COMPARE_OP               8 (is)
             36 POP_JUMP_IF_FALSE       80
  5          39 LOAD_GLOBAL              6 (__attr_validator_name)
             42 LOAD_FAST                0 (self)
             45 LOAD_GLOBAL              7 (__attr_name)
             48 LOAD_FAST                0 (self)
             51 LOAD_ATTR                1 (name)
             54 CALL_FUNCTION            3
             57 POP_TOP             
  6          58 LOAD_GLOBAL              8 (__attr_validator_id)
             61 LOAD_FAST                0 (self)
             64 LOAD_GLOBAL              9 (__attr_id)
             67 LOAD_FAST                0 (self)
             70 LOAD_ATTR                2 (id)
             73 CALL_FUNCTION            3
             76 POP_TOP             
             77 JUMP_FORWARD             0 (to 80)
        >>   80 LOAD_CONST               0 (None)
             83 RETURN_VALUE        
Disassembly of __le__:
1807           0 LOAD_FAST                1 (other)
... snip ....

但是,如果我们查看globals(),nada。

如果我们进入User.__init__ 方法,我们会看到一个不同的故事。

当我们离开__init__ 时,那些全局变量消失了!我不确定@attr 包是如何将全局变量范围限定为__init__ 与@987654340 中的这个sn-p @:

def _compile_and_eval(script, globs, locs=None, filename=""):
    """
    "Exec" the script with the given global (globs) and local (locs) variables.
    """
    bytecode = compile(script, filename, "exec")
    eval(bytecode, globs, locs)

但是这里发生了魔法,它阻止了我倾倒__init__ 的完整图片。


为了尝试回答我的问题,我开始编写递归脚本的 start 来转储“完整”类。由于 Python 的魔力,我放弃了它,但对于它的价值,@attr 增加了 很多 更多。

import attr
import inspect
import types


@attr.s(slots=True, frozen=False)
class User(object):
    name = attr.ib(
        validator=attr.validators.instance_of(str),
        converter=lambda _name: str(_name).upper(),
    )
    id = attr.ib(validator=attr.validators.instance_of(int))


def public_dir(obj):
    return [x for x in dir(obj) if not x.startswith("__")]


def indent(level, txt):
    padding = "    " * level
    print(padding + ("\n" + padding).join(str(txt).rstrip().split("\n")))


def source_all_properties(clazz, level=0):
    indent(level, clazz.__name__)
    props = dir(clazz)
    indent(level, props)
    for p in props:
        indent(level + 1, p)
        if p in clazz.__dict__:
            ref = clazz.__dict__[p]
            indent(level + 2, "type: {}".format(type(ref)))
            if isinstance(ref, types.FunctionType):
                indent(level + 2, "source: " + inspect.getsource(ref))
            elif isinstance(ref, types.ObjectType):
                indent(level + 2, "mros: " + str(inspect.getmro(type(ref))))
                indent(level + 2, "props: " + str(public_dir(ref)))
        else:
            _type = eval("type(clazz.{})".format(p))
            _val = eval("clazz.{}".format(p))
            indent(level + 2, "type: {}: {}".format(_type, _val))

    return level + 1


if __name__ == "__main__":
    print(source_all_properties(User))

输出如下:

User
['__attrs_attrs__', '__attrs_own_setattr__', '__class__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', '__weakref__', 'id', 'name']
    __attrs_attrs__
        type: <class 'UserAttributes'>
        mros: (<class 'UserAttributes'>, <type 'tuple'>, <type 'object'>)
        props: ['count', 'id', 'index', 'name']
    __attrs_own_setattr__
        type: <type 'bool'>
        mros: (<type 'bool'>, <type 'int'>, <type 'object'>)
        props: ['bit_length', 'conjugate', 'denominator', 'imag', 'numerator', 'real']
    __class__
        type: <type 'type'>: <type 'type'>
    __delattr__
        type: <type 'wrapper_descriptor'>: <slot wrapper '__delattr__' of 'object' objects>
    __doc__
        type: <type 'NoneType'>
        mros: (<type 'NoneType'>, <type 'object'>)
        props: []
    __eq__
        type: <type 'function'>
        source: def __eq__(self, other):
            if other.__class__ is not self.__class__:
                return NotImplemented
    __format__
        type: <type 'method_descriptor'>: <method '__format__' of 'object' objects>
    __ge__
        type: <type 'function'>
        source:     def __ge__(self, other):
                """
                Automatically created by attrs.
                """
                if other.__class__ is self.__class__:
                    return attrs_to_tuple(self) >= attrs_to_tuple(other)
        
                return NotImplemented
    __getattribute__
        type: <type 'wrapper_descriptor'>: <slot wrapper '__getattribute__' of 'object' objects>
    __getstate__
        type: <type 'function'>
        source:         def slots_getstate(self):
                    """
                    Automatically created by attrs.
                    """
                    return tuple(getattr(self, name) for name in state_attr_names)
    __gt__
        type: <type 'function'>
        source:     def __gt__(self, other):
                """
                Automatically created by attrs.
                """
                if other.__class__ is self.__class__:
                    return attrs_to_tuple(self) > attrs_to_tuple(other)
        
                return NotImplemented
    __hash__
        type: <type 'NoneType'>
        mros: (<type 'NoneType'>, <type 'object'>)
        props: []
    __init__
        type: <type 'function'>
        source: def __init__(self, name, id):
            self.name = __attr_converter_name(name)
            self.id = id
            if _config._run_validators is True:
                __attr_validator_name(self, __attr_name, self.name)
                __attr_validator_id(self, __attr_id, self.id)
    __le__
        type: <type 'function'>
        source:     def __le__(self, other):
                """
                Automatically created by attrs.
                """
                if other.__class__ is self.__class__:
                    return attrs_to_tuple(self) <= attrs_to_tuple(other)
        
                return NotImplemented
    __lt__
        type: <type 'function'>
        source:     def __lt__(self, other):
                """
                Automatically created by attrs.
                """
                if other.__class__ is self.__class__:
                    return attrs_to_tuple(self) < attrs_to_tuple(other)
        
                return NotImplemented
    __module__
        type: <type 'str'>
        mros: (<type 'str'>, <type 'basestring'>, <type 'object'>)
        props: ['_formatter_field_name_split', '_formatter_parser', 'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
    __ne__
        type: <type 'function'>
        source:     def __ne__(self, other):
                """
                Check equality and either forward a NotImplemented or
                return the result negated.
                """
                result = self.__eq__(other)
                if result is NotImplemented:
                    return NotImplemented
        
                return not result
    __new__
        type: <type 'builtin_function_or_method'>: <built-in method __new__ of type object at 0x55f761d79a20>
    __reduce__
        type: <type 'method_descriptor'>: <method '__reduce__' of 'object' objects>
    __reduce_ex__
        type: <type 'method_descriptor'>: <method '__reduce_ex__' of 'object' objects>
    __repr__
        type: <type 'function'>
        source:     def __repr__(self):
                """
                Automatically created by attrs.
                """
                try:
                    working_set = _already_repring.working_set
                except AttributeError:
                    working_set = set()
                    _already_repring.working_set = working_set
        
                if id(self) in working_set:
                    return "..."
                real_cls = self.__class__
                if ns is None:
                    qualname = getattr(real_cls, "__qualname__", None)
                    if qualname is not None:
                        class_name = qualname.rsplit(">.", 1)[-1]
                    else:
                        class_name = real_cls.__name__
                else:
                    class_name = ns + "." + real_cls.__name__
        
                # Since 'self' remains on the stack (i.e.: strongly referenced) for the
                # duration of this call, it's safe to depend on id(...) stability, and
                # not need to track the instance and therefore worry about properties
                # like weakref- or hash-ability.
                working_set.add(id(self))
                try:
                    result = [class_name, "("]
                    first = True
                    for name, attr_repr in attr_names_with_reprs:
                        if first:
                            first = False
                        else:
                            result.append(", ")
                        result.extend(
                            (name, "=", attr_repr(getattr(self, name, NOTHING)))
                        )
                    return "".join(result) + ")"
                finally:
                    working_set.remove(id(self))
    __setattr__
        type: <type 'wrapper_descriptor'>: <slot wrapper '__setattr__' of 'object' objects>
    __setstate__
        type: <type 'function'>
        source:         def slots_setstate(self, state):
                    """
                    Automatically created by attrs.
                    """
                    __bound_setattr = _obj_setattr.__get__(self, Attribute)
                    for name, value in zip(state_attr_names, state):
                        __bound_setattr(name, value)
        
                    # The hash code cache is not included when the object is
                    # serialized, but it still needs to be initialized to None to
                    # indicate that the first call to __hash__ should be a cache
                    # miss.
                    if hash_caching_enabled:
                        __bound_setattr(_hash_cache_field, None)
    __sizeof__
        type: <type 'method_descriptor'>: <method '__sizeof__' of 'object' objects>
    __slots__
        type: <type 'tuple'>
        mros: (<type 'tuple'>, <type 'object'>)
        props: ['count', 'index']
    __str__
        type: <type 'wrapper_descriptor'>: <slot wrapper '__str__' of 'object' objects>
    __subclasshook__
        type: <type 'builtin_function_or_method'>: <built-in method __subclasshook__ of type object at 0x55f763779300>
    __weakref__
        type: <type 'getset_descriptor'>
        mros: (<type 'getset_descriptor'>, <type 'object'>)
        props: []
    id
        type: <type 'member_descriptor'>
        mros: (<type 'member_descriptor'>, <type 'object'>)
        props: []
    name
        type: <type 'member_descriptor'>
        mros: (<type 'member_descriptor'>, <type 'object'>)
        props: []

测试性能的最简单方法是对类的手工制作版本进行 A/B 测试,并将其与 attrs 生成的版本进行比较。

不容易,但这里别无选择。非常感激。希望这是其他人的学习机会。

【讨论】:

  • 有趣的文章。我想知道是否只是用热循环制作一个人为的示例是否有助于您切换到手动创建的类与 attrs 的论点。如果人为设计的示例显示出前景,它可能会为您提供更好的理由来进行更昂贵/更现实的测试。
  • "是也不是。我没有写init@attr 写了" — 我的意思不是你自己编码的,我的意思是就 Python 所知,两者的来源UserUser.__init__ 正是您在 OP 中报告的内容。 “当我们离开 __init__ 时,那些全局变量消失了!”——compile 创建了一个模块; globs 将成为该模块中的全局变量。 Python 中没有跨模块全局变量,这就是为什么在 __init__ 之外看不到它们的原因。
  • @Amadan 太疯狂了。你知道,我实际上用周末重写了包含 20 个属性/验证器/转换器的大型类,而我的热代码速度要快得多,尽管这些 glob 在 init 中通过“@attr”秘密可用。我必须让人们相信我对 glob 的看法,并且只是展示速度测试比较来提倡删除“@attr”。再次感谢大家。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-03
  • 2011-04-05
  • 1970-01-01
  • 1970-01-01
  • 2018-06-04
  • 2010-12-17
相关资源
最近更新 更多