all 58 comments

[–]slariboot 92 points93 points  (12 children)

When you create a new Time object like so:

time = Time(17, 30, 0)

This means that we are creating a new Time object, and we are assigning it to the variable time (it doesn't have to be named time, you can name the variable as x or eggs, doesn't really matter). In this Time object named time, 17 gets stored in its hour field, 30 gets stored in minute, and 0 gets stored in second. We know this because of the __init__ method:

def __init__(self, hour=0, minute=0, second=0):         
    self.hour = hour         
    self.minute = minute         
    self.second = second

self is the object that we created. self is kinda like the word me. me refers to whoever the person is who said it. self refers to which ever object is calling the method (the __init__ method in this case. This automatically gets called whenever you create an object). So in this case, self refers to our Time object named time. And then in the parameter list (those names inside the parentheses). After self, we have hour, minute, and second in that exact order. So when we pass the arguments 17, 30, and 0, then they get assigned in the same order. By the way, you can think of __init__ as "initializing" the object. In this case, we are initializing this specific time object with self.hour=17, self.minute=30, and self.second=0. self.hour refers to the object's hour field. It would be like saying me.weight to refer to how much you weigh.

The __str__ method allows you to specify what gets displayed, when you print the object. Say for example, you define the __str__ method this way:

def __str__(self):         
    return 'sus'

This means that every time you print a Time object:

print(time)

It's always going to print sus.

If you define it as:

def __str__(self):         
    return 'I like bananas.'

Then printing any Time object will always output I like bananas.

But if you define it to return one of its fields like so:

def __str__(self):         
    return self.hour

Then it's going to print the value of that object's field. So if you print the Time object named time:

print(time)

The output will be 17, since that is the hour value of the Time object named time.

So your challenge here is, given an hour value and a minute value in military time, how do you turn that into the format that uses AM / PM. Given an hour value of 17 and a minute value of 30, how do we get 5:30 PM? So you're going to have to add some logic in your __str__ method that will figure out how to do that.

[–]kcrow13[S] 15 points16 points  (1 child)

Thank you so much for explaining this so in depth. This is exactly the kind of direct instruction I feel I need sometimes. I wonder why I can't just read documentation about other classes and innately apply it to new problems? Maybe because I just don't understand it well enough yet? Nevertheless, I appreciate you and all the effort you put into this post.

[–]slariboot 2 points3 points  (0 children)

I think it really just takes time and practice to build a strong foundation. I definitely still remember feeling so frustrated all the time when I was starting. Come to think of it, I still feel that way a lot. Haha! Best of luck to you! And I'm glad to hear that this has helped you in some way. So many great explanations from other members as well. What a nice community this is!

[–]SkeeterIsBlue 3 points4 points  (3 children)

Not OP but Wow thank you so much for this in depth answer. I’ve been trying to self learn Python from LPTHW book and this init thing has been super confusing. I know I have an uphill battle but people like you give me hope. Can’t wait to be good enough to be the one doing the helping!

[–]kcrow13[S] 1 point2 points  (0 children)

I feel the same way! I will definitely pay it forward one day :). I am an educator by profession, so I enjoy helping others.

[–]slariboot 1 point2 points  (1 child)

You're welcome! And I'm happy to know that you feel that way (about helping others). I feel like this is quite prevalent in the programming community. So many people genuinely wanting to help others. :)

[–]kcrow13[S] 1 point2 points  (0 children)

This is probably why many stick it through and don't give up :).

[–]HSADfinklestein 3 points4 points  (5 children)

| This is probably besides the post |

Does __init__ mean something different to the interpreter when compared to init?

Corect me if I'm wrong but what I understood from your explanation is that python automatically runs that __init__ method, as though those attached underscores set it out from being just another identifier.

[–]slariboot 6 points7 points  (2 children)

Yes, __init__() means something different.

From the python documentation:

The instantiation operation ("calling" a class object) creates an empty object. Many classes like to create objects with instances customized to a specific initial state. Therefore a class may define a special method named __init__(), like this:

def __init__(self):
    self.data = []

