На объект класса ссылаются как на строку

#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 будет отражать объект сотрудника, на который затем можно ссылаться методом удаления, а также методом редактирования для дальнейших действий.