代码之家  ›  专栏  ›  技术社区  ›  Lokesh Agrawal

python-为什么不总是缓存所有不变的对象?

  •  1
  • Lokesh Agrawal  · 技术社区  · 7 年前

    我不确定下面代码的python对象模型到底发生了什么。

    您可以从此下载ctabus.csv文件的数据 link

    import csv
    
    def read_as_dicts(filename):
        records = []
        with open(filename) as f:
            rows = csv.reader(f)
            headers = next(rows)
    
            for row in rows:
                route = row[0]
                date = row[1]
                daytype = row[2]
                rides = int(row[3])
                records.append({
                        'route': route,
                        'date': date,
                        'daytype': daytype,
                        'rides': rides})
    
        return records
    
    # read data from csv
    rows = read_as_dicts('ctabus.csv')
    print(len(rows)) #736461
    
    # record route ids (object ids)
    route_ids = set()
    for row in rows:
        route_ids.add(id(row['route']))
    
    print(len(route_ids)) #690072
    
    # unique_routes
    unique_routes = set()
    for row in rows:
        unique_routes.add(row['route'])
    
    print(len(unique_routes)) #185
    

    当我呼唤 print(len(route_ids)) 资讯科技印刷 "690072" . 为什么python最终创建了这些多个对象?

    我估计这个数字是185或736461。因为,当我计算集合中的唯一路线时,集合的长度为185。736461因为,这是csv文件中记录的总数。

    这个奇怪的数字“690072”是什么?

    我试图理解为什么这种部分缓存?为什么python不能执行像下面这样的完全缓存。

    import csv
    
    route_cache = {}
    
    #some hack to cache
    def cached_route(routename):
        if routename not in route_cache:
            route_cache[routename] = routename
        return route_cache[routename]
    
    def read_as_dicts(filename):
        records = []
        with open(filename) as f:
            rows = csv.reader(f)
            headers = next(rows)
    
            for row in rows:
                row[0] = cached_route(row[0]) #cache trick
                route = row[0]
                date = row[1]
                daytype = row[2]
                rides = int(row[3])
                records.append({
                        'route': route,
                        'date': date,
                        'daytype': daytype,
                        'rides': rides})
    
        return records
    
    # read data from csv
    rows = read_as_dicts('ctabus.csv')
    print(len(rows)) #736461
    
    # unique_routes
    unique_routes = set()
    for row in rows:
        unique_routes.add(row['route'])
    
    print(len(unique_routes)) #185
    
    # record route ids (object ids)
    route_ids = set()
    for row in rows:
        route_ids.add(id(row['route']))
    
    print(len(route_ids)) #185
    
    2 回复  |  直到 7 年前
        1
  •  3
  •   ead    7 年前

    文件中的典型记录如下:

    rows[0]
    {'route': '3', 'date': '01/01/2001', 'daytype': 'U', 'rides': 7354}
    

    这意味着大多数不变的对象都是字符串,只有 'rides' -值为整数。

    对于小整数( -5...255 )python3保存 an integer pool -所以这些小整数就像是被缓存(只要 PyLong_FromLong 使用的是一氧化碳)。

    这些规则对于字符串来说更为复杂——正如@timgeb所指出的,它们是被实习生使用的。有 a greate article about interning 即使是关于蟒蛇2.7-但从那时起没有太大变化。简而言之,最重要的规则是:

    1. 所有长度的字符串 0 1 被拘留。
    2. 如果包含多个字符的Sting是可以在标识符中使用的字符的组成部分,并且是在编译时直接创建的,或者是通过窥视孔优化创建的(但在第二种情况下,仅当结果不超过20个字符时)。

    以上所有内容都是实现细节,但考虑到这些细节,我们得到以下 row[0] 以上:

    1. 'route', 'date', 'daytype', 'rides' 都是实习的,因为它们是在函数的编译时创建的 read_as_dicts 而且没有“奇怪”的字符。
    2. '3' 'W' 因为它们的长度只有 .
    3. 01/01/2001 没有被拘留,因为它比 ,在运行时创建,但由于它具有字符而无论如何都不符合条件 / 在里面。
    4. 7354 不是来自小整数池,因为太大。但其他条目可能来自这个池。

    这是对当前行为的解释,只有一些对象被“缓存”。

    但是为什么python不缓存所有创建的字符串/整数呢?

    让我们从整数开始。为了能够在已经创建整数的情况下快速查找(比 O(n) )必须保留一个额外的查找数据结构,这需要额外的内存。但是,整数太多了,再次命中一个已经存在的整数的概率不是很高,因此查找数据结构的内存开销在大多数情况下不会得到偿还。

    因为字符串需要更多的内存,所以查找数据结构的相对(内存)成本并不高。但是实习一个1000个字符的字符串没有任何意义,因为随机创建的字符串拥有相同字符的概率几乎是 !

    另一方面,如果使用哈希字典作为查找结构,则哈希的计算将采用 o(n) ( n -字符数),这可能对大字符串无效。

    因此,python做了一个权衡,在大多数情况下都可以很好地工作——但在某些特殊情况下,它不可能是完美的。然而,对于那些特殊的场景,您可以使用 sys.intern() .


    注意:拥有相同的ID并不意味着成为同一个对象,如果两个对象的生存时间不重叠,-那么您在问题中的推理不是绝对可靠的,但是在这种特殊情况下,这并不重要。

        2
  •  4
  •   timgeb    7 年前

    中有736461个元素 rows .

    因此,您正在添加 id(row['route']) 到集合 route_ids 736461次。

    既然什么 id 回报保证为 独特的 在同时存在的对象中,我们希望 杂种狗 最后得到736461个项目,减去足够小的字符串数量以缓存两个。 'route' 两排钥匙 .

    在您的具体案例中,这个数字是736461-690072==46389。

    缓存小的不可变对象(字符串、整数)是一个不应该依赖的实现细节-但下面是一个演示:

    >>> s1 = 'test' # small string
    >>> s2 = 'test'
    >>> 
    >>> s1 is s2 # id(s1) == id(s2)
    True
    >>> s1 = 'test'*100 # 'large' string
    >>> s2 = 'test'*100
    >>> 
    >>> s1 is s2
    False
    

    最后,程序中可能存在语义错误。你想用它做什么? 身份证件 python对象的s?