У меня есть небольшой скрипт, который мы используем для чтения в CSV-файле, содержащем сотрудников, и выполняем некоторые основные манипуляции с этими данными.Python - Преобразование CSV в объекты - Code Code
Мы читаем данные (import_gd_dump) и создаем объект Employees
, содержащий список объектов Employee
(может быть, я должен подумать о лучшем соглашении об именах ... lol). Затем мы звоним clean_all_phone_numbers()
по номеру Employees
, который называет clean_phone_number()
по каждому Employee
, а также lookup_all_supervisors()
, на Employees
.
import csv
import re
import sys
#class CSVLoader:
# """Virtual class to assist with loading in CSV files."""
# def import_gd_dump(self, input_file='Gp Directory 20100331 original.csv'):
# gd_extract = csv.DictReader(open(input_file), dialect='excel')
# employees = []
# for row in gd_extract:
# curr_employee = Employee(row)
# employees.append(curr_employee)
# return employees
# #self.employees = {row['dbdirid']:row for row in gd_extract}
# Previously, this was inside a (virtual) class called "CSVLoader".
# However, according to here (http://tomayko.com/writings/the-static-method-thing) - the idiomatic way of doing this in Python is not with a class-function but with a module-level function
def import_gd_dump(input_file='Gp Directory 20100331 original.csv'):
"""Return a list ('employee') of dict objects, taken from a Group Directory CSV file."""
gd_extract = csv.DictReader(open(input_file), dialect='excel')
employees = []
for row in gd_extract:
employees.append(row)
return employees
def write_gd_formatted(employees_dict, output_file="gd_formatted.csv"):
"""Read in an Employees() object, and write out each Employee() inside this to a CSV file"""
gd_output_fieldnames = ('hrid', 'mail', 'givenName', 'sn', 'dbcostcenter', 'dbdirid', 'hrreportsto', '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 employee in employees_dict.employee_list:
# We're using the employee object's inbuilt __dict__ attribute - hmm, is this good practice?
gd_formatted.writerow(employee.__dict__)
class Employee:
"""An Employee in the system, with employee attributes (name, email, cost-centre etc.)"""
def __init__(self, employee_attributes):
"""We use the Employee constructor to convert a dictionary into instance attributes."""
for k, v in employee_attributes.items():
setattr(self, k, v)
def clean_phone_number(self):
"""Perform some rudimentary checks and corrections, to make sure numbers are in the right format.
Numbers should be in the form 0XYYYYYYYY, where X is the area code, and Y is the local number."""
if self.telephoneNumber is None or self.telephoneNumber == '':
return '', 'Missing phone number.'
else:
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(self.telephoneNumber):
result = standard_format.search(self.telephoneNumber)
return '0' + result.group('area_code') + result.group('local_first_half') + result.group('local_second_half'), ''
elif extra_zero.search(self.telephoneNumber):
result = extra_zero.search(self.telephoneNumber)
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(self.telephoneNumber):
result = missing_hyphen.search(self.telephoneNumber)
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 recognised format. Original text is: " + self.telephoneNumber
class Employees:
def __init__(self, import_list):
self.employee_list = []
for employee in import_list:
self.employee_list.append(Employee(employee))
def clean_all_phone_numbers(self):
for employee in self.employee_list:
#Should we just set this directly in Employee.clean_phone_number() instead?
employee.PHFull, employee.PHFull_message = employee.clean_phone_number()
# Hmm, the search is O(n^2) - there's probably a better way of doing this search?
def lookup_all_supervisors(self):
for employee in self.employee_list:
if employee.hrreportsto is not None and employee.hrreportsto != '':
for supervisor in self.employee_list:
if supervisor.hrid == employee.hrreportsto:
(employee.SupervisorEmail, employee.SupervisorFirstName, employee.SupervisorSurname) = supervisor.mail, supervisor.givenName, supervisor.sn
break
else:
(employee.SupervisorEmail, employee.SupervisorFirstName, employee.SupervisorSurname) = ('Supervisor not found.', 'Supervisor not found.', 'Supervisor not found.')
else:
(employee.SupervisorEmail, employee.SupervisorFirstName, employee.SupervisorSurname) = ('Supervisor not set.', 'Supervisor not set.', 'Supervisor not set.')
#Is thre a more pythonic way of doing this?
def print_employees(self):
for employee in self.employee_list:
print(employee.__dict__)
if __name__ == '__main__':
db_employees = Employees(import_gd_dump())
db_employees.clean_all_phone_numbers()
db_employees.lookup_all_supervisors()
#db_employees.print_employees()
write_gd_formatted(db_employees)
Во-первых, мой преамбула вопрос, вы можете увидеть что-нибудь по существу неправильно с вышеизложенным, либо из конструкции класса или Python точка-обзора? Является ли логика/дизайн звуком?
Во всяком случае, к специфике:
- Объект
Employees
имеет метод,clean_all_phone_numbers()
, который вызываетclean_phone_number()
на каждомEmployee
объекта внутри него. Это плохой дизайн? Если да, то почему? Также, как я называюlookup_all_supervisors()
плохой? - Первоначально, я обернул метод
clean_phone_number()
иlookup_supervisor()
в одной функции, с одним внутри него внутри. clean_phone_number - это O (n), я считаю, lookup_supervisor - это O (n^2) - нормально ли он разбивает его на две петли вроде этого? - В
clean_all_phone_numbers()
я зацикливаюсь на объектахEmployee
и задает их значения с использованием return/assign - должен ли я устанавливать это внутриclean_phone_number()
?
Есть также несколько вещей, которые меня сортируют из-за взлома, не уверен, что они плохие практики - например, print_employee()
и gd_formatted()
оба используют __dict__
, а конструктор для Employee
использует setattr()
для преобразования словаря в атрибуты экземпляра.
Я бы оценил любые мысли вообще. Если вы считаете, что вопросы слишком широкие, дайте мне знать, и я могу перепечатать их как несколько разделенных (я просто не хотел загрязнять доски несколькими подобными вопросами, и три вопроса более или менее довольно тесно связаны).
Приветствия, Виктор
Спасибо за быстрый ответ. Он будет запускаться каждую неделю или около того, входной файл немного изменится. Мы можем получить дельт, но есть проблемы с ними, и, похоже, проще просто переписать весь файл. Что касается пункта 2, что именно вы имели в виду здесь? Первоначально я использовал dicts (см. Http://stackoverflow.com/questions/2901872/python-checking-for-membership-inside-nested-dict), однако я перешел к классу дизайна. Можете ли вы добавить хэш-файл/dict в класс? Подкласс dict? Для точки 3, так что вы говорите из дизайна POV, я ничего не должен возвращать, но должен использовать clean_phone_number для установки? – victorhooi
ваш 'lookup_all_supervisors' использует вложенный цикл для поиска супервизора для каждого сотрудника. Вложенный цикл должен просто быть просмотром в словаре супервизоров, который вы можете создать при чтении сотрудников (один проход) или позднее (во втором проходе). Это приведёт O (n^2) до O (n) для назначения супервизоров. –
Ах, да и в отношении 3. именно это: ваше решение обновляет каждого сотрудника тем, что тот же самый сотрудник считает чистым номером телефона. Вместо этого просто скажите сотруднику очистить номер телефона friggin! Пусть сотрудник управляет своим собственным состоянием - внешние объекты не должны возиться с состоянием других объектов! –