使用可选参数从 Python 中的字典列表中分离出来

Separating from a List of Dictionaries in Python with optional parameters

我正在尝试匹配一长串 Python 词典。我正在寻找的是根据字典键的值将此列表中的字典附加到新列表中。我所拥有的一个例子是:

1000 多个词典的列表结构如下:

{'regions': ['north','south'],
'age':35,
'name':'john',
'cars':['ford','kia']}

我想使用几乎所有键对该列表进行排序和匹配,并将匹配的键附加到新列表。有时我可能只使用 age 进行搜索,而其他时候我将使用 regionsname 进行搜索,一直到使用 agename, regions, & cars 因为要搜索的所有参数都是可选的。

我目前使用for循环对其进行排序,但随着我添加越来越多的可选参数,它变得越来越慢和复杂。有没有更简单的方法来陪伴我正在做的事情?

用户发送的示例是:

regions: north
age: 10

它会 return 一个包含 north 作为 region10 作为age

我编写了代码来执行此操作

test = [
    {
        'regions': ['south'],
        'age':35,
        'name':'john',
        'cars':['ford']
    },
    {
        'regions': ['north'],
        'age':15,
        'name':'michael',
        'cars':['kia']
    },
    {
        'regions': ['north','south'],
        'age':20,
        'name':'terry',
        'cars':['ford','kia']
    },
    {
        'regions': ['East','south'],
        'age':35,
        'name':'user',
        'cars':['other','kia']
    },
    {
        'regions': ['East','south'],
        'age':75,
        'name':'john',
        'cars':['other']
    }
]



def Finder(inputs: list, regions: list = None, age: int = None, name: str = None, cars: list = None) -> list:
    output = []
    for input in inputs:
        valid = True

        if regions != None and valid: valid = all(i in input["regions"] for i in regions)
        if age != None and valid: valid = input["age"] == age
        if name != None and valid: valid = input["name"] == name
        if cars != None and valid: valid = all(i in input["cars"] for i in cars)

        if valid:
            output.append(input)

    return output

print(Finder(test))
print(Finder(test, age = 25))
print(Finder(test, regions = ["East"]))
print(Finder(test, cars = ["ford","kia"]))
print(Finder(test, name = "john", regions = ["south"]))

这个函数只是检查所有的参数和检查输入是否有效,他把所有有效的输入放在一个输出列表中

我认为这个非常开放,特别是因为您建议您希望它在添加更多键等时可扩展,并且没有真正讨论您的操作要求。但这里有一些想法:

第三方模块

这些词典是否会嵌套更多?或者它会一直是 'key -> value' 还是 'key -> [list, of, values]'?

如果你能接受一个大块的依赖,你可能会考虑像 pandas 这样的东西,我们通常认为它代表 tables,但它肯定可以在某种程度上管理嵌套。

例如:

from functools import partial
from pandas import DataFrame
from typing import Dict


def matcher(comparator, target=None) -> bool:
    """
    matcher

    Checks whether a value matches or contains a target
    (won't match if the target is a substring of the value)

    """
    if target == comparator:  # simple case, return True immediately
        return True
    if isinstance(comparator, str):
        return False  # doesn't match exactly and string => no match
    try:  # handle looking in collections
        return target in comparator
    except TypeError:  # if it fails, you know there's no match
        return False


def search(data: DataFrame, query: Dict) -> DataFrame:
    """
    search

    Pass in a DataFrame and a query in the form of a dictionary
    of keys and values to match, for example:
        {"age": 42, "regions": "north", "cars": "ford"}

    Returns a matching subset of the data

    """
    # each element of the resulting list is a boolean series
    # corresponding to a dictionary key
    masks = [
        data[key].map(partial(matcher, target=value)) for key, value in query.items()
    ]

    # collapse the masks down to a single boolean series indicating
    # whether ALL conditions are met for each record
    mask = pd.concat(masks, axis="columns").all(axis="columns")

    return data.loc[mask]


if __name__ == "__main__":

    data = DataFrame(your_big_list)

    query = {"age": 35, "regions": "north", "cars": "ford"}

    results = search(data, query)

    list_results = results.to_dict(orient="records")

此处 list_results 会将过滤后的数据恢复为原始格式,如果这对您很重要的话。

我发现matcher这个功能要出奇的复杂,我一直在想edge-cases(比如:我们需要支持在集合中搜索,但是in也可以找到子字符串,这不是我们想要的......当然,除非它是!)。

但至少所有逻辑都被隔离在那里。您可以为其编写一系列单元测试,如果您将来扩展架构,则可以相应地更改函数并检查测试是否仍然通过。

search 函数纯粹是为了让 pandas 做你想用 matcher 做的事。

match case

在 Python 3.10 中,新的 match case 语句可能允许您非常干净地封装匹配逻辑。

性能

这里的一个非常基本的问题是,如果您关心性能(而且我觉得这对您来说是次要的可维护性)那么

  1. 数据越大,速度越慢
  2. python已经不算快了,一般来说

您可以通过为您的数据建立某种索引来改进。然而,归根结底,使用专业工具总是会更可靠。这将是 某种 数据库。

具体细节将取决于您的要求。例如这些数据会变得非常非结构化吗?是否有任何字段将成为需要在 Elasticsearch/Solr 之类的内容中正确索引的文本?

您可以在短期内使用 Python 实施的真正 light-touch 解决方案是

  • 将该数据放入 SQLite
  • 搜索靠SQL

我建议 SQLite 因为它运行 out-of-the-box 并且只在一个本地文件中:

from sqlalchemy import create_engine

engine = create_engine("sqlite:///mydb.sql")

# ... that's it, we can now connect to a SQLite DB/the 'mydb.sql' file that will be created

...但缺点是不支持array-like数据。您的选择是:

  1. 改用 postgreSQL 并利用 运行 火力更大的数据库
  2. 标准化这些数据

我认为选项 2 不会太难。类似于:

REGIONS
id | name
----------
1  | north
2  | south
3  | east
4  | west

CUSTOMERS
id | age | name
---------------
...

REGION_LINKS
customer_id | region_id
-----------------------
     1      |     1
     1      |     2

我把主要数据称为table'customers'但是你没有提到这些数据真正代表什么,所以更多的是举例。

然后可以使用 sqlalchemy 的 ORM 功能构建和执行您的 SQL 查询。