#python #python-3.x
Вопрос:
Я пишу код для создания базового программного обеспечения для управления сотрудниками с графическим интерфейсом. Когда я загружаю образцы данных, я считаю, что они создаются как объекты класса ( Employee
), потому что при отладке я вижу, что все аргументы переданы и объект создан. ОДНАКО, когда я пытаюсь изменить их позже (например, статус «нанят»), он возвращает ошибку 'str' object has no attribute 'hired_status'
Employee
Класс:
"""Create Employee class so all employees can be Employee Objects"""
def __init__(self, fname='', lname='', phone='', email='', job='', interview_rating=0, hired_status='N'):
self.first = fname
self.last = lname
self.phone_number = phone
self.email_address = email
self.interview = interview_rating
self.__emp_job = job
self.hired_status = hired_status
@property
def first_name(self):
return self.__first
@first_name.setter
def first_name(self, fname):
if fname.isalpha() and len(fname) >= 1:
self.__first = fname.capitalize()
else:
self.__first = 'Unknown'
@property
def last_name(self):
return self.__last
@last_name.setter
def last_name(self, lname):
if lname.isalpha() and len(lname) >= 1:
self.__last = lname.capitalize()
else:
self.__last = 'Unknown'
@property
def email_address(self):
return self.__email_address
@email_address.setter
def email_address(self, email):
if len(email) >=1 and '@' in email:
self.__email_address = email
else:
self.__email_address = 'Unknown'
@property
def phone_number(self):
return self.__phone_number.format(str)
@phone_number.setter
def phone_number(self, phone):
if len(phone) >= 1:
self.__phone_number = phone
else:
self.__phone_number = 'Unknown'
@property
def job(self):
return self.__emp_job
@job.setter
def job(self, job):
self.__emp_job = job
@property
def hired_status(self):
return self.__hired_status
@hired_status.setter
def hired_status(self, hired):
self.__hired_status = hired
def __str__(self):
return f'{self.first_name},{self.last_name},{self.phone_number},{self.email_address},{self.job},'
f'{self.hired_status}'
Основной Класс:
def load_employees():
"""
Loads same employees from the text file.
- Creates an Employee object for each employee listed in the file.
- Retrieves comma-separated values from each line and assigns it to the corresponding arg in Employee()
- Adds the new employee object to the list of current employees.
- Displays the employees on the screen for the user.
"""
global first_name, last_name, phone, email, job, new_employee, current_employees, hired_status
current_employees = []
with open('employees.txt', 'r') as file:
for f in file:
first_name, last_name, phone, email, job, hired_status = f.split(',')
sample_employee = Employee(first_name, last_name, phone, email, job, hired_status)
sample_employee.first_name = first_name
sample_employee.last_name = last_name
sample_employee.phone_number = phone
sample_employee.email_address = email
sample_employee.job = job
sample_employee.hired_status = hired_status
current_employees.append(f)
emp_list.insert(END, f)
Пытаюсь нанять
def hire_candidate():
global first_name, last_name, phone, email, job, new_employee, current_employees, edit_mode, hired_status, emp_list
new_employee = emp_list.get(ANCHOR)
new_employee.hired_status = 'Y'
adding new employee with the GUI
def save_new_employee():
"""
When user clicks "Save Employee" the program will create or edit an Employee object.
- A new Employee will be added to the list of current employees and appear in the GUI.
- If editing, the Employee object will be updated with the new information.
- The user is prompted with a message saying they have added or updated an Employee.
- The Employee Management form will then clear all textboxes.
"""
global first_name, last_name, phone, email, job, new_employee, current_employees, edit_mode, hired_status
global applicant_first_tbx, applicant_last_tbx, applicant_phone_tbx, applicant_email_tbx, jobs_cbx
new_employee = None
new_employee = Employee(first_name, last_name, phone, email, job, hired_status)
new_employee.first_name = applicant_first_tbx.get()
new_employee.last_name = applicant_last_tbx.get()
new_employee.phone_number = applicant_phone_tbx.get()
new_employee.email_address = applicant_email_tbx.get()
new_employee.job = job
if edit_mode:
x = emp_list.curselection()
current_employees[edit_index] = x
emp_list.delete(x)
emp_list.insert(edit_index, new_employee)
current_employees.pop(edit_index)
current_employees.append(new_employee)
edit_mode = False
messagebox.showinfo('Employee Information Updated.', f'Information for {new_employee.first_name} '
f'{new_employee.last_name} has been updated.')
else:
current_employees.append(new_employee)
emp_list.insert(END, new_employee)
messagebox.showinfo('New Employee Added', f'{new_employee.first_name} {new_employee.last_name} '
f'has been added to your roster.')
applicant_first_tbx.delete(0, END)
applicant_last_tbx.delete(0, END)
applicant_phone_tbx.delete(0, END)
applicant_email_tbx.delete(0, END)
Creating the relevant GUI
# create the main program window
win = Tk()
win.title('Employee Management Form')
win.config(bg='skyblue', pady=20, padx=50)
# global variables
current_employees = []
edit_mode = False
first_name = StringVar()
last_name = StringVar()
phone = StringVar()
email = StringVar()
job = StringVar()
hired_status = StringVar()
job_titles = ['Cook', 'Dishwasher', 'Server']
food_handler = ['Yes', 'No']
cook_certs = {1: 'Basic', 2: 'Advanced'}
interview_scores = [1, 2, 3, 4, 5]
y_offset = 10
color = 'skyblue'
new_employee = None
edit_index = 0
employeeManagement = True
# options menu
menu_bar = Menu(win)
win.config(menu=menu_bar)
win.iconbitmap('emgmt.ico')
# Create a menu bar with required cascades and commands
file_menu = Menu(menu_bar, tearoff=False)
view = Menu(menu_bar, tearoff=False)
menu_bar.add_cascade(label='File', menu=file_menu)
# Saves the information entered into the textboxes as a new employee, or overwrites an employee if editing
file_menu.add_command(label='Save Employee', command=save_new_employee)
# Turns on Edit Mode so user can modify information of an existing employee
file_menu.add_command(label='Edit Employee', command=edit_employee)
# Deletes employee obj from the data structure and removes their name from the GUI
file_menu.add_command(label='Delete Employee', command=delete_employee)
# updates the text file of employees, so upon reopening the program, all changes made previously are reflected
file_menu.add_command(label='Save All Changes', command=save_all_changes)
# Adds the option for the user to switch between employee management and the interview scorecard
menu_bar.add_cascade(label='View', menu=view)
view.add_command(label='Employee Management', command=employee_mgmt)
view.add_command(label='Employee Rating', command=employee_rating)
# frame to display list of current employees. Always visible as per instructions
emp_list_frm = Frame(win, bg='skyblue')
emp_list_frm.pack()
# create the listbox of employees
emp_list_lbl = Label(emp_list_frm, text='Current Employees', bg='systembuttonface', width=30, justify=CENTER)
emp_list_lbl.grid(row=0, column=1, sticky=S)
emp_list = Listbox(emp_list_frm, width=75, listvariable=current_employees)
emp_list.grid(row=1, columnspan=3)
# employee management frame to add, edit, and delete employees
em_frame = Frame(win, bg='skyblue')
em_frame.pack()
# create layout for job application data
applicant_first_lbl = Label(em_frame, text='First Name:', justify=LEFT, pady=y_offset, bg=color)
applicant_first_lbl.grid(row=2, column=0, sticky=W)
applicant_first_tbx = Entry(em_frame, justify=LEFT, width=30, textvariable=first_name)
applicant_first_tbx.grid(row=2, column=1, sticky=W)
applicant_first_tbx.bind('<Key>', name_keys)
applicant_last_lbl = Label(em_frame, text='Last Name:', justify=LEFT, pady=y_offset, bg=color)
applicant_last_lbl.grid(row=3, column=0, sticky=W)
applicant_last_tbx = Entry(em_frame, justify=LEFT, width=30, textvariable=last_name)
applicant_last_tbx.grid(row=3, column=1, sticky=W)
applicant_last_tbx.bind('<Key>', name_keys)
applicant_phone_lbl = Label(em_frame, text='Phone Number:', justify=LEFT, pady=y_offset, bg=color)
applicant_phone_lbl.grid(row=4, column=0, sticky=W)
applicant_phone_tbx = Entry(em_frame, justify=LEFT, width=30, textvariable=phone)
applicant_phone_tbx.grid(row=4, column=1, sticky=W)
applicant_phone_tbx.bind('<Key>', phone_keys)
applicant_email_lbl = Label(em_frame, text='Email:', justify=LEFT, pady=y_offset, bg=color)
applicant_email_lbl.grid(row=5, column=0, sticky=W)
applicant_email_tbx = Entry(em_frame, justify=LEFT, width=30, textvariable=email)
applicant_email_tbx.grid(row=5, column=1, sticky=W)
# create dropdown list for food handlers card
food_handler_label = Label(em_frame, text='Food Handler Card:', justify=LEFT, pady=y_offset, bg=color)
food_handler_label.grid(row=6, column=0, sticky=W)
food_handler_cbx = ttk.Combobox(em_frame, values=food_handler, width=10)
food_handler_cbx.grid(row=7, column=0, sticky=W)
# create dropdown list for applied positions
job_applied_label = Label(em_frame, text='Select Job:', justify=LEFT, pady=y_offset, bg=color)
job_applied_label.grid(row=8, column=0, columnspan=3, sticky=W)
jobs_cbx = ttk.Combobox(em_frame, values=job_titles, width=15)
jobs_cbx.grid(row=9, column=0, sticky=W)
jobs_cbx.bind('<<ComboboxSelected>>', add_job_applied)
# Textbox showing list of all jobs the employee applied for
jobs_applied_txt = Entry(em_frame, justify=LEFT, width=25, textvariable=job_titles)
jobs_applied_txt.grid(row=9, columnspan=2, sticky=E)
# Highest Cook cert
cook_cert_label = Label(em_frame, text='If Cook, select highest certification:', justify=LEFT,
pady=y_offset, bg=color)
cook_cert_label.grid(row=11, column=0, columnspan=3, sticky=W)
# If applying for Cook, Buttons to show the highest level cooking cert
basic_btn = Radiobutton(em_frame, variable=cook_certs, text='Basic', value=1, bg=color, indicatoron=1, state=DISABLED)
basic_btn.grid(row=12, column=0, sticky=W)
advanced_btn = Radiobutton(em_frame, variable=cook_certs, text='Advanced', value=2, bg=color, indicatoron=1, padx=2,
state=DISABLED)
advanced_btn.grid(row=12, column=1, sticky=W)
# Creates a frame for Rating System
rating_frame = Frame(win, bg='skyblue')
rating_lbl = Label(rating_frame, bg=color, text='Interview Rating Form:n'
'Rate an Employee from 1 to 5, where 1 is the '
'worst and 5 is the best'' in the following categories.')
rating_lbl.grid(row=2, column=0, columnspan=3)
comms_rate_lbl = Label(rating_frame, bg=color, text='1. Verbal/Communication Skills')
comms_rate_lbl.grid(row=3, column=0, sticky=W)
comms_cbx = ttk.Combobox(rating_frame, values=interview_scores, width=10)
comms_cbx.grid(row=4, column=0, sticky=W)
personal_rate_lbl = Label(rating_frame, bg=color, text='2. Interpersonal Skills and Friendliness')
personal_rate_lbl.grid(row=5, column=0, sticky=W)
personal_cbx = ttk.Combobox(rating_frame, values=interview_scores, width=10)
personal_cbx.grid(row=6, column=0, sticky=W)
math_lbl = Label(rating_frame, bg=color, text='3. Math/Problem Solving Skills')
math_lbl.grid(row=7, column=0, sticky=W)
math_cbx = ttk.Combobox(rating_frame, values=interview_scores, width=10)
math_cbx.grid(row=8, column=0, sticky=W)
exp_lbl = Label(rating_frame, bg=color, text='4. Applicable World Experience')
exp_lbl.grid(row=9, column=0, sticky=W)
exp_cbx = ttk.Combobox(rating_frame, values=interview_scores, width=10)
exp_cbx.grid(row=10, column=0, sticky=W)
interview_average_btn = Button(rating_frame, bg='gray', text='Calculate Interview Average',
command=lambda: calculate_interview_average(interview_average_btn))
interview_average_btn.grid(row=11, column=0, columnspan=3, sticky=W, pady=8,)
interview_average_tbx = Entry(rating_frame, bg='white', width=10)
interview_average_tbx.grid(row=11, column=0, sticky=E)
hire_btn = Button(rating_frame, bg='gold', text='Hire Candidate', state=DISABLED, command=hire_candidate)
hire_btn.grid(row=12, column=0, sticky=W)
Идея заключается в том, что вы нажимаете, и сотрудник формирует список, а затем нажимает кнопку, чтобы нанять их. Я думаю, что он возвращает строку из списка, но я хочу, чтобы он возвращал объект в этом месте.
Сообщение об ошибке
File "C:UsersJackPycharmProjectsClass_Project_jxhawki1employee_management.py", line 188, in hire_candidate
new_employee.hired_status = 'Y'
AttributeError: 'str' object has no attribute 'hired_status'
Комментарии:
1. В вашем фрагменте кода не отображается соответствующий код? Где определение класса сотрудника, где вы создаете экземпляр записи сотрудника, где ваш код tkinter, показывающий обработку графического интерфейса?
2. Ваша функция load_employees не создает экземпляр сотрудника для записей в employees.txt, он просто создает список строковых переменных, содержащих информацию о сотрудниках.
3. @itprorh66. Верно. На этом этапе идея заключается в загрузке образцов сотрудников ИЗ файла .txt для создания объектов сотрудников. Мне интересно, работает ли тогда функция save_new_employee() неправильно. В моей функции hire_candidate() я хочу изменить объект
4. можете ли вы, по крайней мере, опубликовать сообщение об ошибке, которое вы получаете, в виде кода в своем вопросе?
5. @itprorh66 опубликовал сообщение об ошибке. Извините, что сначала не совсем ясно выразился. Это мой первый раз, когда я размещаю вопрос на этом сайте. Я все еще учусь форматировать все и что включать. Я ценю, что вы нашли время, чтобы вернуться.
Ответ №1:
Есть несколько проблем с кодом, который вы показали. Во-первых, ваш код, по моему скромному мнению, опирается на слишком много глобальных переменных, которые не являются необходимыми или желательными. Во-вторых, хотя технически все в порядке, то, как вы смешиваете геометрию сетки и упаковки в одном окне, будет работать, это опасная практика. Я предпочитаю придерживаться одной геометрии во всем. Наконец, я полагаю, что ваша основная проблема заключается в том, что вы не предоставили структуру данных для сопоставления строкового значения, возвращаемого функцией выбора списка, с конкретным экземпляром сотрудника, содержащим данные выбранного сотрудника. Следующее не является полным решением общих потребностей вашего приложения, но устраняет некоторые из вышеперечисленных проблем.
Во-первых, перепишите свой класс сотрудников
class Employee:
"""Create Employee class so all employees can be Employee Objects"""
def __init__(self, fname= 'Unknown', lname= 'Unknown', phone= 'Unknown', email= 'Unknown', job= '', interview_rating= 0, hired_status= False):
self._first = None
self._last = None
self._emAdr = None
self._phnNo = None
self._status = False
self._job = None
self._intrvRate = int(interview_rating)
self.first_name = fname
self.last_name = lname
self.phone_number = phone
self.email_address = email
self.employee_job = job
self.hired_status = hired_status
@property
def first_name(self):
return self._first
@first_name.setter
def first_name(self, fname):
try:
if fname.isalpha() and len(fname) >= 1:
self._first = fname.capitalize()
except:
raise ValueError(f"First Name '{fname}' must be alpha and be at least 1 character long")
@property
def last_name(self):
return self._last
@last_name.setter
def last_name(self, lname):
if lname.isalpha() and len(lname) >= 1:
self._last = lname.capitalize()
else:
raise ValueError(f"Last Name '{lname}' must be alpha and be at least 1 character long")
@property
def full_name(self):
return f"{self.last_name}, {self.first_name}"
@property
def email_address(self):
return self._emAdr
@email_address.setter
def email_address(self, email):
if email == '':
self.emAdr = 'Unknown'
elif '@' in email and len(email) >= 1:
self._emAdr = email
else:
raise ValueError(f"Email Address '{email}' must contain an '@' and be at least 1 character long")
@property
def phone_number(self):
return self._phnNo
@phone_number.setter
def phone_number(self, phone):
if len(phone) == 0:
self._phnNo = 'Unknown'
elif len(phone) >= 1:
self._phnNo = phone
else:
raise ValueError(f"Phone Number '{phone}' must be at least 1 character long")
@property
def employee_job(self):
return self._job
@employee_job.setter
def employee_job(self, job):
self._job = job
@property
def hired_status(self):
return self._status
@hired_status.setter
def hired_status(self, status):
if isinstance(status, bool):
self._status = status
else:
raise ValueError(f"Hiring Status '{status}' must be either 'True' or 'False'")
@property
def hire(self):
self.hired_status = True
@property
def fire(self):
self.hired_status = False
def __repr__(self):
return f'{self.first_name}, {self.last_name}, {self.phone_number}, {self.email_address}, {self.employee_job}, {self.hired_status}'
Обновленная функция загрузки данных:
def load_employees(fn):
"""
Loads employees from text file fn.
- Retrieves comma-separated values from each line and assigns it to the corresponding arg in Employee()
- Adds the employee object to the employee_dict.
"""
employee_dict = dict()
with open(fn, 'r') as file:
for l in file.readlines():
itms = l.split(',')
emp = Employee(fname= itms[0].strip(),
lname = itms[1].strip(),
phone= itms[2].strip(),
email = itms[3].strip(),
job = itms[4].strip(),
interview_rating = itms[5].strip(),
hired_status = True if itms[6].strip().lower() == 'y' else False)
employee_dict[emp.full_name] = emp
return employee_dict
Пара перечислимых постоянных классов
class job_titles(Enum):
Cook = auto()
Dishwasher = auto()
Server = auto()
class food_handlers(Enum):
Cook = True
Dishwasher = False
Server = True
class cook_certs(Enum):
Basic = 1
Advanced = 2
Новый класс для управления графическим интерфейсом
# Class to display the Employee Gui and Manage all related employee methods
class EmployeeForm():
def __init__(self, employee_list):
self.emp_list = employee_list
self.selected_employee = None
self.win = tk.Tk()
self.win.title("Employee Management Form")
self.win.config(bg='skyblue', pady=20, padx=50)
self.build_menuBar(self.win)
self.build_list_frame(self.win)
self.win.mainloop()
def do_nothing(self):
#Stub for menu items not yetbimplements
pass
def get_currewnt_employees(self):
pass
def build_menuBar(self, frm):
""" Format the Menu bar """
def add_command(mnu, itm):
mnu.add_command(label= itm[0], command= itm[1])
def add_sub_menu(mnu, cmd_dict):
for ky in cmd_dict.keys():
if len(cmd_dict[ky]) > 0:
mi = tk.Menu(mnu, tearoff= 0)
mnu.add_cascade(label=ky, menu=mi)
for itm in cmd_dict[ky]:
if type(itm) == tuple:
add_command(mi, itm)
else:
add_sub_menu(mi, itm)
menuoptions = {'File': [('Save Employee', self.do_nothing),
('Edit Employee', self.do_nothing),
('Delete Employee', self.do_nothing),
('Save All Changes', self.do_nothing)],
'View': [('Employee Management', self.do_nothing),
('Employee Rating', self.do_nothing)],
}
mbar = tk.Menu(frm)
add_sub_menu(mbar, menuoptions)
frm.config(menu=mbar)
return mbar
def select_employee(self, event):
selection = event.widget.curselection()
if self.selected_employee != self.cur_emps[selection[0]]:
self.selected_employee = self.cur_emps[selection[0]]
# if selection:
# index = selection[0]
# data = event.widget.get(index)
def build_list_frame(self, parent):
""" frame to display list of current employees. Always visible as per instructions"""
self.emp_list_frm = tk.Frame(parent, bg='skyblue', relief= tk.RAISED, borderwidth= 5)
self.emp_list_frm.grid(row= 0 , sticky= (tk.E, tk.W))
emp_list_lbl = tk.Label(self.emp_list_frm, text='Current Employees',
bg='systembuttonface', justify= tk.CENTER)
emp_list_lbl.grid(row=0, column=1, sticky= (tk.E, tk.W))
self.cur_emps = list(x for x in sorted(self.emp_list.keys()))
self.emp_list_dsply = tk.Listbox(self.emp_list_frm, width=75,
listvariable =tk.StringVar(value= self.cur_emps) )
self.emp_list_dsply.grid(row=1, columnspan=3)
self.emp_list_dsply.bind("<<ListboxSelect>>", self.select_employee)
Чтобы выполнить вышеуказанное:
emp_file = 'employees.txt'
emplist = load_employees(emp_file)
EmployeeForm(load_employees(emp_file))
Как только вы выберете сотрудника из параметров выбора списка, содержимое self.selected_employee будет отражать объект сотрудника, на который затем можно ссылаться методом удаления, а также методом редактирования для дальнейших действий.