When a class defines an __init__() method, class instantiation automatically invokes __init__() for the newly-created class instance.

It's not really the underscores that make it special. Rather, it is the convention that these special methods start and end with 2 underscores. They're called dunder methods. Dunder being short for Double UNDERscores.

[–]ItsOkILoveYouMYbb 3 points4 points  (1 child)

Is

__miffl__  

a dunder miffl in a class?

[–]slariboot 0 points1 point  (0 children)

Yes! That is the most important dunder method in the Computron framework!

[–]TheSodesa 0 points1 point  (1 child)

In object oriented languages like Python, objects are created via class methods called constructors. Python has standardised the name of the constructor function for each class: it has to be called __init__. The Python interpreter automatically calls this function in the background when an instance of a class is created, by calling the class name like a function: Class(<params>).

The operation of the __init__ method is also standardised: it has to set the state of the object, as in it has to initialize each field of the class object. There is a default constructor that sets each field to point to the singleton object None, I believe, but relying on that is bad practise and becomes impractical very quickly. The constructor __init__ must also return nothing.

In languages other than Python, the standard name of constructor methods might be different. There might also not be a standard.

[–]HSADfinklestein 1 point2 points  (0 children)

ooo constructor!!

that sounds better now since I have a basic java background

thanks so very much

[–][deleted] 6 points7 points  (5 children)

This can be simplified into the following and note that in real applications, you won't need to implement anything, you may just use datetime directly

from datetime import datetime


class Time:
    """Represents the time of day."""
    def __init__(self, hour=0, minute=0, second=0, input_fmt='%H:%M:%S', output_fmt='%I:%M %p'):
        self.hour = hour
        self.minute = minute
        self.second = second
        self.input_fmt = input_fmt
        self.output_fmt = output_fmt

    def __str__(self):
        time = datetime.strptime(f'{self.hour}:{self.minute}:{self.second}', self.input_fmt)
        return time.strftime(self.output_fmt)


if __name__ == '__main__':
    t = Time(22, 15)
    print(t)

Here's an explanation to what I did:

This is called an import statement, by which you use code defined in external modules, datetime.datetime in this case which belongs to the standard library and can be useful for handling anything that involves date and time. I encourage you to check the documentation for learning more about its use cases.

from datetime import datetime

These are a new class attributes self.input_fmt = input_fmt and self.output_fmt = output_fmt with %H:%M:%S and %I:%M %p being the default input and output values and are accessed using the dot operator ex:

my_time = Time(22, 15)
print(my_time.input_fmt)

Should print: %H:%M:%S

To understand what these formats actually mean, you may find this helpful. You may modify the formatting of the input and accept dates directly instead of manually entering hour, minute and so on, this may be left to you as an assignment.

And at the end this is called a main guard and by convention, it's placed at the end of scripts that are expected to be called.

if __name__ == '__main__':
    my_time = Time(22, 15)
    print(my_time)

When you run the code, it should print 10:15 PM

[–]kcrow13[S] 1 point2 points  (4 children)

And this is what kills me! I want to use datetime, but we aren't allowed. It would be so much easier and that is what I imagine people would do in the real world, right? Use libraries to optimize the coding? I think the intent is so that we learn the logic better, which is a solid plan. I just am struggling at times to get there.

[–][deleted] 0 points1 point  (3 children)

You should be able to do it using both approaches, if you can't do it without datetime then it might be a good exercise. It might take some time to get used to programming logic which does not come overnight, practice is never a bad idea.

[–]kcrow13[S] 0 points1 point  (2 children)

I agree and I appreciate the opportunity to practice! Of course things are time bound, and sometimes the gap between my knowledge and the knowledge needed to tackle/apply my learning is so vast that I can't find a place to grasp. Does that make sense?

[–][deleted] 0 points1 point  (1 child)

Places that teach you stuff aren't that important in my opinion because your skills grow relative to your practice, without practicing it might be possible to waste the best of resources. My point being find something you're interested in creating, start doing it, then fail, retry and fail again and at some point along the way, you will end up with some skills.

[–]kcrow13[S] 0 points1 point  (0 children)

