As you can see I am using a counter variable to iterate through my JSON file and it's a dictionary that has a list of dictionaries. Once I find the key normalHours I divide it and add it to my dictionary hours_per_week. Then I update the dictionary it is associated with. Is it better to use for i in range instead of the counter? Anything else I can do to clean up or speed up the code?
Here is my json file, I deleted most of the key pairs for easier readability.
{ "employeeData": [ { "normalHours": 80, "givenName": "ROBERTO", }, { "normalHours": 80, "givenName": "HEATHER", }, { "normalHours": 80, "givenName": "PAUL", } ] }
def add_hours_per_week(json_file):
"""
Add new dictionary value hoursPerWeek to our JSON data
"""
# Create new dictionary hours_per_week.
hours_per_week = {}
hours_divide_by_2 = 0
normal_hours_counter = 0
try:
for key, values in json_file.items():
if str(key) == "employeeData":
# JSON is in a list of dictionaries
for item in values:
# If normalHours field is Null, "", or 0 add an empty field
if not item["normalHours"] or item["normalHours"] == 0:
hours_per_week["hoursPerWeek"] = ""
else:
# Divide normalHours by 2 rounded to 2 decimals.
hours_divide_by_2 = round(float(item["normalHours"]) / 2, 2)
hours_per_week["hoursPerWeek"] = hours_divide_by_2
json_file['employeeData'][normal_hours_counter].update(hours_per_week)
normal_hours_counter += 1
print("%d employee updated with hoursPerWeek" % normal_hours_counter if normal_hours_counter <= 1 else
"%d employees updated with hoursPerWeek" % normal_hours_counter)
return json_file
except Exception as e:
print("Exception in add_hours_per_week: ", e)
1 Answer 1
This code exhibits a collection of anti-patterns typically seen in Python that together are making your life more difficult:
- Dictionary lasagna, having non-structured data used directly from the result of a JSON parse
- In-place mutation
- Exception swallowing
- Infectious nullity
I don't see any value in your employees updated
print. At the absolute least, this should be converted to a proper logging call, but I would sooner drop it entirely.
This code needs to be significantly re-thought, along with any code you haven't shown that depends on such patterns. Consider instead:
- Define a simple
dataclass
orNamedTuple
representing eachEmployee
- Write a simple property method for
hours_per_week
- Do not
round()
in your business logic, only upon presentation - Explicit casting to a
float
should not be needed
Suggested
from typing import Any, Iterable, Iterator, NamedTuple, Optional
class Employee(NamedTuple):
given_name: str
normal_hours: Optional[float]
@classmethod
def from_dict(cls, data: Iterable[dict[str, Any]]) -> Iterator['Employee']:
for employee in data:
yield cls(
given_name=employee['givenName'],
normal_hours=employee.get('normalHours'),
)
@property
def hours_per_week(self) -> Optional[float]:
return self.normal_hours and self.normal_hours / 2
def __str__(self) -> str:
if self.normal_hours:
return (
f'{self.given_name}:'
f' {self.normal_hours} hours,'
f' {self.hours_per_week:.2f} hours per week'
)
return self.given_name
def main() -> None:
json_content = {
'employeeData': [
{'normalHours': 80, 'givenName': 'ROBERTO'},
{'normalHours': 80, 'givenName': 'HEATHER'},
{'normalHours': 80, 'givenName': 'PAUL'},
{'normalHours': None, 'givenName': 'JULIO'}
]
}
employees = Employee.from_dict(json_content['employeeData'])
for employee in employees:
print(employee)
if __name__ == '__main__':
main()
Output
ROBERTO: 80 hours, 40.00 hours per week
HEATHER: 80 hours, 40.00 hours per week
PAUL: 80 hours, 40.00 hours per week
JULIO
Python 2
Similar, just worse.
class Employee:
def __init__(self, given_name, normal_hours):
self.given_name = given_name
self.normal_hours = normal_hours
@classmethod
def from_dict(cls, data):
for employee in data:
yield cls(
given_name=employee['givenName'],
normal_hours=employee.get('normalHours'),
)
@property
def hours_per_week(self):
return self.normal_hours and self.normal_hours / 2
def __str__(self):
if self.normal_hours:
return '%s: %f hours, %.2f hours per week' % (
self.given_name,
self.normal_hours,
self.hours_per_week,
)
return self.given_name
def main():
json_content = {
'employeeData': [
{'normalHours': 80, 'givenName': 'ROBERTO'},
{'normalHours': 80, 'givenName': 'HEATHER'},
{'normalHours': 80, 'givenName': 'PAUL'},
{'normalHours': None, 'givenName': 'JULIO'}
]
}
employees = Employee.from_dict(json_content['employeeData'])
for employee in employees:
print(employee)
if __name__ == '__main__':
main()
-
\$\begingroup\$ I'm using Python 2, in def str how would the format work? \$\endgroup\$JThao– JThao2022年06月09日 15:40:05 +00:00Commented Jun 9, 2022 at 15:40
-
2\$\begingroup\$ @JThao don't use Python 2. \$\endgroup\$Reinderien– Reinderien2022年06月09日 15:43:21 +00:00Commented Jun 9, 2022 at 15:43
-
\$\begingroup\$ I get that a lot but I don't have a choice in this matter :( \$\endgroup\$JThao– JThao2022年06月09日 15:45:01 +00:00Commented Jun 9, 2022 at 15:45
-
1\$\begingroup\$ @JThao Edited; minor changes necessary. \$\endgroup\$Reinderien– Reinderien2022年06月09日 20:38:23 +00:00Commented Jun 9, 2022 at 20:38
-
1\$\begingroup\$ First, if your class has 50 more fields/attributes, it's possible that that's needed and appropriate, but also possible that that's a data schema misrepresentation and/or a normalisation error. Impossible to say without seeing it. \$\endgroup\$Reinderien– Reinderien2022年06月10日 14:04:18 +00:00Commented Jun 10, 2022 at 14:04
except
block. \$\endgroup\$