len() 函数的底层原理并不是简单计数,而是通过不同对象的 __len__() 方法来实现的。
对于不同的数据类型,底层实现机制完全不同。
1. len() 的通用原理
# len() 实际调用的是对象的 __len__() 方法
s = "hello"
print(len(s)) # 等价于 s.__len__()
lst = [1, 2, 3]
print(len(lst)) # 等价于 lst.__len__()
# 验证
print(len("hello") == "hello".__len__()) # True
2. 字符串字数的底层原理
# Python 3 字符串:Unicode 码点序列
text = "Hello 你好 🎉"
print(len(text)) # 10
# 底层实现(概念性)
class PyUnicodeObject:
"""Python 字符串对象的简化表示"""
def __init__(self, value):
self.length = self._count_code_points(value)
self.data = value
def _count_code_points(self, s):
"""计算 Unicode 码点数量"""
count = 0
for ch in s:
count += 1
return count
def __len__(self):
return self.length
# 实际在 CPython 中的实现
"""
typedef struct {
PyObject_HEAD
Py_ssize_t length; // 字符串长度(码点数量)
Py_hash_t hash; // 哈希值
struct {
unsigned int interned:2;
unsigned int kind:3; // 存储类型(1字节/2字节/4字节)
unsigned int compact:1;
unsigned int ascii:1;
unsigned int ready:1;
} state;
wchar_t *wstr; // 宽字符指针
} PyUnicodeObject;
Py_ssize_t PyUnicode_GetLength(PyObject *unicode) {
return ((PyUnicodeObject*)unicode)->length;
}
"""
不同类型字符的统计
# Python 3 统计的是 Unicode 码点,不是字节
s1 = "a" # 1个字符,1个码点
s2 = "好" # 1个字符,1个码点
s3 = "🎉" # 1个字符,1个码点(但占用2个UTF-16代码单元)
s4 = "🇨🇳" # 1个字符(但由多个码点组成)
s5 = "café" # 4个字符,"é"是一个码点
print(len(s1), len(s2), len(s3), len(s5)) # 1 1 1 4
# 注意:组合字符的情况
s6 = "caf\u0065\u0301" # "cafe" + 重音符号
print(s6) # "café"(显示为一个字符)
print(len(s6)) # 5(实际上是5个码点)
print(list(s6)) # ['c', 'a', 'f', 'e', '\u0301']
3. 文本行数的底层原理
对于字符串中的行数
# 统计字符串中的行数
text = "line1\nline2\nline3"
lines = text.splitlines() # ['line1', 'line2', 'line3']
line_count = len(lines) # 3
# 底层:splitlines() 识别多种换行符
print("A\nB\r\nC\rD".splitlines()) # ['A', 'B', 'C', 'D']
对于文件的行数
# 方法1:使用 readlines()
with open('file.txt', 'r') as f:
lines = f.readlines() # 读取所有行到列表
line_count = len(lines) # 调用列表的 __len__()
# 底层:readlines() 内部实现(简化版)
class TextIOWrapper:
def readlines(self, hint=-1):
lines = []
while True:
line = self.readline()
if not line:
break
lines.append(line)
return lines
高效统计大文件行数
# 方法2:逐行计数(内存友好)
def count_lines(filename):
count = 0
with open(filename, 'r', buffering=1024*1024) as f: # 1MB缓冲
# 使用迭代器,不存储所有行
for _ in f:
count += 1
return count
# 方法3:使用缓冲区(最快的方法之一)
def fast_line_count(filename):
"""最快的行数统计方法之一"""
count = 0
buffer_size = 1024 * 1024 # 1MB
with open(filename, 'rb') as f: # 二进制模式更快
buffer = f.read(buffer_size)
while buffer:
count += buffer.count(b'\n') # 统计换行符
buffer = f.read(buffer_size)
return count
# 测试性能
import timeit
filename = 'large_file.txt'
print("方法1时间:", timeit.timeit(lambda: len(open(filename).readlines()), number=10))
print("方法2时间:", timeit.timeit(lambda: sum(1 for _ in open(filename)), number=10))
print("方法3时间:", timeit.timeit(lambda: fast_line_count(filename), number=10))
4. 不同数据类型的 __len__() 实现
# 1. 列表:记录元素个数
class List:
def __init__(self):
self.ob_item = [] # 元素数组
self.allocated = 0 # 已分配空间
self.size = 0 # 实际元素数量
def append(self, item):
# 添加元素逻辑...
self.size += 1
def __len__(self):
return self.size # 直接返回计数器
# 2. 字典:存储键值对数量
class Dict:
def __init__(self):
self.ma_used = 0 # 已使用的条目数
# ... 其他字典结构
def __len__(self):
return self.ma_used
# 3. 集合:类似字典
class Set:
def __len__(self):
return self.used_count
5. Python 源码中的实际实现
/* CPython 中 len() 的实际实现 (Objects/abstract.c) */
static PyObject *
builtin_len(PyObject *module, PyObject *obj)
{
Py_ssize_t res;
res = PyObject_Size(obj); // 获取对象大小
if (res < 0) {
if (PyErr_Occurred()) {
return NULL; // 出错
}
/* 如果对象没有 __len__,抛出 TypeError */
PyErr_SetString(PyExc_TypeError,
"object of type '%.200s' has no len()",
Py_TYPE(obj)->tp_name);
return NULL;
}
return PyLong_FromSsize_t(res); // 转换为 Python 整数
}
/* PyObject_Size 的实现 */
Py_ssize_t
PyObject_Size(PyObject *o)
{
PySequenceMethods *m;
if (o == NULL) {
return -1;
}
m = Py_TYPE(o)->tp_as_sequence;
if (m && m->sq_length) {
return m->sq_length(o); // 调用序列的 sq_length
}
// 尝试调用对象的 __len__ 方法
return PyObject_Length(o);
}
6. 自定义类的 __len__() 实现
class TextDocument:
"""自定义文本文档类"""
def __init__(self, content):
self.content = content
self._line_count = None
self._char_count = None
def _count_chars(self):
"""统计字符数"""
count = 0
for char in self.content:
count += 1
self._char_count = count
return count
def _count_lines(self):
"""统计行数"""
if not self.content:
self._line_count = 0
else:
# 统计换行符,考虑最后一行可能没有换行符
self._line_count = self.content.count('\n')
if not self.content.endswith('\n'):
self._line_count += 1
return self._line_count
def __len__(self):
"""返回字符数(类似字符串)"""
if self._char_count is None:
self._count_chars()
return self._char_count
def line_count(self):
"""返回行数"""
if self._line_count is None:
self._count_lines()
return self._line_count
def word_count(self):
"""统计单词数"""
import re
words = re.findall(r'\b\w+\b', self.content)
return len(words)
# 使用示例
doc = TextDocument("Hello world!\nThis is a test.\nPython is awesome.")
print(f"字符数: {len(doc)}") # 调用 __len__(),返回 55
print(f"行数: {doc.line_count()}") # 3
print(f"单词数: {doc.word_count()}") # 9
7. 性能对比和注意事项
import sys
# 1. 不同字符串长度的内存占用
short = "a"
long_str = "a" * 1000
print(f"短字符串长度: {len(short)}") # 1
print(f"长字符串长度: {len(long_str)}") # 1000
print(f"短字符串内存: {sys.getsizeof(short)} 字节") # ~50字节
print(f"长字符串内存: {sys.getsizeof(long_str)} 字节") # ~1049字节
# 2. 大数据量的性能考虑
class LazyTextAnalyzer:
"""惰性计算的文本分析器"""
def __init__(self, filename):
self.filename = filename
self._line_count = None
self._char_count = None
@property
def line_count(self):
if self._line_count is None:
# 惰性计算
self._line_count = self._calculate_line_count()
return self._line_count
def _calculate_line_count(self):
count = 0
with open(self.filename, 'r') as f:
for _ in f:
count += 1
return count
@property
def char_count(self):
if self._char_count is None:
self._char_count = self._calculate_char_count()
return self._char_count
def _calculate_char_count(self):
total = 0
with open(self.filename, 'r') as f:
for line in f:
total += len(line)
return total
# 使用惰性计算
analyzer = LazyTextAnalyzer('large_file.txt')
print(f"行数: {analyzer.line_count}") # 第一次调用时才计算
print(f"字符数: {analyzer.char_count}") # 第一次调用时才计算
敲黑板!!@!!!(十一剑的CS_DN博客)
len() 的底层原理:
- 通用机制:调用对象的
__len__()方法 - 时间复杂度:通常是 O(1),因为长度被缓存
- 数据类型差异:
- 字符串:统计 Unicode 码点数量
- 列表/元组:返回元素个数
- 字典/集合:返回键值对数量
- 文件行数:实际是列表长度或计数循环
文本行数的真相:
- 并没有直接
len(文件)的方法 - 需要先将文件内容转换为列表(如
readlines())或迭代计数 - 对于大文件,推荐使用迭代器方式避免内存问题
字符串字数的真相:
- 统计的是 Unicode 码点,不是字节
- 对于组合字符、表情符号等特殊字符需要特别注意
- 如果需要字节数,使用
len(s.encode('utf-8'))
416

被折叠的 条评论
为什么被折叠?