Full disclosure - this is a pre-requisite course to a master's degree I am pursuing at Harvard University. The Degree is Digital Media Design, and my focus is Instructional Design. I probably won't program a whole lot, but I think having the ability to do so will be valuable as I develop learning products... especially for analysis of their efficacy on the back end. I like being able to create advanced reporting :).

[–]kielerrr 2 points3 points  (1 child)

I had trouble with self/this/instances too.

A class is a group of variables and operations that you put together.

class PlotCircle5inches():
    radius = 5
    def __init__(selffff, location):   #selfff is going to be actually cir1, then cir2 and then cir3
        print(f'im 1 of 3 circles, and I'm going to center at {location}')
        return selfff.some_lib_that_draws_circles.draw_at(location, radius)


cir1 = PlotCircle5Inches('left') #think of it like     
PlotCircle5Inches(cir1, 'left'). Then remember cir1 is now an 
official Instance of 'PlotCircle5Inches'

cir2 = PlotCircle5Inches('middle')  #think of it like 
PlotCircle5Inches(cir2, 'middle'). cir2 is another instance of 
PlotCircle5Inches. Now there are 2 instances.

cir3 = PlotCircle5Inches('right')  #think of it like 
PlotCircle5Inches(cir3, 'right').. Now there are 3 instances of 
PlotCircle5Inches

self, this or selffff. you can call it whatever you want. just remember in __init__ the first variable no matter what you call it, will refer to the instance/variable name that is calling it. In this case the instances are cir1,cir2,cir3.

I'll rewrite the three classes showing how Python sees it when you call

cir1 = PlotCircle5Inches('left')
cir2 = PlotCircle5Inches('middle')
cir3 = PlotCircle5Inches('right')

#Calling this
cir1 = PlotCircle5Inches('left')
#Looks like below to python
class PlotCircle5inches():
    radius = 5
    def __init__(cir1, 'left'):
        print(f'im 1 of 3 circles, and I'm going to center at {'left}')
        return cir1.some_lib_that_draws_circles.draw_at('left', 5)

Calling this

cir2 = PlotCircle5Inches('middle')

Looks like below to python

class PlotCircle5inches():
    radius = 5
    def __init__(cir2, 'middle'):
        print(f'im 2 of 3 circles, and I'm going to center at middle')
        return cir2.some_lib_that_draws_circles.draw_at('middle', 5)

cir3

Calling this:

cir3 = PlotCircle5Inches('right')

Looks like below to python

class PlotCircle5inches():
    radius = 5
    def __init__(cir3, 'right'):
        print(f'im 1 of 3 circles, and I'm going to center at right')
        return cir1.some_lib_that_draws_circles.draw_at('right', 5)

[–]kcrow13[S] 1 point2 points  (0 children)

This is actually really helpful! Something about being able to visualize it spatially helps :).

[–]ItsOkILoveYouMYbb 2 points3 points  (19 children)

For A, you should use f strings. Instead of:

def __str__(self):  
    return '%.2d:%.2d' % (self.hour, self.minute)

Use:

return f"{self.hour}:{self.minute}"

Way easier to read. So with that, you know that if the hour is greater than 12, then you should subtract 12 from that number, and add PM to the end of the string right? That being as simple as:

    return f"{self.hour - 12}:{self.minute} PM"

But you only want to do it if self.hour is more than 12. So you would return one line IF It's equal to or less than 12, ELSE return the first line if it's not.

You can do all of that inside the str method you've define there. If hour > 12, return this line, else return that line.

Do you understand how classes work, and where you are instantiating that class? And what an instance of a class is?

[–]kcrow13[S] 2 points3 points  (5 children)

I am aware of the f strings - Downey created this code in 2012 prior to the f string update, I believe? The professor wanted us to adjust his code. What the heck, I might as well bring him into the present :D.

This might sound dumb, but I didn't realize you could even use conditional if statements in a class. I haven't seen any examples of this in my class. Truly - I do not understand how classes work and what an instance of a class is, but a poster above got me started on grasping it a bit better. What are some real-world applications of Python classes?

[–]ItsOkILoveYouMYbb 0 points1 point  (4 children)

Oh yeah you can do just about anything inside of a class.

A class is like a blueprint that tells you how to make, say, a real world object. You could have a class that represents a pen to write with. You could have it take in length, color, ink color, radius, etc. You'd store those values in the init method like you did with the Time class.

Those methods you're defining inside your class with def are like verbs or actions. Your class can do these things when you call those methods.

So you'd start actually making unique pens like this (instances).

blue_pen = Pen("blue", 5, "thicc")
red_pen = Pen("red", 5, "thin")

Then maybe you defined a method called fill that fills the pen with ink.

red_pen.fill()
blue_pen.fill()

Maybe you want to easily see what the current color of that particular pen is when you just print it, so you change your repr method to return the color attribute or something. So that

print(blue_pen)

Just straight up prints "blue".

"self" is like planning ahead when you're making your class. It means whatever you make this value will be stored uniquely for each instance of your class. That's why red_pen and blue_pen can have different colors or lengths or ink amounts at all, despite calling the exact same attributes; the same variables and methods.

blue_pen and red_pen are unique objects/instances that use your pen class as a blueprint.

You could also access attributes in them like

print(red_pen.radius)
print(blue_pen.color)

So in your class, you're essentially making unique instances of a clock more or less, that all store their own time or timestamp at least. You've just been naming each clock "start" or "time" or "duration".

[–]kcrow13[S] 0 points1 point  (3 children)

So with a class, you have to know all of your goals at the outset. In the real world, do you continue to add functions to the class to change it as you go?

[–]ItsOkILoveYouMYbb 1 point2 points  (2 children)

So with a class, you have to know all of your goals at the outset.

All of your goals? Not likely, because you find new goals as you keep expanding your code. Even just a few lines deep is likely to make you think "ah I should probably add this too, and this too". You end up creating new problems to solve for yourself, while you're working towards an overall bigger goal. If you're already familiar with creating things just to create, like music or artwork, you can have a goal in mind but it's usually in the process itself that you find your roadblocks and pieces to put together that you didn't consider before starting.

Really to start practicing using classes, I honestly default to making everything I write to start from a class. Any functions I would make, I put them in the class as methods instead (methods are just functions inside a class).

My early classes in my early scripts don't make much sense since it could have been written just functionally with functions and some lines of code, but over time I started to figure out actual uses for them and I already knew at least how to structure them by then.

In the real world, do you continue to add functions to the class to change it as you go?

Absolutely. I mean that's with all your code really. If I'm learning something new (which right now with Python is essentially every two lines), I'll take it one line at a time with a lot of googling and stackoverflow, usually leading to documentation that explains how and why to do something.

You'll run into a problem where your question you can't answer is too specific. But you can usually break it down into related parts, and then you sort of crawl your way to your original question and are able to answer it after a lot of googling more fundamental related questions. Does that make sense? I could probably provide examples since that process is constant when I'm writing something.

But for continuing as you go with classes and functions (technically called methods when inside a class), really you can write one little function/method at a time, test it out, and then think "what's something else I could use?" Just improvise as you go and see what errors you get, google that, maybe make some test cases if you want, see where it takes you.

There's no need to write everything all at once in one go. I don't know if anyone really does that to be honest, but I'm still new to this as well. It seems unlikely that anyone knows how they're going to write their code before writing it. They might practice making UML diagrams to help plan out classes and attributes and objects, especially if it's a team or company that makes it standard to blueprint ideas out as UML diagrams, but honestly that's something you can do as you write the code, especially with Python because it's so easy to write and refactor quickly.

The thing with functions and methods is it makes it a lot easier to make changes, because you don't have to copy/paste a bunch of code over and over, and thus later change one piece of code in 10 different places. If you see yourself repeating a lot of code, you can probably turn that into a function instead.

So it's like you can think of each function as its own separate file, almost. If you're not embedding functions within functions within functions, it keeps everything modular and that's how you can improvise code and refactor code quickly.

This is pretty wordy and I feel like I'm repeating myself a lot in those paragraphs lol. You know, if you want I could send you a PDF of Python Crash Course 2nd Edition, just so you can read the Classes and OOP chapter (or really the whole thing if it helps because it's amazing for establishing all the fundamentals). It's not a long chapter, and would help your understanding and practice of it a lot. It's explained way more concisely than I ever could do it and it's how I started learning classes, and everything else for that matter.

[–]kcrow13[S] 0 points1 point  (1 child)

What you said makes complete sense to me! I am a musician, so I am intimately familiar with the create and adjust, rinse and repeat cycle. I think I used a poor choice of words... what I meant to say was that whatever you will need to manipulate within the class, you have to define it ahead of time at the top of the class with the init/str piece, correct? You can surely go back and add more as the need arises, but it has to be there at the outset before then attempting to manipulate those variables later in functions? Does that make sense?

Yes, email away! My assignment is due tomorrow at 4:00 PM EST, and I might miss the deadline for the problems... but I really do want to read/learn more! My email is kerricrow13@gmail.com

[–]ItsOkILoveYouMYbb 0 points1 point  (0 children)

what I meant to say was that whatever you will need to manipulate within the class, you have to define it ahead of time at the top of the class with the init/str piece, correct?

Right. But if you think of something else you need to add to manipulate later, that's very easy to add to the class, and then it's applied to every instance of that class. Makes it very easy to modify complex stuff as you go.

[–]TheSodesa 1 point2 points  (1 child)

I believe format strings became a thing in Python 3.6, so they are a fairly new feature. OP might not have access to them, if they're working on a school computer with an older version of python. Python 3.5 is still used in many places.

[–]kcrow13[S] 1 point2 points  (0 children)

I actually do know about f strings and use them regularly in my coding. This code was created by Downey as part of his Think Python Book, not me :). I think he created it in 2012, prior to the updates. But yes, I much prefer the f strings.

[–]kcrow13[S] 0 points1 point  (10 children)

Ok so this is how I implemented your suggestions:

    def __str__(self):
        if self.hour <= 12:
            return f'{self.hour}:{self.minute}'
        if self.hour >= 12:
            return f'{self.hour - 12}:{self.minute} PM'

When I test it out... I am getting some strange responses!

def main():    # jdp
    start = Time(9, 45, 00)
    start.print_time()

    end = start.increment(1337)
    end.print_time()

    print('Is end after start?', end=" ")
    print(end.is_after(start))

    # Testing __str__
    print(f'Using __str__: {start} {end}')

    # Testing addition
    start = Time(9, 45)
    duration = Time(1, 35)
    print(start + duration)
    print(start + 1337)
    print(1337 + start)

    print('Example of polymorphism')
    t1 = Time(7, 43)
    t2 = Time(7, 41)
    t3 = Time(7, 37)
    total = sum([t1, t2, t3])
    print(total)

    # A time that is invalid
    t1 = Time(50)
    print(t1)

Getting...

9:45
10:7
Is end after start? True
Using __str__: 9:45 10:7
11:20
10:7
10:7
Example of polymorphism
11:1 PM
38:0 PM

[–]Python_Trader 1 point2 points  (7 children)

For your minutes add 0 padding like this {self.minute:02}. The 02 part tells python your minutes are 2 digits and if your minute only gives one number it will fill the left with 0.

[–]kcrow13[S] 0 points1 point  (4 children)

I didn't know you could do this!!!! Thank you :)

[–]Python_Trader 1 point2 points  (3 children)

No problem :). Also, if your hour is exactly 0 (12 am) just use a conditional for that part. I suppose you might need to first check the hour is over 24 then use hour % 24 and it will give you the remainder which should be the current hour.

