[Python] 魔法方法__len__的底层实现

文章详细介绍了Python中的魔法方法__len__,包括其作用、时间复杂度以及如何自定义。通过对源码的解读,揭示了__len__方法在C语言层面的实现,如何通过PyObject_Size和sq_length等函数获取对象的长度,强调了其高效性。
摘要由CSDN通过智能技术生成

1 魔法方法__len__的介绍

1.1 魔法方法介绍

Python中的魔法方法是指以双下划线__开头和结尾的特殊方法,也称为"dunder methods"(double underscore methods)。这些方法允许你自定义类的行为,使其具有类似于内置类型的行为,例如加法、比较等。它们通常被称为Python的特殊方法,因为它们有着特殊的功能和语法。

以下是一些常见的Python魔法方法及其用途的例子:

__init__(self, ...): 初始化对象,在创建对象时调用该方法。
__str__(self): 返回对象的字符串表示,使用print()函数时会调用该方法。
__repr__(self): 返回对象的"official"字符串表示,通常用于调试和开发目的。
__len__(self): 返回对象的长度,通常用于支持内置len()函数。
__getitem__(self, key): 定义对象的索引操作,使其支持类似列表和字典的索引访问。
__setitem__(self, key, value): 定义对象的索引赋值操作。
__delitem__(self, key): 定义对象的删除操作。
__add__(self, other): 定义对象的加法操作,使其支持+运算符。
__sub__(self, other): 定义对象的减法操作,使其支持-运算符。
__eq__(self, other): 定义对象的相等比较操作,使其支持==运算符。
__lt__(self, other): 定义对象的小于比较操作,使其支持<运算符。
__gt__(self, other): 定义对象的大于比较操作,使其支持>运算符。
__call__(self, ...): 让对象可以像函数一样被调用。

1.2 例子

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __str__(self):
        return f"Vector({self.x}, {self.y})"
    
    def __add__(self, other):
        if isinstance(other, Vector):
            return Vector(self.x + other.x, self.y + other.y)
        else:
            raise TypeError("Unsupported operand type for +")
    
    def __eq__(self, other):
        if isinstance(other, Vector):
            return self.x == other.x and self.y == other.y
        return False

# 创建两个Vector对象
v1 = Vector(1, 2)
v2 = Vector(3, 4)

# 使用自定义的魔法方法进行加法操作
v3 = v1 + v2

# 使用自定义的魔法方法进行相等比较
if v1 == v2:
    print("v1 and v2 are equal")
else:
    print("v1 and v2 are not equal")

print(v3)  # 输出:Vector(4, 6)

1.2 __len__()方法的介绍

该方法的时间复杂度为O(1),也就是说,它的性能是非常高的。当你调用内置的len()函数并传递一个对象作为参数时,Python会尝试调用该对象的__len__()方法来获取其长度。这使得自定义的对象可以像内置的容器对象(例如列表、元组、字符串等)一样支持len()函数。

__len__()方法在你创建的类中可以根据需要进行定义,它应该返回一个表示对象长度的整数值。如果未定义__len__()方法,当尝试使用len()函数获取对象的长度时,会抛出TypeError异常。

2 __len__()的实现源码解读

2.1 Objects的实现

设计到底层的方法,由于对象和方法都是内建的,并不是由Python实现的,我们就不得不看一下它的C语言实现,所以我们先打开Objects的实现,如下,该定义可以在cpython/Include /object.h中找到。

typedef struct {
    PyObject ob_base;
    Py_ssize_t ob_size; /* Number of items in variable part */
} PyVarObject;

可以看到,在Objects中,存在一个ob_size,用于对象的大小,我们在后面会发现__len__()和它的关系。

2.2 __len__()方法实现

该方法的实现可以在cpython/Python /bltinmodule.c中找到,如下

/*[clinic input]
len as builtin_len

    obj: object
    /

Return the number of items in a container.
[clinic start generated code]*/

static PyObject *
builtin_len(PyObject *module, PyObject *obj)
/*[clinic end generated code: output=fa7a270d314dfb6c input=bc55598da9e9c9b5]*/
{
    Py_ssize_t res;

    res = PyObject_Size(obj);
    if (res < 0) {
        assert(PyErr_Occurred());
        return NULL;
    }
    return PyLong_FromSsize_t(res);
}

可以看到,这个函数主要做了以下事情:

  1. 调用了PyObject_Size(obj)函数,该函数的实现如下:
    Py_ssize_t // 返回类型是大小
    PyObject_Size(PyObject *o)
    {
        if (o == NULL) { // 如果为空,则报错
            null_error();
            return -1;
        }
    	/** 
    	   这是通过序列方法表调用对象的sq_length方法,
    	   以获取序列对象的长度。sq_length是一个函数指针,
    	   指向用于获取序列对象长度的函数
    	**/
        PySequenceMethods *m = Py_TYPE(o)->tp_as_sequence;
        // 指针不为空
        if (m && m->sq_length) {
            // 获取长度
            Py_ssize_t len = m->sq_length(o);
            // 确保长度是非负的
            assert(_Py_CheckSlotResult(o, "__len__", len >= 0));
            return len;
        }
    
        return PyMapping_Size(o);
    }
    
    该函数主要是获取了指向对象头的指针,如果它的长度是合理的,就返回,如果对象不是序列对象(即没有定义sq_length方法),则会调用PyMapping_Size(o)来获取映射对象的大小。这个函数用于获取映射对象(例如字典)的键值对数量。
  2. 判断返回的结果,如果长度为非负,则转成long类型返回,否则报错并返回空。
  3. list为例,我们可以看到
     static PySequenceMethods list_as_sequence = {
         (lenfunc)list_length,                       /* sq_length */
         (binaryfunc)list_concat,                    /* sq_concat */
         (ssizeargfunc)list_repeat,                  /* sq_repeat */
         (ssizeargfunc)list_item,                    /* sq_item */
         0,                                          /* sq_slice */
         (ssizeobjargproc)list_ass_item,             /* sq_ass_item */
         0,                                          /* sq_ass_slice */
         (objobjproc)list_contains,                  /* sq_contains */
         (binaryfunc)list_inplace_concat,            /* sq_inplace_concat */
         (ssizeargfunc)list_inplace_repeat,          /* sq_inplace_repeat */
     };
    
    list的长度和list_length函数进行了关联,其定义如下
     static Py_ssize_t
     list_length(PyListObject *a)
     {
         return Py_SIZE(a);
     }
    
    Py_SIZE则返回了PyObject当中的ob_size

3 总结

从该__len__()方法,理解了为什么内建的方法可以做到高效,不仅是逻辑简单,更因为直接是C语言,效率更高。此外,也学习了如何查询Python内建函数对应的C语言实现,帮助我们理解和感叹Python语言设计的细节,对于其他函数可使用同样的方式学习了解。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

The Daylight

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值