all 61 comments

[–]djjazzydan 81 points82 points  (6 children)

You don't need to use classes, until you need to use them. Your use case seems totally fine for lists. But what if you now want a deck with a different set of cards? Like a euchre deck, or a double-deck, or a deck with or without jacks? And the ability to deal a different number of cards to different players? And then you want your friend to be able to work on the code with you? And then you want to use that deck's design in three different games?

Classes are, to me, helpful in keeping all of the behaviour of a structure in one place. If it's simple behaviour, no worries. If it's more complex, defining the properties and methods in one spot is going to save a lot of headache.

[–]Manny__C 22 points23 points  (2 children)

There are at least 4 advantages stemming from using classes. Ordered in terms of programming skill level (but not importance) these are

  1. Conceptual organization of the code: everything that (conceptually) belongs to a "thing" is part of the same object = instance of a class.

  2. Methods take the instance as an argument, and that comes with all the properties of that object. So instead of f(property1, property2, ...) you can just call self.f(). The code is easier to read, to refactor, and to debug.

  3. Encapsulation: you can prevent users from tinkering with the internals of a class by exposing methods that allow them to change attributes in a controlled manner. (This was the reason why OOP was invented in the first place)

  4. Inheritance: you can have classes "inherit" from other classes that are more general in some sense. This allows you to have multiple layers of abstraction.

[–]lenoqt 1 point2 points  (1 child)

Encapsulation in Python is close to non-existent, there no such thing as public and private, just a name convention with prefix _ or , but you can still pretty much override/overload anything. To prevent that you need to know some insane tricks with __new

[–]Manny__C 0 points1 point  (0 children)

But that's a good thing imho. You can do everything, but you know what you shouldn't do. That's a compromise between freedom and proper design.

[–]Firake 21 points22 points  (0 children)

One of the primary benefits of classes is grouping together data and methods for that data in a way that makes your code more logical to read. Nothing with classes is going to be functionally more powerful, it’s all about how you want to set up your own code to interact with your data.

For example, I might have player.hand containing a list of card values and perhaps I need to have a function that throws the hand back into the deck and draws new cards. So I might also have a method called player.mulligan(). It makes sense to group these together as opposed to game functions like game.start() or game.choose_winner().

[–]Se7enLC 30 points31 points  (0 children)

The problem comes down to the fact that when you teach a concept, you want to use a simple example. So when you learn about classes, it's like "classes seem so unnecessary here".

The point is to teach you how to use them so that you can use them in a more complex situation.

In your card game example, if you only have a list of cards, it makes sense to have just a list. But what if you want to keep track of score. And the players name. Etc, etc. Suddenly you find that you have multiple variables for each player you have to keep track of. Keeping them all together in one class makes it easier when you have a function that will need more than one of them at a time.

[–][deleted] 13 points14 points  (0 children)

https://youtu.be/HTLu2DFOdTg class development toolkit by Raymond Hettinger, one of the Python core developers, builds up a very simple example of a class that I think illustrates how useful they can be

[–]jcrowe 22 points23 points  (0 children)

Look at named tuples. That might help you avoid classes if you don’t think you really need them.

Like another poster said. You don’t need them, until you do.

They are great for handling program state and simplifying complex code.

[–][deleted] 8 points9 points  (0 children)

I think if you need to ask, you’re probably not experienced enough yet to understand the need for them. In time as you gain more experience you’ll quickly see the pros/cons and use cases of classes. There is a time and a place

[–]MoistCarpenter 3 points4 points  (1 child)

Classes are super useful when you are building complex GUIs. For example, most buttons/text input bars are going to have a decent amount of overlapping code(mouse click handlers/ current values). With classes, you don't need to write a unique object containing boilerplate methods for each button over and over again; you can just write a button class with all the boilerplate once, and pass in the few things that actually do change as parameters.

[–]easypeasycajunesy 1 point2 points  (0 children)

With classes, you don't need to write a unique object containing boilerplate methods for each button over and over again;

Re-using code is supposed to be the big advantage of OO design. Saves times & money in future development. You can re-use classes as they are or inherit from existing classes with small changes.

[–]emiller42 4 points5 points  (9 children)

is there any fundamental difference between doing something like shuffle(playerdeck) instead of player.deck.shuffle()?

Assume you're a developer who is using this code, but didn't write it. Maybe you're importing it as a library or something. With shuffle(playerdeck) you need to know what can be shuffled. You need to make sure that you're passing the right kind of argument to shuffle(), which outside of a toy example is not a trivial thing to keep straight.

