Knottystatue
Knottystatue

Reputation: 55

The use of self in multiple methods of a class

So basically as a beginner programmer I created this program that calculates maximum possible evolves for different pokemon/candy combinations in pokemon go using Tkinter. I created this program before just using a function with al the code. However, I thought it would be good to use a class and after some research this was the result. However, I basically got it working by adding self to a whole bunch of variables in the different methods.

My question is: Is the use of a class even relevant here. My research on classes involved populating a class with objects etc, but here i just create 1 object in order to use the class and run a bunch of calculations.

Furthermore, how do I know when to use self when calling a variable. For instance I create static class variables with the impression that i don't have to put the instance self in front of it to use it, but that didn't work so I just put more selfs in front of the calling of the variable. Why does it work to put a bunch of selfs in front of everything? Are the selfs overkill and could I do it in some smarter way?

from tkinter import *
from tkinter import messagebox


class PokeCalculator(Frame):
    pokedex = {
        'pidgey': 12,
        'caterpie': 12,
        'weedle': 12,
        'rattata': 25
    }
    choices = pokedex.keys()
    screen_title = 'Pokemon evolve calculator'

    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.master = master
        self.make_window()

    def make_window(self):
        self.master.title(self.screen_title)

        L1 = Label(self, text='Candies')
        L1.grid(column=1, row=0)

        L2 = Label(self, text='Pokemon amount in storage')
        L2.grid(column=2, row=0)

        self.var = StringVar()
        self.var.set('Pokemon')
        self.Pokemon = OptionMenu(self, self.var, *self.choices)
        self.Pokemon.grid(column=0, row=1)

        self.Candies = Entry(self)
        self.Candies.grid(column=1, row=1)

        self.Poke_amount = Entry(self)
        self.Poke_amount.grid(column=2, row=1)

        Calculate = Button(self, text='Calculate', command=self.get_and_check)
        Calculate.grid(column=1, row=2)

    def get_and_check(self):
        self.get_values()
        self.check_input()

    def get_values(self):
        self.poke = self.var.get()
        self.candies_total = self.Candies.get()
        self.p_amount = self.Poke_amount.get()

    def check_input(self):
        string1 = 'Please select a Pokemon from the dropdown menu'
        string2 = 'Please only enter numbers'
        if self.poke == 'Pokemon':
            messagebox.showinfo(self.screen_title, string1)
        elif not self.p_amount.isdigit() or not self.candies_total.isdigit():
            messagebox.showinfo(self.screen_title, string2)
        else:
            self.too_few_pokemon()

    def too_few_pokemon(self):
        candies_total = int(self.candies_total)
        p_amount = int(self.p_amount)
        evolve = int((candies_total - 1) / (self.pokedex[self.poke] - 1))
        candies_needed = (p_amount * (self.pokedex[self.poke] - 1)) + 1
        if p_amount <= evolve:
            n = 0
            while candies_needed <= candies_total:
                n = n + 1
                p_amount = p_amount + 1
                candies_needed = ((p_amount) * (self.pokedex[self.poke] - 1)) + 1
                candies_total = candies_total + 3
                evolve2 = int((candies_total - 1) / (self.pokedex[self.poke] - 1))
            string1 = '''            You have enough candies too evolve {0} {1},
            but you only have {2} {1} in storage and thus can only
            evolve {2} {1}.
            If you catch {3} more {1} you can now evolve {4} {1}.'''
            messagebox.showinfo(self.screen_title, string1.format(evolve, self.poke,
                                                                  self.p_amount,
                                                                  n, evolve2))
        else:
            self.too_much_pokemon()

    def too_much_pokemon(self):
        candies_total = int(self.candies_total)
        p_amount = int(self.p_amount)
        candies_needed = (p_amount * (self.pokedex[self.poke] - 1)) + 1
        m = 0
        while candies_total <= candies_needed:
            m = m + 1
            p_amount = p_amount - 1
            candies_needed = ((p_amount) * (self.pokedex[self.poke] - 1)) + 1
            candies_total = candies_total + 1
            evolve = int((candies_total - 1) / (self.pokedex[self.poke] - 1))
        string2 = 'Transfer {0} {1} so you can evolve a total of {2} {1}.'
        messagebox.showinfo(self.screen_title, string2.format(m, self.poke, evolve))


root = Tk()
app = PokeCalculator(root)
app.pack()
root.mainloop()

Upvotes: 3

Views: 13511

Answers (3)

juanpa.arrivillaga
juanpa.arrivillaga

Reputation: 95948

Consider the following example:

class A:
    a_class_attribute = 'A class attribute'
    def __init__(self, a):
        self.a_instance_attribute = a
    def amethod(self):
        print('a method')

class B(A):
    b_class_attribute = 'B class attribute'
    def __init__(self, b):
        self.b_instance_attribute = b
    def bmethod(self):
        print('b method')

Now, let's make out an instance of A:

In [2]: a = A('foo')

And note, we can see what attributes belong to the instance by checking the __dict__ of the instance,

In [3]: a.__dict__
Out[3]: {'a_instance_attribute': 'foo'}

This makes sense, since we have an instance attribute. What about the class?

In [7]: A.__dict__
Out[7]:
mappingproxy({'__dict__': <attribute '__dict__' of 'A' objects>,
              '__doc__': None,
              '__init__': <function self_oop_ex.A.__init__>,
              '__module__': 'self_oop_ex',
              '__weakref__': <attribute '__weakref__' of 'A' objects>,
              'a_class_attribute': 'A class attribute',
              'amethod': <function self_oop_ex.A.amethod>})

