【问题标题】:Checking for membership inside nested dict检查嵌套字典中的成员资格
【发布时间】:2010-05-25 03:45:15
【问题描述】:

这是对这个问题的后续问题:

Python DictReader - Skipping rows with missing columns?

原来我很傻,并且使用了错误的 ID 字段。

顺便说一句,我在这里使用的是 Python 3.x。

我有一个员工字典,由字符串“directory_id”索引。每个值都是一个带有员工属性(电话号码、姓氏等)的嵌套字典。这些值之一是辅助 ID,例如“internal_id”,另一个是他们的经理,称为“manager_internal_id”。 “internal_id”字段是非强制性的,并不是每个员工都有一个。

{'6443410501': {'manager_internal_id': '989634', 'givenName': 'Mary', 'phoneNumber': '+65 3434 3434', 'sn': 'Jones', 'internal_id': '434214'}
'8117062158': {'manager_internal_id': '180682', 'givenName': 'John', 'phoneNumber': '+65 3434 3434', 'sn': 'Ashmore', 'internal_id': ''}
'9227629067': {'manager_internal_id': '347394', 'givenName': 'Wright', 'phoneNumber': '+65 3434 3434', 'sn': 'Earl', 'internal_id': '257839'}
'1724696976': {'manager_internal_id': '907239', 'givenName': 'Jane', 'phoneNumber': '+65 3434 3434', 'sn': 'Bronte', 'internal_id': '629067'}

}

(我已经稍微简化了这些字段,既是为了更容易阅读,也是出于隐私/合规的原因)。

这里的问题是我们按每个员工的 directory_id 索引(键),但是当我们查找他们的经理时,我们需要通过他们的“internal_id”来查找经理。

之前,当我们的 dict 使用 internal_id 作为键时,employee.keys() 是一个 internal_id 列表,我对此使用了成员资格检查。现在,我的 if 语句的最后一部分不起作用,因为 internal_ids 是 dict 值的一部分,而不是键本身。

def lookup_supervisor(manager_internal_id, employees):
    if manager_internal_id is not None and manager_internal_id != "" and manager_internal_id in employees.keys():
        return (employees[manager_internal_id]['mail'], employees[manager_internal_id]['givenName'], employees[manager_internal_id]['sn'])
    else:
        return ('Supervisor Not Found', 'Supervisor Not Found', 'Supervisor Not Found')

所以第一个问题是,如何修复 if 语句以检查 manager_internal_id 是否存在于字典的 internal_ids 列表中?

我尝试用employee.values() 替换employee.keys(),但没有奏效。另外,我希望效率更高一些,不确定是否有办法获取值的子集,特别是employees[directory_id]['internal_id'] 的所有条目。

希望有一些 Pythonic 的方式来执行此操作,而无需使用大量嵌套的 for/if 循环。

我的第二个问题是,我如何干净地返回所需的员工属性(邮件、名字、姓氏等)。我的 for 循环遍历每个员工,并调用 lookup_supervisor。我在这里感觉有点愚蠢/难倒。

def tidy_data(employees):
    for directory_id, data in employees.items():
        # We really shouldnt' be passing employees back and forth like this - hmm, classes?
        data['SupervisorEmail'], data['SupervisorFirstName'], data['SupervisorSurname'] = lookup_supervisor(data['manager_internal_id'], employees)

我应该重新设计我的数据结构吗?还是有别的办法?

编辑:我稍微调整了代码,见下文:

class Employees:

    def import_gd_dump(self, input_file="test.csv"):
        gd_extract = csv.DictReader(open(input_file), dialect='excel')
        self.employees = {row['directory_id']:row for row in gd_extract}

    def write_gd_formatted(self, output_file="gd_formatted.csv"):
        gd_output_fieldnames = ('internal_id', 'mail', 'givenName', 'sn', 'dbcostcenter', 'directory_id', 'manager_internal_id', 'PHFull', 'PHFull_message', 'SupervisorEmail', 'SupervisorFirstName', 'SupervisorSurname')
        try:
            gd_formatted = csv.DictWriter(open(output_file, 'w', newline=''), fieldnames=gd_output_fieldnames, extrasaction='ignore', dialect='excel')
        except IOError:
            print('Unable to open file, IO error (Is it locked?)')
            sys.exit(1)

        headers = {n:n for n in gd_output_fieldnames}
        gd_formatted.writerow(headers)
        for internal_id, data in self.employees.items():
            gd_formatted.writerow(data)

    def tidy_data(self):
        for directory_id, data in self.employees.items():
            data['PHFull'], data['PHFull_message'] = self.clean_phone_number(data['telephoneNumber'])
            data['SupervisorEmail'], data['SupervisorFirstName'], data['SupervisorSurname'] = self.lookup_supervisor(data['manager_internal_id'])

    def clean_phone_number(self, original_telephone_number):
        standard_format = re.compile(r'^\+(?P<intl_prefix>\d{2})\((?P<area_code>\d)\)(?P<local_first_half>\d{4})-(?P<local_second_half>\d{4})')
        extra_zero = re.compile(r'^\+(?P<intl_prefix>\d{2})\(0(?P<area_code>\d)\)(?P<local_first_half>\d{4})-(?P<local_second_half>\d{4})')
        missing_hyphen = re.compile(r'^\+(?P<intl_prefix>\d{2})\(0(?P<area_code>\d)\)(?P<local_first_half>\d{4})(?P<local_second_half>\d{4})')
        if standard_format.search(original_telephone_number):
            result = standard_format.search(original_telephone_number)
            return '0' + result.group('area_code') + result.group('local_first_half') + result.group('local_second_half'), ''
        elif extra_zero.search(original_telephone_number):
            result = extra_zero.search(original_telephone_number)
            return '0' + result.group('area_code') + result.group('local_first_half') + result.group('local_second_half'), 'Extra zero in area code - ask user to remediate. '
        elif missing_hyphen.search(original_telephone_number):
            result = missing_hyphen.search(original_telephone_number)
            return '0' + result.group('area_code') + result.group('local_first_half') + result.group('local_second_half'), 'Missing hyphen in local component - ask user to remediate. '
        else:
            return '', "Number didn't match format. Original text is: " + original_telephone_number    

    def lookup_supervisor(self, manager_internal_id):
        if manager_internal_id is not None and manager_internal_id != "":# and manager_internal_id in self.employees.values():
            return (employees[manager_internal_id]['mail'], employees[manager_internal_id]['givenName'], employees[manager_internal_id]['sn'])
        else:
            return ('Supervisor Not Found', 'Supervisor Not Found', 'Supervisor Not Found')

if __name__ == '__main__':
    our_employees = Employees()
    our_employees.import_gd_dump('test.csv')
    our_employees.tidy_data()
    our_employees.write_gd_formatted()

我猜是 (1)。我正在寻找一种更好的方法来构建/存储员工/员工,并且 (2) 我遇到了特别是 lookup_supervisor() 的问题。\

我应该创建一个员工类,并将它们嵌套在员工中吗?

我什至应该使用 tidy_data() 做我正在做的事情,并在 dict 项目的 for 循环中调用 clean_phone_number() 和 lookup_supervisor() 吗?呃。 困惑

【问题讨论】:

  • 所有经理都有内部ID吗?如果是这样,那么为什么不创建一个以内部 ID 为键的单独字典,但使用相同的数据呢?至于返回值的格式,如何复制员工dict,并将manager dict添加为那个字段?
  • 关于您的第一个问题不会遍历字典然后遍历值以搜索经理 ID(即具有嵌套循环)帮助?
  • 进行嵌套循环会产生 O(N^2) 复杂度,但会使用更少的内存。构建一个将 internal_id 映射到 directory_id 的字典将是 O(N),但会使用一点额外的内存。

标签: python dictionary


【解决方案1】:

您可能需要进行一些迭代来获取数据。我假设您不想要一个可能会过时的额外字典,因此尝试存储所有以内部 ID 为键的内容是不值得的。

试穿一下尺寸:

def lookup_supervisor(manager_internal_id, employees):
    if manager_internal_id is not None and manager_internal_id != "":
        manager_dir_ids = [dir_id for dir_id in employees if employees[dir_id].get('internal_id') == manager_internal_id]
        assert(len(manager_dir_ids) <= 1)
        if len(manager_dir_ids) == 1:
            return manager_dir_ids[0]
    return None

def tidy_data(employees):
    for emp_data in employees.values():
        manager_dir_id = lookup_supervisor(emp_data.get('manager_internal_id'), employees)
        for (field, sup_key) in [('Email', 'mail'), ('FirstName', 'givenName'), ('Surname', 'sn')]:
            emp_data['Supervisor'+field] = (employees[manager_dir_id][sup_key] if manager_dir_id is not None else 'Supervisor Not Found')

你绝对正确,类是传递employees 的答案。事实上,我建议不要将“主管”键存储在员工字典中,而是建议在需要时让主管字典保持新鲜,也许使用 get_supervisor_data 方法。

除了我已经提到的更改和对clean_phone_number 的一些调整之外,您的新 OO 版本看起来都很合理。