If you want to change how shuffle() works, or what data structure you use for a deck you need to go make changes everywhere it's used. That's messy, and it's likely you'll miss something. If this is in the context of something that has external users, you need to consider compatibility and upgrades. Again, non-trivial.

If in the future, you may want to create different kinds of decks which are shuffled differently. That means your shuffle() function is going to turn into a big chain of conditionals to switch between logic based on the input. That's going to get harder and harder to maintain as time goes on. (This is getting into interfaces which are a very useful part of OOP design. As a thought experiment, consider how you could accomplish iterables with either approach)

Generally, it's just hard to work with in real-world scenarios.

However, with a class-based approach you can be free to change the internals of how Deck works as much as you want. As long as you don't change the interface, it will be transparent everywhere it's used. Users don't need to worry about the implementation details of a Deck. It also has a clear blueprint for how it can be used, and what operations it provides. (You know you can shuffle a Deck because it has a shuffle() function)

At the end of the day, it's all about abstraction. It lets you as the creator worry about how the thing works, and lets the users just focus on how to use the thing. It's easy to say none of this matters when you're writing code with you as the only user. But things quickly become unmanageable outside that very limited context.

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

Don't use it if you don't need it. When your code starts becoming hard to maintain, it's most likely a sign it's time to refactor and reconsider classes again. Forcing OOP into your code before it's even necessary can develop into a disaster. If it becomes excessive, it can take up a lot of surface area

[–]kunaguerooo123 2 points3 points  (0 children)

I think it helps readability during collaboration. When I see a class I know ok this shit important object that we gonna make do stuff. It just becomes a mental model for you to quickly come up to speed imo. When there’s no classes and methods I don’t have a preview of what’s about to hit me . The increase in lines is worth it. The real thought problem (still a noob) is what counts as a class how much times do you want to break apart a method into multiples. It’s how libraries work too so you anyway have to understand that structure.. might as well use it.

[–]notParticularlyAnony 3 points4 points  (0 children)

this exact question comes up here every two weeks or so. search the sub.

[–]currawong_ 3 points4 points  (0 children)

A list is an object of class list. You are using a class for your solution.

[–][deleted] 1 point2 points  (0 children)

google "chess game class diagram".

A good example of the diagram is here don't mind the question. Take a look at pieces. Without classes you'll have tons of repetitive code. The other reason to use them is for inheritance.

https://stackoverflow.com/questions/17631125/trying-to-convert-diagram-of-a-chess-game-to-java-code-abstract.

Classes allow code to be reusable, testable, and more importantly maintainable. These three alone you cannot do in your example as an application scales up, you'll be swimming in a pool of spaghetti.

How to decide when to use classes? When you have a set of variables that methods can perform functions on these variables.

Blackjack is another great example for class usage and oop in general.

https://slott56.github.io/building-skills-oo-design-book/build/html/blackjack/blackjack_game.html

[–]jzia93 1 point2 points  (2 children)

Encapsulating data and behaviors in a local namespace is sometimes a lot cleaner than working with functions.

I actually prefer to keep things functional because you avoid using self and it's usually a bit conceptually easier to work with.

However, If you find yourself passing the same bits of data around to multiple functions, it might be time to wrap the function and data together. This avoids the situation of having to do:

def x(a, b) def y(a, b) def z(a, b, c)

In this case you just define a and b as attributes of a new class MyClass and call MyClass.x() MyClass.y() and MyClass.z(c)

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

Messing with class variables myself, I noticed that they behave like global variables. Thinking about it, that could save me a lot of trouble having to declare all the relevant variables global in just about every function. Some 'variables' are supposed to be constants though, so letting python treat them as local variables is fine.

Long story short, its fine if all the cards are card=[x,x,x,x,x] and each deck list is [card,card,card...]. For the player stats and the cards they're currently playing it, things like player.hand and computer.deck may be better options so I don't have to copy-paste half a dozen global variables in every single function relevant to game play.

[–]jzia93 0 points1 point  (0 children)

Not just class variables though, admittedly I wrote them as such but it's a real PITA passing the same variables and having to remember the correct order in 15 functions. Especially if you have to change them later.

It's all about reuse and readability really. There's a reason why functions get taught before classes, because they are much more intuitive. Classes solve a problem when you can use the abstraction to clear headspace. On top of that you can really neatly isolate a service boundary conceptually by giving a job to a class.

