如何戒除对 Python 词典的依赖

How to cut my addiction to Python dictionaries

所以,我已经编写了一个 30k 行的大型程序一年了。它基本上从多个来源收集非规范化和非标准化数据,并在对来源进行标准化后匹配所有内容。

我用有序的字典写了大部分东西。这使我能够使列保持有序、命名和可变,这使得处理更容易,因为在整个代码混乱中值可以是 assigned/fixed。

但是,我目前 运行 内存不足,无法使用所有这些词典。从那以后我了解到切换到 namedtuples 会解决这个问题,唯一的问题是它们不是可变的,因此在进行转换时会出现一个问题。

我相信我可以使用 class 来消除不可变性,但 RAM 节省是否会相同?另一种选择是使用 namedtuples 并在每次需要更改值时将它们重新分配给新的 namedtouples(即 NewTup=Tup(oldTup.odj1, oldTup.odj2, "something new")。但我想我需要一个明确的后记或 space 销毁旧版本的方法可能再次成为问题。

最重要的是我的输入文件在磁盘上大约有 6GB(大量数据)。我被迫在具有 16GB RAM 和 4GB 交换空间的服务器上处理这些数据。我最初用字典对这些不同 I/O 数据集的所有行进行编程,这占用了太多 RAM...但是可变性质和命名引用对加快开发有很大帮助,如何我减少了对字典的添加,这样我就可以利用其他对象的成本节省,而无需重写整个应用程序来实现元组的不可变特性。

示例代码:

    for tan_programs_row in f_tan_programs:
    #stats not included due to urgent need
    tan_id = tan_programs_row["Computer ID"].strip() #The Tanium ID by which to reference all other tanium files (i.e. primary key)
    if("NO RESULT" not in tan_id.upper()):
        tan_programs_name = tan_programs_row["Name"].strip() #The Program Name
        tan_programs_publisher = tan_programs_row["Publisher"].strip() #The Program Vendor
        tan_programs_version = tan_programs_row["Version"].strip() #The Program Vendor

        try:
            unnorm_tan_dict[tan_id] #test the key, if non-existent go to exception
        except KeyError:
            #form the item since it doesn't exist yet
            unnorm_tan_dict[tan_id] = {
                "Tanium ID": tan_id,
                "Computer Name": "INDETERMINATE",
                "Operating System": "INDETERMINATE",
                "Operating System Build Number": "INDETERMINATE",
                "Service Pack": "INDETERMINATE",
                "Country Code": "INDETERMINATE",
                "Manufacturer": "INDETERMINATE",
                "Model": "INDETERMINATE",
                "Serial": "INDETERMINATE"
            }
        unnorm_tan_prog_list.append(rows.TanRawProg._make([tan_id, tan_programs_name, tan_programs_publisher, tan_programs_version]))

for tan_processes_row in f_tan_processes:
    #stats not included due to urgent need
    tan_id = tan_processes_row["Computer ID"].strip() #The Tanium ID by which to reference all other tanium files (i.e. primary key)
    if("NO RESULT" not in tan_id.upper()):
        tan_process_name = tan_processes_row["Running Processes"].strip() #The Program Name
        try:
            unnorm_tan_dict[tan_id] #test the key, if non-existent go to exception
        except KeyError:
            #form the item since it doesn't exist yet
            unnorm_tan_dict[tan_id] = {
                "Tanium ID": tan_id,
                "Computer Name": "INDETERMINATE",
                "Operating System": "INDETERMINATE",
                "Operating System Build Number": "INDETERMINATE",
                "Service Pack": "INDETERMINATE",
                "Country Code": "INDETERMINATE",
                "Manufacturer": "INDETERMINATE",
                "Model": "INDETERMINATE",
                "Serial": "INDETERMINATE"
            }
        unnorm_tan_proc_list.append(rows.TanRawProc._make([tan_id, tan_process_name]))

*后来这些值经常通过引入其他数据集而改变。

只需编写自己的 class,并使用 __slots__ 将内存占用降至最低:

class UnnormTan(object):
    __slots__ = ('tan_id', 'computer_name', ...)
    def __init__(self, tan_id, computer_name="INDETERMINATE", ...):
        self.tan_id = tan_id
        self.computer_name = computer_name
        # ...

可能 可能有点冗长,如果您需要将它们用作字典键,您将需要输入更多内容。

有一个项目可以让创建这样的 classes 更容易:attrs:

from attrs import attrs, attrib

@attrs(slots=True)
class UnnormTan(object):
    tan_id = attrib()
    computer_name = attrib(default="INDETERMINATE")
    # ...
使用 attrs 库创建的

类 会自动处理适当的相等性测试、表示和可散列性。

此类对象是 Python 可以提供的最有效的内存表示形式。如果这还不够(很可能是它还不够),您需要考虑将数据卸载到磁盘。最简单的方法是使用 SQL 数据库,就像捆绑的 sqlite3 SQLite library 一样。即使您使用 :memory: 临时数据库,该数据库也会根据需要通过将页面换出到磁盘来管理您的内存负载。

在我看来,您的主要问题是您试图完全在内存中创建数据库。您应该使用像 MySQL 或 PostgreSQL 这样的实际数据库。您可以使用不错的 ORM,例如 peewee 或 Django ORM 来与数据库交互。

另一方面,如果您无法处理整个数据,则可以将数据拆分成您可以处理的部分。

模块“TinyDB”(http://tinydb.readthedocs.io/en/latest/) 可以帮助您继续使用字典,并且不会 运行 内存不足。