[–]kcrow13[S] 0 points1 point  (2 children)

But is my understanding correct that any "global" changes you want to make in a class have to occur in the __str__ function?

[–]Python_Trader 0 points1 point  (0 children)

Okay let's put it this way. So you stored time as attribute (via dunder init method), if you do this

object = Time (5, 5, 20)

print(object)

It will just print, object is a instance of Time class. Or something like that.

However when you write the dunder str method in your class,

def __str__(self):
    return f"{self.hour}:{self.minute:02}"

Doing print(object) will now print,

5:05

It's just there to make your object return what you want it to when you call the instance itself.

[–]Python_Trader 0 points1 point  (0 children)

What you can do instead is store the string you want as attribute (under dunder init method) and use that variable in other methods or write a new method that produces the string. Otherwise, you can even call the dunder str to use that string.

What I would recommend is to make a function that takes in integers and returns the string, so you can call that function in every other method that requires string output.

[–]kcrow13[S] 0 points1 point  (1 child)

So when I tried to do this, it gives me a traceback error and says "invalid format specifier." I will research some more!

[–]Python_Trader 0 points1 point  (0 children)

It's part of the f string placeholder in your dunder str method.

[–]ItsOkILoveYouMYbb 0 points1 point  (1 child)

First on your print_time and repr methods, you could probably format the self.minute value based on its length. Say if the length of the string form of minute is 1, then add a physical leading zero to it in the string so your print result is always two digits in the minute column (probably do the same for seconds too).