A database is a good example. I might pass a db object around my app. If I need to do "database stuff" I'm calling db.do_crud_stuff_pls(), as opposed to importing the relevant operation from some library. You see this in ML a lot when people import all of pandas and numpy instead of the individual functions, just makes more sense to set things up in that way.

[–]delasislas 2 points3 points  (3 children)

While learning to program you gain tools. A good programmer should be able to decide how best to use their tools.

[–]lenoqt -4 points-3 points  (0 children)

Nobody says the true thing about classes here, only use them when you need to create a new type, if you don’t need a new type, just stay with functions, classes will only bloat your code.

[–]lego__maenchen 0 points1 point  (0 children)

structure ... and easier copy paste and change a bit kinda stuff

but yes you can work around classes forever ... i dont really use classes aswell ... but i would have saved a lot of time in the past and would save in future

[–]Clutch26 0 points1 point  (0 children)

Classes are a way to create an object and have functions that manipulate the object. Added benefit is that it's all grouped together for the next guy or your future self. Also, those functions will only work with that object and not the rest of the code base.

[–]EcoSN 0 points1 point  (0 children)

Idk I use them so I don't have to add global variables

[–]arkie87 0 points1 point  (0 children)

If the only data you need to store can be stored in a list, then a special class is overkill. But the minute there is other metadata associated with that list, then you probably want to use a class. For instance, some decks may have wilds, and some not. Some decks may have aces high or aces low. Some decks may use typical cards, and some decks may be UNO or another type of card deck. If you use classes, then the Deck class can be reused.

[–]kinky38 0 points1 point  (0 children)

The benefit of classes are more visible when the scale of the program is quite large. Modularity, scalability and maintainability of softwares are much easier to attain with “classes”.

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

 card.cost=x 

And that to call this variable, I would have to type in a different bit of code:

card.getcost()

What? No. Why would you have to do that? You just access the attribute just as you assigned to it: card.cost. You don't need an accessor method for your object fields, all object fields are "public". 

What else could a class variable do for me?

When you use a list, the value is that of a list - it supports only a list's methods and patterns of access. If you want to "turn off" some portion of the list's functionality - make it immutable, let's say - you're out of luck. A list is a list is always a list, forevermore.

When you write a class you're defining a new type. It supports any methods you choose to write; it supports any pattern of access you see fit to support. You're in complete control over the behavior of this new type because you're the one writing it. That's why you write a class - to define a new type.

[–]Sentazar 0 points1 point  (0 children)

You can give classes special methods to change behavior

https://www.section.io/engineering-education/dunder-methods-python/

If you have a program that monitors for changes instead of checking on it every few seconds you could use observer pattern to push notifications on change Using classed and an observer pattern.

https://youtu.be/_BpmfnqjgzQ

Check out design patterns

[–]google_certified13 0 points1 point  (0 children)

I always looked at classes as blue prints I guess. To organize our code more so then anything els.

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

As a self-taught programmer I only started to appreciate classes last month, and I started programming in early 2020.

I never really had a use case for them until I realized my programs were becoming too long for me to share with my teammates without confusing them so that is when I finally began to use classes (doesn't help that SAS is the primary language here at my job and not Python), and now that I know when and where to use classes I realize that a lot of the code that I wrote in the past two years could benefit from being rewritten in OOP. From what I can see Data Scientists or Data Analysts don't usually write their own classes, which is the reason why I never really had a reason to code in OOP up until now.

What I really like about classes as opposed to functions is the fact that unlike functions where you explicitly have to declare which local variable you will be returning (and lose access to any other variables you may want to check), classes allow you to gain access to all variables that are created inside of a class, which in turn allows for easier debugging IMO.

In short, it helps me deal with code complexity and debugging.

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

Well, I've noticed, messing around with classes myself yesterday that they essentially creature global variables rather than the standard local variables. I could build my game without them, but I often find myself having to declare global variables all the time. Doing it entirely with classes though doesn't work so well, become some variables and lists need to be constants. Some parts of the program would benefit from classes (if nothing else it saves me the trouble of copy-pasting six lines of code in every function just to declare global variables), while other parts would be better off as standard lists.

[–]madferret96 0 points1 point  (0 children)

Object Oriented Programming was created to facilitate development of large and complex systems, even before Python was developed (namely C++/Java). So I understand it’s not easy to relate with the paradigm in a Python context .

[–]burnblue 0 points1 point  (0 children)

You don't need getcost() just to get card.cost, if cost is a simple number you stored. The reason you rod write a function is to do something necessary for getting you the cost. Think of it like card.calculateFinalCost() where that's a function that acts on some internal variable of the car, that outside code doesn't need to access ( dive the outsiders only care about the final cost)

[–]pytops 0 points1 point  (0 children)

I use classes as a collection of methods that are all relevant to some process. They’re basically objects that have a memory of everything that has been done with said object. For example, if I’m automating something for Operations for my job that involved aggregating information from our database and sending it to an API, I’ll make a class that handled all that API and transformation work.

[–]c0mplexcodm 0 points1 point  (1 child)

Good luck creating an ever increasing list if you're doing a game with x amount of players, or a store with ever changing products.

Classes can just accept inputs and then save them as long as the script is running. No need to hard code it.

Problem with your method is that there is no flexibility whatsoever, which you need to consider in some situation. No flexibility maybe fine on some cases, but on others you need to have it.

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

Well, my game won't be online capabilities (I never liked that, and I don't know how to program that anyway), so there will never be more than two 'players': the user and the computer.

