来自每个人的精彩的 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 生成的版本进行比较。
不容易,但这里别无选择。非常感激。希望这是其他人的学习机会。