So a_class_attribute is merely an attribute that belongs to the class. Note, the methods belong to the class as well, you can think of a method as merely a class-object that happens to be a function. There is a little bit more magic that happens though. But first, you need to keep in mind that when you access an attribute on an instance, e.g. my_instance.some_attribute, python check's the objects namespace (i.e. the __dict__) first, and if it finds the attribute, it returns it. If it doesn't, it looks for the attribute in the namespace of the class! So, this is why we can access a class-attribute from a class and from an instance:

In [8]: A.a_class_attribute
Out[8]: 'A class attribute'

In [9]: a.a_class_attribute
Out[9]: 'A class attribute'

Now, what does this have to do with self? Well, the one magical thing that Python does is if a method is found when you access an attribute on an instance, if you call that method, it implicitly passes the instance as the first argument, i.e., what we conventionally call self. So,

In [10]: a.amethod()
a method

Is equivalent to:

In [11]: A.amethod(a)
a method

That is all there is to methods! It's just like any other function, just that Python does us a favor by implicitely passing the instance if the function is accesed from the instance. So note, we could do something like this:

In [17]: class Krazy:
    ...:     def use_pop(self):
    ...:         return self.pop()
    ...:     def pop(self):
    ...:         print("I am a Krazy pop")
    ...:

In [18]: k = Krazy()

In [19]: k.pop()
I am a Krazy pop

In [20]: mylist = [1,2,3,4,5]

In [21]: mylist.pop()
Out[21]: 5

In [22]: k.use_pop()
I am a Krazy pop

In [23]: Krazy.use_pop(mylist)
Out[23]: 4

Note, the use_pop method uses self.pop, and just as we would expect with any function, it calls the appropriate pop method of self, which when we use Krazy.use_pop(mylist) is the pop that belongs to the list object.

Finally, consider class B, which derives from A. B instances will have access to their own instance attributes, as well as the class attributes of the B class, AND the A class. That is inheritance in a nutshell:

In [25]: b = B('bar')

In [26]: b.__dict__
Out[26]: {'b_instance_attribute': 'bar'}

In [27]: B.__dict__
Out[27]:
mappingproxy({'__doc__': None,
              '__init__': <function self_oop_ex.B.__init__>,
              '__module__': 'self_oop_ex',
              'b_class_attribute': 'B class attribute',

              'bmethod': <function self_oop_ex.B.bmethod>})

In [28]: b.amethod()
a method

In [29]: b.bmethod()
b method

In [30]: b.a_class_attribute
Out[30]: 'A class attribute'

In [31]: b.b_class_attribute
Out[31]: 'B class attribute'

Of course, it gets more complicated with multiple inheritance, but for now, try to worry about simple inheritance situations.

Upvotes: 3

Peter Majko
Peter Majko

Reputation: 1321

1st - use of class is relevant even here. Almost everything in python is a object, even if you don't realize it. 2nd - There is a difference in class variables and instance variables. Think about class as a blueprint. This blueprint is accessed each time you want to create object based on the class. Variables declared inside class, but outside of any method (in your case e.g. pokedex) are called class variables. These hold the values for the blueprint. When you create an instance of the class (object) these class variables are transformed to instance variables of the object. Instance variables are defined in the init and other functions and you refer to any instance variable with self.variable . There is much to know about classes and instances and it can't be comperhanded in a simple stack overflow answer. Follow https://docs.python.org/3.6/tutorial/classes.html

Looking at your code you did very well. I would use much more "self" :) for example on L1, L2 or Calculate, because never know when I will want to reference them outside the method they were created. Good rule is that anything you think you could need in the future reference with self inside a class :)

At last but not least: Yes, your program could be without any custom class. Or you didn't need to use class or instance variables, but keep everything in globals and locals. It could also be without any method or function :) and it could be written in assembly :D there are many possibilities, but as you are doing object oriented programming, it is nice to use objects :) now your class is reusable and e.g. I could import it from your module to my program. :) Even if you are instantianting just one object from a class per program you usually use class as a blueprint for it. It is easier to understand the whole code and its parts alone too. Like in the real world - I am Peter, I am an instance of class Person. I have a right hand which I refer to as to self.right_hand (MY right hand, not just ANY hand) and this hand is an instance of class Hand. Class Hand has a method grab. My right hand can grab then too! I grab an apple with my right hand by calling self.right_hand.grab(apple).

Good luck with advancing!

Upvotes: 1

Will_of_fire
Will_of_fire

Reputation: 1189

self refers to the instance (object) of that class. There are 2 types of attributes in a class in python. Class attributes and instance attributes.

https://docs.python.org/2/tutorial/classes.html#class-and-instance-variables

Instance attributes are specific to the instance (object) of a class.
Class attributes are with respect to a class. These attributes are shared by all instances of a class.

In your code, pokedex, choices and screen_title are all examples of class attributes. Attributes like self.master, self.var, self.Pokemon are examples of instance attributes.

Is the use of a class even relevant here?

I would say YES. A class is a logical grouping of data and methods. It encapsulates some data and a few methods that can be performed on that data. Rather than just throwing random things together in a class, we try to create classes where there is a logical connection between things.

This might be an useful source: https://jeffknupp.com/blog/2014/06/18/improve-your-python-python-classes-and-object-oriented-programming/

Upvotes: 1

Related Questions