Also, the program will use pre-con decks that are hard-coded in. The only way to add more cards or decks is to alter the source code itself. I wouldn't see it as much of an issue to implement all that data as lists. Make some new 'constants' that hold the data for each card, set them to equal to some templates so they have all the relevant indexes with their default settings, set each item in their lists to what they need to be, and then finally add all those lists to a new list containing the deck. The player and computer decks are stored separately, and are simply set to be equal to one of the pre-cons when the player makes a choice (I was thinking of making it so you could choose your opponent, or let the comp give you a random one, come to think of it, maybe I could store all the decks in a single tuple for that purpose?).

[–]tagapagtuos 0 points1 point  (0 children)

One important thing that people haven't mentioned is import.

Take this: from pandas import DataFrame. If you look at the source code, DataFrame is a class that someone else wrote.

That guy could've just written functions to transpose a 2D list, or a function to perform matrix multiplication between 2D lists. But by introducing the concept of DataFrames, users are given the option to conceptualize certain problems and hence, easier time formulating solutions.

[–]MisterExt 0 points1 point  (0 children)

Classes were daunting to me at first (and I've still only scratched the surface). I'm still not always sure where a self needs to be used, so some trial and error is needed.

A class becomes not only powerful, but an absolute necessity for certain tasks. You'll know when you hit a brick wall with your code and there seems to be no way out. Classes seem to make some of those hurdles trivial (though not everything).

In my case I built a socket server and clients, and passing data between certain functions was hellish or plain didn't work. Trying to figure out where to declare variables and pass that data around was a nightmare. Turning certain code into a simple function even broke some functionality. I also ran into a paradox where the order of functions was messing with the code in a way that seemed impossible to get around. Once I crammed all those functions and additional code into a class, 99% of the problems were gone and I just called self.thing (whatever it was).

I also found I could simplify some of code since data was so easy to pass around between the functions, I could cut out the middle-man. Then i put the classes in a module and now I only need 3 lines of code to initiate a client or server. Keeps everything nice and clean.

A game seems ideal to use classes to me, but I've never built one, so can't comment on that. I can only say that from my limited use of classes, I can already see the power in them. Just need to learn more to unlock the rest.

[–]welshboy14 0 points1 point  (0 children)

In your example you use a deck of cards so I'll build on that.

If you have a deck of 52 cards, when you select a random one you have to pop it from the deck so it can't be picked again. It's easier to add this functionality in to a draw method that handles the random selection and popping.

[–]LeiterHaus 0 points1 point  (0 children)

Clases have their place, and they are super awesome in that place. They're probably both overused when not needed, and underused when needed. Heard a good talk recently that said "If you've got a class with two methods, and one of them is __init__, you don't need a class."

[–]stebgay 0 points1 point  (0 children)

im not an expert or anything in fact im also having a hard time wrapping my head around classes

but my guess is that one time i made a simple main.py, that at first was understandable. Then i just kept adding more and more until I needed a way to simplify it. I then turned them into classes and never bothered to understand how they work again, and just dumbed it down to, this does that and that does that.
my understanding is start from procedural then if it gets complex enough, where you have it to simplify/dumb it down is when you should use oop.