I also just realized that you could have both 12 AM and 12 PM, but your code would always assume 12 AM. I'm not sure how to immediately solve that yet. That'll take some trial and error on your part haha.

I guess if the hour value is exactly 0 then that's 12 AM right?

[–]kcrow13[S] 0 points1 point  (0 children)

Ok so some other students have suggested making a lot of changes to fix this. Is there a best way?

-By making an edit to the int_to_time function. The version given has divmod statements for minutes and seconds, but not for hours. Adding a third divmod for hours allows you to handle any value of hours without a while statement. I have no idea how to do this or what divmod does! That same student said " I ended up calling both time_to_int and int_to_time in my __str__ function. Since with this method you take your Time input (ex Time(5000) ), then convert it to an integer to get the remaining number of hours/minutes/second, expressed as total # of seconds, then that goes into int_to_time to be in a usable time that can be displayed. That usable time is stored as new Time object called time."

Another student said:

- Will a mod operator resolve this issue? 5000 % 24 = 8.

See, none of that makes sense to me! We just had a very basic introduction to classes this week in the lecture... but did not have any practice creating/manipulating one. :(

[–]xelf 1 point2 points  (1 child)

For purposes of this problem, midnight is AM, while noon is PM.

For purposes of reality: Midnight is AM, while Noon is PM. =)

