这是对此问题的后续问题:
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是否存在于dict的internal_ids列表中?
我尝试用employee.values()替换employee.keys(),但是没有用。此外,我希望提高效率,不确定是否有办法获取值的子集,特别是员工的所有条目[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)。我正在寻找一种更好的方法来构建/存储Employee / Employees,以及(2)我在使用lookup_supervisor()时遇到了问题。\
我应该创建一个Employee类,并将它们嵌套在Employees中吗?
我是否应该使用tidy_data()执行我正在做的事情,并在dict项目的for循环上调用clean_phone_number()和lookup_supervisor()? Urgh。 混淆
答案 0 :(得分:2)
您可能需要进行一些迭代才能获取数据。我假设你不想要一个可以过时的额外dict,所以试图存储键入内部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
你绝对可以为每个员工制作一个单独的对象,但是看看你如何使用数据以及你需要什么,我猜它不会有那么多的回报。
答案 1 :(得分:1)
我的蟒蛇技能很差,所以我太无知了,无法在任何合理的时间内写出我的想法。但我确实知道如何进行OO分解。
为什么Employees
班级要完成所有工作?您的整体Employees类有几种类型的东西:
我建议您创建一个类来处理列出的每个任务组。
定义Employee
类以保持跟踪或员工数据并处理字段处理/整理任务。
使用Employees
类作为员工对象的容器。它可以处理跟踪员工主管等任务。
定义一个虚拟基类EmployeeLoader来定义一个接口(load,store,??)。然后实现CSV文件序列化的子类。 (虚拟基类是可选的 - 我不确定Python如何处理虚拟类,所以这甚至没有意义。)
所以:
EmployeeCSVLoader
的实例。Employees
对象并解析该文件。为什么这个设计值得付出努力?
让事情变得更容易理解。较小的,以任务为中心的对象更容易为。
创建干净,一致的API如果您发现需要XML序列化格式,则添加新格式变得微不足道。对虚拟加载器类进行子类化以处理XML解析/生成。现在,您可以在CSV和XML格式之间无缝切换。
总之,使用对象来简化和构建数据。将常见数据和行为分成不同的类。让每个班级都专注于一种能力。如果您的类是集合,访问器,工厂,厨房接收器,则API永远不可用:它将太大并且加载了不同的方法组。但是,如果您的课程保持主题,那么它们将易于测试,维护,使用,重用和扩展。