I am trying to learn object orientated programming in python 3. I am making a variation of a notebook program that I have in a book but instead of adding notes to a notebook I am trying to add days to a timesheet.
In the original tutorial, this is in the main program:
def add_note(self):
memo = input("Enter a memo: ")
self.notebook.new_note(memo)
print("Your note has been added")
and this is in the class module (notebook):
def new_note(self, memo, tags = ''):
'''create a new note and add it to the list'''
self.notes.append(Note(memo,tags=''))
My variation looks like this:
main:
def add_work_day(self):
date = input ("Enter date : ")
hours = input ("Enter hours worked : ")
rate = input ("Enter hourly rate : £")
workday = Timesheet.day(date, hours, rate)
module:
class Timesheet:
def __init__(self):
self.timesheet = []
def day(self, date, hours, rate):
self.timesheet.append(day(date, hours, rate))
It is giving me this error:
File "C:\Python33\timesheet_menu.py", line 39, in add_work_day
workday = Timesheet.day(date, hours, rate)
TypeError: day() missing 1 required positional argument: 'rate'
It seems like the 'self' in 'def day(self, date, hours, rate)' is hogging one of my input arguments.
Can someone tell me what I am missing here?
.....Update.....
So now I have created an instance of Timesheet() in main:
def add_work_day(self):
date = input ("Enter date : ")
hours = input ("Enter hours worked : ")
rate = input ("Enter hourly rate : £")
workday = Timesheet()
workday.add_day(date, hours, rate)
But I am getting a new error from my Timesheet() method 'day'
class Timesheet:
def __init__(self):
self.timesheet = []
def day(self, date, hours, rate):
self.timesheet.append(day(date, hours, rate))
File "C:\Python33\timesheet_menu.py", line 40, in add_work_day
workday.add_day(date, hours, rate)
File "C:\Python33\timesheet.py", line 29, in add_day
self.timesheet.append(day(date, hours, rate))
NameError: global name 'day' is not defined
I understand that the problem is the .append(day part but I can't figure out how to fix it. I know a variable isn't global unless specified but my logic tells me that the method should be. So it must be that the .append(day is looking for a pre-exisiting variable called 'day'. I'm confused because this method worked in the example from the book.
The root of your problem is that you don't yet understand how Python classes and instances work.
A class, like Timesheet
, is a collection of methods (functions) and variables, which live in the class's namespace. An instance is a specific instance of the class (i.e., this timesheet, as opposed to all the other timesheets that exist). Each instance has its very own namespace, which is slightly special: when you look for a method or variable in an instance namespace, if the name is not found, the class namespace will be searched next. (And if the class inherits from other classes, the namespaces of its ancestors will be searched, in order, until either the name is found or there are no more namespaces left to search.)
Now, methods (functions) defined in classes have a special behavior, which functions defined outside of classes don't have -- this is why a different term (methods) is used for functions defined in classes, to help remind you of this special behavior. The special behavior is this: if the function is being called on an instance of the class, then that instance will get passed as an "extra" first parameter to the function. (By convention, that first parameter is called self
, but there's no reason you couldn't call it fhqwhgads
if you wanted to. You shouldn't -- it would just make your code utterly confusing to read -- but you could if you wanted to.) Why that extra first paremeter? Well, remember how I said that instances have their own namespace? If you want to look up variables on the instance (e.g., you want to look up the entries on this timesheet, not that other timesheet over there), then you need a reference to the instance. The self
parameter provides that reference.
Now, if you call the methods on the class, as opposed to on an instance, there's no need for that extra self
parameter, because you clearly already have a reference to the class: that reference is the name Timesheet
. So when you do Timesheet.day(...)
, there will be no "extra" first parameter added before your other parameters. That's because you're not referencing an instance, you're referencing a class.
But if you call Timesheet().day(...)
, then two things are happening. First, you're creating an instance of Timesheet
(the Timesheet()
formulation is how you create an instance), and then you're calling the day()
method on that instance. So the "extra" first parameter will be passed to your day()
method so that your code inside day()
will be able to access that instance's variables.
One other thing you'll need to understand: when variables belong on an instance, and when they belong on a class. There's a very simple question you can ask yourself to determine this: "does this apply to every timesheet, or only to specific timesheets?" Your day()
method clearly needs to access values from specific timesheets (Joe worked different hours than Bob, at a different rate of pay), so you need to call it on instances, not on the class. So having a self
parameter in your day()
method makes sense, but you also need to call it from a method, not from the class.
So instead of Timesheet.day(...)
, you should do something like:
my_timesheet = Timesheet()
my_timesheet.day(...)
# Now do something with the timesheet: calculate total pay, print it out, etc.
my_timesheet.calculate_total_pay() # Made up example
my_timesheet.print_to_screen() # Made up example
It would make no sense to do Timesheet.calculate_total_pay()
, because the total pay depends on the values in specific, individual timesheets. So calculate_total_pay()
should also be an instance method, and should therefore have a self
parameter. Actually, in this case I'm not coming up with any methods that should be called as Timesheet.some_method()
. (Methods called like that are called "static methods" in Python, BTW). Every single example method I can come up with is an instance method (i.e., a method that should be called on an instance, because it would need to access data from that specific timesheet).
A bit long-winded, but I hope this helps you understand classes and instances better.
Change workday = Timesheet.day(date, hours, rate)
to workday = Timesheet().day(date, hours, rate)
The issue you're having is that you're not creating a Timesheet
instance in your current code. You're just calling a method on the class directly. Since unbound methods are simply functions, you're getting an argument mismatch error, since there is no self
object to be passed implicitly.
Here's how I'd fix your code:
def add_work_day(self):
date = input ("Enter date : ")
hours = input ("Enter hours worked : ")
rate = input ("Enter hourly rate : £")
workday = Timesheet() # create Timesheet instance
workday.day(date, hours, rate) # add a workday to it
# presumably you'll also want to do something with `workday` here too
Now, it may be that you already have a Timesheet
instance created somewhere else in the class your method is in. In that case, you just need to refer to it (via self
), rather than creating a new one:
def add_work_day(self):
date = input ("Enter date : ")
hours = input ("Enter hours worked : ")
rate = input ("Enter hourly rate : £")
self.myTimesheetInstanceCreatedSomewhereElse.day(date, hours, rate)