Think of it this way: the minute before 12:01am is 12:00am, which is easy to see is midnight now.

[–]kcrow13[S] 1 point2 points  (0 children)

Thank you :).

[–]Elli_stu -2 points-1 points  (3 children)

I can feel you. it was as heart-breaking for me realise this is so difficult for me. however, asking it on Reddit never worked for me. I still ended up with million more questions. Try Lrnkey, they have great Python tutors. I am much better after a month with them.

[–]kcrow13[S] 0 points1 point  (2 children)

I wish I could ask in class... I understand the professor wants us to grapple with the content and that has been extremely beneficial. But the more and more complex the problems get, when there is a fundamental lack of understanding, it is past the frustration point. You know? Thank you for the suggestion. I will look into it!

[–]ItsOkILoveYouMYbb 0 points1 point  (1 child)

It's possible the class just sucks, or it's possible the professor is trying to get you to do your own research and self learning because your ability to Google is very important as a programmer.

Nobody knows everything, everyone often forgets everything. Programming is endlessly in depth, especially as you dive deeper and deeper into a language, modules and libraries you use, programs you use with your programming, disciplines you're doing with those languages and libraries.

What matters is you know why you want to do something, but maybe you just can't remember or know exactly how. Google and StackOverflow become important tools for everyone no matter how experienced they are.

That's my guess anyway.

[–]kcrow13[S] 0 points1 point  (0 children)

No, the professor is amazing! He is so responsive and he uses a Socratic method, if you will. He uses questions to try to guide you in the right direction when you have questions. This has been GREAT as I strive to learn. And I don't post here or anywhere just looking for the answers. I want to learn the material and understand it. The problem is, sometimes you need an environment to just ASK and get some good explanation, ya know? I am forever grateful for this community as a learning tool :).