【问题标题】:Why does SimpleNamespace have a different size than that of an empty class?为什么 SimpleNamespace 的大小与空类的大小不同?
【发布时间】:2017-02-06 23:06:32
【问题描述】:

考虑以下几点:

In [1]: import types

In [2]: class A:
    ...:     pass
    ...:

In [3]: a1 = A()

In [4]: a1.a, a1.b, a1.c = 1, 2, 3

In [5]: a2 = types.SimpleNamespace(a=1,b=2,c=3)

In [6]: sys.getsizeof(a1)
Out[6]: 56

In [7]: sys.getsizeof(a2)
Out[7]: 48

这种尺寸差异来自哪里?正在看:

In [10]: types.__file__
Out[10]: '/Users/juan/anaconda3/lib/python3.5/types.py'

我发现:

import sys

# Iterators in Python aren't a matter of type but of protocol.  A large
# and changing number of builtin types implement *some* flavor of
# iterator.  Don't check the type!  Use hasattr to check for both
# "__iter__" and "__next__" attributes instead.

def _f(): pass
FunctionType = type(_f)
LambdaType = type(lambda: None)         # Same as FunctionType
CodeType = type(_f.__code__)
MappingProxyType = type(type.__dict__)
SimpleNamespace = type(sys.implementation)

好吧,这没什么:

>>> import sys
>>> sys.implementation
namespace(cache_tag='cpython-35', hexversion=50660080, name='cpython', version=sys.version_info(major=3, minor=5, micro=2, releaselevel='final', serial=0))
>>> type(sys.implementation)
<class 'types.SimpleNamespace'>

我好像在这里追自己的尾巴。

我能够找到 this related question,但我的特定查询没有答案。

我在 64 位系统上使用 CPython 3.5。这 8 个字节的大小似乎正好适合一些我无法确定的错误引用。

【问题讨论】:

  • 它的出现是因为它们是不同的类型......而sys.getsizeof 并没有告诉你一个对象消耗的总内存,那么为什么这两种类型应该是相同的呢?例如,在您的示例中,getsizeof(A())(“空”A)也是 56。
  • 对,添加属性是一个红鲱鱼。但是即使我只有一个空类class A: pass; a = A()s = SimpleNamespace(),仍然存在差异。
  • 当然......但是,为什么您希望两个空类型具有相同的大小?您是否希望 set()[] 具有相同的大小?
  • @donkopotamus 不一定,但我想了解为什么
  • SimpleNamespace类的定义在namespaceobject.c源文件中。

标签: python


【解决方案1】:

考虑以下具有不同大小的类:

class A_dict:
    pass

class A_slot_0:
    __slots__ = []

class A_slot_1:
    __slots__ = ["a"]

class A_slot_2:
    __slots__ = ["a", "b"]

每个都有不同的基本内存占用:

>>> [cls.__basicsize__ for cls in [A_dict, A_slot_0, A_slot_1, A_slot_2]]
>>> [32, 16, 24, 32]

为什么?在负责创建底层类型和计算实例基本大小的type_new(在typeobject.c中)的源代码中,我们看到tp_basicsize被计算为:

  • 基础类型的tp_basicsizeobject ... 16 字节);
  • 每个插槽的另一个sizeof(PyObject *)
  • sizeof(PyObject *) 如果需要__dict__
  • 如果定义了__weakref__,则为sizeof(PyObject *)

A_dict 这样的普通类将定义__dict____weakref__,而具有插槽的类默认没有__weakref__。因此,普通A_dict 的大小为 32 字节。您可以认为它实际上由 PyObject_HEAD 加上两个指针组成。

现在,考虑在namespaceobject.c 中定义的SimpleNamespace。这里的类型很简单:

typedef struct {
    PyObject_HEAD
    PyObject *ns_dict;
} _PyNamespaceObject;

tp_basicsize 被定义为sizeof(_PyNamespaceObject),使其比普通对象大一个指针,因此为24 字节。

注意:

这里的区别在于A_dict 提供了对弱引用的支持,而types.SimpleNamespace 没有。

>>> weakref.ref(types.SimpleNamespace())
TypeError: cannot create weak reference to 'types.SimpleNamespace' object

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-09-04
    • 2021-10-31
    相关资源
    最近更新 更多