Problems with tuples

I am doing OOP in Python and I am having problems when the user enters a tuple as one of the arguments. Here's the code:

class Height:
    def __init__(self,ft,inch=0):
        if isinstance(ft,tuple):
            self.feet = ft
            self.inches = inch
        elif isinstance(ft,int):
            self.feet = ft // 12
            self.inches = ft % 12
    def __str__(self):
        return str(self.feet) + " Feet " + str(self.inches) + " Inches"
    def __repr__(self):
        return "Height (" + str(self.feet * 12 + self.inches) + ")"

      

I tried to think that initializing inches to zero would help me, but that didn't work. Tuples also don't support indexing, so the option doesn't exist either. I feel like the answer is simple and I just overdo it. The validation code I'm using is:

from height import *
def test(ht):
    """tests the __str__, __repr__, and to_feet methods for the height
   Height->None"""
    #print("In inches: " + str(ht.to_inches()))
    #print("In feet and inches: " + str(ht.to_feet_and_inches()))
    print("Convert to string: " + str(ht))
    print("Internal representation: " + repr(ht))
    print()
print("Creating ht1: Height(5,6)...")
ht1 = Height(5,6)
test(ht1)
print("Creating ht2: Height(4,13)...")
ht2 = Height(4,13)
test(ht2)
print("Creating ht3: Height(50)...")
ht3 = Height(50)
test(ht3)

      

My code works as expected when injected int

, but again, I cannot figure out when a tuple is injected. Any ideas?

+3


source to share


3 answers


Are you really passing a tuple? It seems to me that your constructor should be simple:

def __init__(self, ft, inch=0):
   self.ft = int(ft)
   self.inch = int(inch)

      

This works if you create your object with any of these (because you have a default value for the argument inch

):

foo = Height(6)
bar = Height(6, 3)
baz = Height("5", 3)

      



and so on .. Note that in the second and third instances, you still don't get the tuple. To get a tuple, you need to call it like this:

foo2 = Height((6, 3))

      

or use the '*' operator in a constructor declaration.

+4


source


Tuples support indexing. They do not support index assignment because they are immutable, but they do support index access.

Your code doesn't work because your code is designed to accept a tuple like ft

. It does nothing for int

, which is a completely different argument. All you have to do is use indexing instead:

class Height:
    def __init__(self,ft,inch=0):
        if isinstance(ft,tuple):
            self.feet = ft[0]
            self.inches = ft[1]
        elif isinstance(ft,int):
            self.feet = ft // 12
            self.inches = ft % 12
    def __str__(self):
        return str(self.feet) + " Feet " + str(self.inches) + " Inches"
    def __repr__(self):
        return "Height (" + str(self.feet * 12 + self.inches) + ")"

      

However, this only works if you actually pass in a tuple, which your test code never does! Height(5,6)

is simply passed on two arguments. To enter a tuple, you need Height((5,6))

.

As others have noted, the way you call it actually makes more sense than using a tuple. To get this working, you just need to see if your second argument is being used (and let's change to v1 and v2 for reasons I'll get later):

def __init__(self, v1, v2=None): # set v2 to None, and then check to see if it isn't None
    if v2 is not None:
        self.feet = v1
        self.inches = v2
    else:
        self.feet = v1 // 12
        self.inches = v1 % 12

      



But this has a usability problem: the meaning of the first argument depends on whether the second argument is there! That's why its name is ft

especially bad: if the second argument is not specified, it is actually in inches!

One solution is to have both arguments named, allowing the user to choose the ones they want to use:

def __init__(self, feet=0, inches=0):
    self.feet = feet + inches // 12
    self.inches = inches % 12

      

Then you have many options and less chance of confusion:

In [4]: str(Height(5,0))
Out[4]: '5 Feet 0 Inches'

In [5]: str(Height(0,5))
Out[5]: '0 Feet 5 Inches'

In [6]: str(Height(5))
Out[6]: '5 Feet 0 Inches'

In [7]: str(Height(inches=25))
Out[7]: '2 Feet 1 Inches'

In [8]: str(Height(feet=3,inches=25))
Out[8]: '5 Feet 1 Inches'

      

+1


source


You have a lot of problems with your code, from formatting to functionality. The most important thing is:

elif isinstance(ft,int):
    self.feet = ft // 12
    self.inches = ft % 12

      

therefore, if the user passes a number in feet , is it actually in inches ? Why?


I think you are fundamentally wrong about what happens when you call, for example.

Height(5, 10)

      

Internally, __init__

it effectively installs ft = 5

and inch = 10

is not ft = (5, 10)

.


This is how I would do it:

class Height:

    def __init__(self, feet=0, inches=0):
        self.feet, self.inches = feet, inches % 12
        self.feet += inches // 12

    def __str__(self):
        return '{0.feet}\' {0.inches}"'.format(self)

    def __repr__(self):
        return 'Height({0.feet}, {0.inches})'.format(self)

      

This allows any of the following to work correctly:

>>> Height()
Height(0, 0)
>>> Height(5, 0)
Height(5, 0)
>>> Height(5)
Height(5, 0)
>>> Height(inches=60)
Height(5, 0)
>>> print(str(Height(inches=123)))
10' 3"

      

Note also the use str.format

and compliance of the style guide . Another alternative would be to raise the error if inches > 11 and feet > 0

, as it is a potentially erroneous input. You can also see the emulation of numeric types .

+1


source







All Articles