def clean_phone_number(self, original_telephone_number):
    phone_re = re.compile(r'^\+(?P<intl_prefix>\d{2})\((?P<extra_zero>0?)(?P<area_code>\d)\)(?P<local_first_half>\d{4})(?P<hyph>-?)(?P<local_second_half>\d{4})')
    result = phone_re.search(original_telephone_number)
    if result is None:
        return '', "Number didn't match format. Original text is: " + original_telephone_number
    msg = ''
    if result.group('extra_zero'):
        msg += 'Extra zero in area code - ask user to remediate. '
    if result.group('hyph'):    # Note: can have both errors at once
        msg += 'Missing hyphen in local component - ask user to remediate. '
    return '0' + result.group('area_code') + result.group('local_first_half') + result.group('local_second_half'), msg

您绝对可以为每个员工制作一个单独的对象,但看看您如何使用数据以及您需要从中获得什么,我猜它不会有那么多回报。

【讨论】:

  • @Mu Mind:我已经添加了更多上面代码的完整示例,现在在一个类中(尽管可能设计得很糟糕) - 我希望现在有一个更清洁的解决方案?感谢您的回答 - 另外,我不知道您可以在 Python 中的 if 子句中进行赋值(示例中的第 3 行)?
  • 是的,您绝对可以在 if 子句中进行赋值。在执行之后,它们甚至可以从 if 块外部访问。 Python 在范围界定上相当松散。
  • 有趣,一定要试试=)。顺便说一句,我在上面粘贴了一些清理过的代码,不确定这是否会影响您的答案?另外,给你点赞。谢谢。
【解决方案2】:

我的 python 技能很差,所以我太无知了,无法在任何合理的时间内写出我的想法。但我确实知道如何进行 OO 分解。

为什么Employees 类来做所有的工作?您的整体员工类有几种类型的事情:

  • 从文件中读取和写入数据 - 也就是序列化
  • 管理和访问个别员工的数据
  • 管理员工之间的关系。

我建议您创建一个类来处理列出的每个任务组。

定义一个Employee 类来跟踪员工数据并处理现场处理/整理任务。

使用Employees 类作为员工对象的容器。它可以处理诸如追踪员工主管之类的任务。

定义一个虚拟基类EmployeeLoader 来定义一个接口(load、store、??)。然后实现一个CSV文件序列化的子类。 (虚拟基类是可选的——我不确定 Python 是如何处理虚拟类的,所以这甚至可能没有意义。)

所以:

  • 创建一个EmployeeCSVLoader 的实例,并使用一个文件名。
  • 加载程序然后可以构建一个Employees 对象并解析文件。
  • 在读取每条记录时,将创建一个新的 Employee 对象并将其存储在 Employees 对象中。
  • 现在要求员工对象填充主管链接。
  • 遍历Employees 对象的员工集合,并要求每个人自行整理。
  • 最后,让序列化对象处理更新数据文件。

为什么这个设计值得付出努力?

它使事情更容易理解。更小的、以任务为中心的对象更容易为其创建干净、一致的 API。

如果您发现需要一种 XML 序列化格式,那么添加新格式就变得很简单了。子类化您的虚拟加载器类以处理 XML 解析/生成。现在您可以在 CSV 和 XML 格式之间无缝切换。

总之,使用对象来简化和结构化您的数据。将公共数据和行为划分为单独的类。让每个班级都专注于单一类型的能力。如果您的类是集合、访问器、工厂、厨房水槽,那么 API 将永远无法使用:它将太大并且加载了不同的方法组。但是,如果您的课程紧扣主题,它们将很容易测试、维护、使用、重用和扩展。

【讨论】:

  • @daotoad:你说得很好,我将 Employee 拆分为他们自己的单独班级。但是,我仍然不确定从 CSV 文件中读取其属性的最佳方式。 csv.DictReader 返回一个字典,以列名作为键。 python中有没有办法根据这些列名为每个员工创建实例属性?其次,我不确定 EmployeeLoader/虚拟类部分,不确定它在 Python 中是如何工作的——还有其他人知道吗?
  • 不确定进行类构建的最佳方法。仍在通过教程工作。但是您应该能够创建一个接受 dict 并使用它来设置员工类的属性的类方法。至于序列化,暂时不要担心基类,只需考虑它的接口即可。然后定义loadstore 方法。你最终会得到像employees = EmployeeCVSLoader.load('data_file.csv'); 这样的代码。 load 方法将调用 cvs 加载器来获取每个字典。将字典传递给new_from_dict 方法。
  • 对不起,我没有足够的知识给你好的代码。这让我很沮丧,因为我看到了确切的设计,但我还没有渲染它的技能水平。你想要一个 Perl 的例子吗?
  • 嗯,是的,一个 Perl 示例可能很好,它会给我一个很好的方向。我相信这里的其他人可以使它更像 Pythonic =)。支持你,顺便说一句。
最近更新 更多