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 itfhqwhgads
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. Theself
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 nameTimesheet
. So when you doTimesheet.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 ofTimesheet
(theTimesheet()
formulation is how you create an instance), and then you're calling theday()
method on that instance. So the "extra" first parameter will be passed to yourday()
method so that your code insideday()
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 aself
parameter in yourday()
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:It would make no sense to do
Timesheet.calculate_total_pay()
, because the total pay depends on the values in specific, individual timesheets. Socalculate_total_pay()
should also be an instance method, and should therefore have aself
parameter. Actually, in this case I'm not coming up with any methods that should be called asTimesheet.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)
toworkday = 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 noself
object to be passed implicitly.Here's how I'd fix your code:
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 (viaself
), rather than creating a new one: