-1

What I'm trying to do: executing the script, I will have to type in two numbers and it will compare them. I want to be asked a total of 3 times. The first time I will type in 10 and 5, second time 5 and 10 and the third time I will type in 10 and 10 to get all three possible answers.

My problem with the first code is: getnumbers() is being called inside of Checknumbers(). I want to create functions and a loop and strictly ONLY execute the functions inside a dedicated loop and not within another function.

I want everything clean cut and no reference of any function inside another function, I don't want to use any global variables either.

I solved this with a class but I'm not really sure if I'm butchering the language or if this is common practice. Also I have to reference the class inside the checknumbers() function.

First solution:

def getnumbers():
    x = input("Enter the X number: ")
    y = input("Enter the Y number: ")
    return x, y

def checknumbers():
    x, y=getnumbers()
    if   x > y:
        print(f'x is larger then y: x is {x} and y is {y}')
    elif y > x:
        print(f"y is larger then x: x is {x} and y is {y}")
    elif y == x:
        print(f"x is equal to y: x is {x} and y is {y}")     
    else:
        print("Dont know mate")


n = 0
while(n < 3):
    checknumbers()
    n += 1

This is the variant with the class:

class ui:
    x = input("Enter the X number: ")
    y = input("Enter the Y number: ")


def checknumbers():
    if   ui.x > ui.y:    
        print(f'x is larger then y: x is {ui.x} and y is {ui.y}')
    elif ui.y > ui.x:
        print(f"y is larger then x: x is {ui.x} and y is {ui.y}")
    elif ui.y == ui.x:
        print(f"x is equal to y: x is {ui.x} and y is {ui.y}")     
    else:
        print("Dont know mate")

n = 0
while(n < 3):
    checknumbers()
    n += 1

Ideal solution, so both functions getnumbers() and checknumbers are clean cut independent of each other and they are being called inside the while loop, the problem is that x and y from the getnumbers() function are unknown to checknumbers.

The requirement is: I cant have any reference to any other function inside my functions, how do I pass x and y without referencing them?:

def getnumbers():
    x = input("Enter the X number: ")
    y = input("Enter the Y number: ")
    return x, y

def checknumbers():
    if   x > y:
        print(f'x is larger then y: x is {x} and y is {y}')
    elif y > x:
        print(f"y is larger then x: x is {x} and y is {y}")
    elif y == x:
        print(f"x is equal to y: x is {x} and y is {y}")     
    else:
        print("Dont know mate")


n = 0
while(n < 3):
    getnumbers()
    checknumbers()
    n += 1
smci
  • 32,567
  • 20
  • 113
  • 146
Barry
  • 311
  • 3
  • 13
  • Welcome to Python and SO! Your question is essentially about why we typically use **instance attributes** (not class attributes), and **how to decompose the class into methods and instance attributes** to get the job done. See e.g. [Python Class Members](https://stackoverflow.com/questions/12409714/python-class-members) and other similar questions. – smci Nov 29 '18 at 13:07

3 Answers3

1

If you don't want to call getnumbers() within checknumbers(), the only alternative that makes sense is to pass the numbers as parameters to checknumbers().

def getnumbers():
    x = int(input("Enter the X number: "))
    y = int(input("Enter the Y number: "))
    return x,y

def checknumbers(x, y):
    if x > y:
        # etc.

...

for _ in range(3):
    x,y = getnumbers()
    checknumbers(x,y)

That at least has better separation of concerns.

khelwood
  • 55,782
  • 14
  • 81
  • 108
  • Passing it as parameters doesn't work: Traceback (most recent call last): File "/Users/bla/Documents/Python/test4.py", line 23, in checknumbers() TypeError: checknumbers() missing 2 required positional arguments: 'x' and 'y' – Barry Nov 29 '18 at 12:40
  • @Barry Passing arguments **does** work if you do it right. The error message says you're still trying to call the function as `checknumbers()`. It needs to be `checknumbers(x,y)`. – khelwood Nov 29 '18 at 12:42
  • indeed I forgot the (x,y) inside checknumbers(x,y) inside the loop. But now I get: Traceback (most recent call last): File "/Users/bla/Documents/Python/test4.py", line 23, in checknumbers(x,y) NameError: name 'x' is not defined – Barry Nov 29 '18 at 12:45
  • And now you omitted the preceding line, which is `x,y = getnumbers()`. See the code in my answer. – khelwood Nov 29 '18 at 12:48
1
  • You're getting confused between classes and instances, and between class attributes and instance attributes. (Read e.g. this)
    • The OO way to store state variables (like x,y) so you don't have to pass them around between function(/method) calls is to make them instance attributes. (Not class attributes, as you were doing. Don't worry, I did that too when I first learned Python).
    • So we declare a class UI; we will access its instance attributes as self.x, self.y inside its methods.
    • Don't try to directly do stuff on class UI. You must instantiate it first: ui = UI(). You should follow the Python convention that class names are Uppercase/CamelCase: UI, instance names are lowercase e.g. ui, ui1, ui2...
    • You were trying to put code directly into the class definition of UI, not define methods and put the code in that, and your UI class didn't even have an __init__()
    • Methods are functions inside a class, they always have a first argument self. If they didn't, the method wouldn't be able to access the rest of the class(!)
  • Now that we cleared that up, there are a couple of ways to decompose the methods to do what you want to do:
    1. Have an empty __init__() (you could just make its body do pass). Have get_numbers() and check_numbers() be separate methods, which you manually call in-order. This is what I show below and is closest to what you said you want ("I want no reference to any function inside another function"), but is bad decomposition - what if the client called check_numbers() before get_numbers()? It would blow up on TypeError since __init__() initializes x,y with None.
    2. Better would be to have __init__() call the method get_numbers() under-the-hood to guarantee the instance gets properly initialized. (We could always call get_numbers() again later if we want to input new numbers). That's easy to change, I leave that to you.
    3. In approach 1., we had to initialize the instance members to something (otherwise trying to access them in check_numbers() will blow up). So we initialize to None, which will deliberately throw an exception if we compare. It doesn't really matter, this is just bad decomposition to not have __init__() properly initialize the instance (and call whatever methods it needs to to get that done). That's why approach 2. is better. Generally you should always have an __init__() that initializes the class into a known state, so that any other method can safely be called.

Code:

class UI:
    def __init__(self, x=None, y=None):
        self.x = x
        self.y = y
    def get_numbers(self):
        self.x = input("Enter the X number: ")
        self.y = input("Enter the Y number: ")
    def check_numbers(self):
        """This is bad decomposition because if the client calls check_numbers() before get_numbers(), the NoneType will throw a TypeError"""
        if   self.x > self.y:    
            print(f'x is larger then y: x is {self.x} and y is {self.y}')
        elif self.y > self.x:
            print(f'y is larger then x: x is {self.x} and y is {self.y}')
        elif self.y == self.x:
            print(f'x is equal to y: x is {self.x} and y is {self.y}')     
        else:
            print("Don't know mate")

# Declare an instance and reuse it three times    
ui = UI()
for n in range(3):
    ui.get_numbers()
    ui.check_numbers()

Also, some minor stylistic points:

  • you don't need a while-loop for a simple counter: n = 0, while(n < 3) ... n += 1 . A for-loop is a one-liner: for n in range(3):
  • good Python style (see PEP-8) is to name the methods lower_case_with_underscores, thus get_numbers(), check_numbers()
  • a great top-down way to design a class is to write its method signatures first, think about what methods and attributes you'll need and how they'll work together. Example: "get_numbers() will get the user input, hence we'll need attributes self.x,y to store the numbers so check_numbers() can access them". And this way you should hit any problems with class design before you've written a wall of code.
smci
  • 32,567
  • 20
  • 113
  • 146
0
  • I don't see anything wrong with the first solution (except for the fact that getumbers returns strings in Python 3) . Classes are not the solution for every problem

  • I cant have any reference of any other function inside my functions, how do I pass x and y without referencing them?

    It's impossible to pass something without referencing it. Even if x and y were global variables (which is much worse than your current design) the using function would need to reference them.

I don't understand why you are under the impression that calling a function inside another function is bad or wrong design.

DeepSpace
  • 78,697
  • 11
  • 109
  • 154
  • I want to have them absolute separate. My ideal way would be having all related functions in one file and having the loop in another. Its quite irritating to reference to other functions within a function. I don't feel its right. – Barry Nov 29 '18 at 12:42
  • @Barry "Its quite irritating to reference to other functions within a function" You'd have to let that go. It's quite impossible to write a functioning, clear code where no function is calling another function. FYI, `print` is a function (in Python 3 at least). You can't really use it unless you call it from another function. – DeepSpace Nov 29 '18 at 12:44