简体   繁体   中英

User input isn't passed to __init__

I am trying to create a polygon class that returns the area and perimeter when given the sides and length from user input. However, it doesn't take the two variables I'm trying to pass into the __init__ method. The sides and length must be private and must be received through user input.

import math

class Polygon:
    use = (input("Enter in the sides and length of sides seperated by a comma"))
    ans = use.split(",")
    __numofSides = ans[0]
    __sideLength = ans[1]

    def __init__(self, __numofSides, __sideLength):
        self.__numofSides = __numofSides
        self.__sideLength = __sideLength

    def get__numofSides(self):
        self.__numofSides = ans[0]
        return __numofSides

    def get__sideLength(self):
        self.__sideLength = ans[1]
        return __sideLength

    def perimeter(self, __numofSides,__sideLength):
        peri = self. __numofSides * self.__sideLength
        return peri

    def area(self, __numofSides, __sideLength):
        area = (((__sideLength **2) * __numofSides) / (tan(4) *(math.pi/__numofSides))) 
        return area

    def __str___(self,):
        print("Number of Sides: {}\n Length of Sides: {}\n" \
              "Perimeter is: {}\n Area is: {}".format(__numofSides,__sideLength,peri,area))

def main():
    p1 = Polygon()
    p1.perimeter()
    p1.area()
    p1.__str__()

main()

You seem to have a fundamental misunderstanding of how OOP works in Python. When you instantiate a class, the __init__() method is called, and usually you assign the arguments given to instance variables, like so:

class Pet(object):

    def __init__(self, name):
        self._name = name # argument stored in self._name

Then in whatever methods you'd like to make use of those, you can access them through the instance:

def get_name(self):
    return self._name

Notice that all this method does is return self._name back to the caller. There's a common idiom for this situation using decorators :

@property
def name(self):
    return self._name

The advantage of this compared to get_name() is twofold. First, you can call the method without parentheses as if it was an instance variable:

my_pet = Pet('Rosita')
print(my_pet.name) 

>> Rosita

Secondly, if the user later tried to overwrite it with something else Python would raise an AttributeError:

my_pet = Pet('Rosita')
my_pet.name = 'Maggie'

>> Traceback (most recent call last):
>> File "<stdin>", line 1, in <module>
>> AttributeError: can't set attribute

In regards to your __repr__ method, what I think you meant was this:

def __repr__(self):
    return "<Polygon sides={}; length={}; perimeter={}; area={}>".format(
        self.sides, self.side_length, self.perimeter, self.area)

__repr__ is called when you do print(my_polygon) or str(my_polygon) so it should return a string.

Finally, you may have noticed I have named the instance variables with one leading underscore rather than two. If you want the users of your class to know that a particular instance variable is "private" and they should not mess with it, it's better to prefix its name with only one underscore. The reason for that is that it allows you to have acessor methods and instance variables with the same name, while at the same time avoiding name mangling . Names with two leading underscores are mangled, and thus generally not recommended.

Taking all of this into account, here's a rewrite of your code:

import math

class RegularPolygon(object):

    def __init__(self, sides, side_length):
        self._sides = sides
        self._side_length = side_length

    @property
    def sides(self):
        return self._sides

    @property
    def side_length(self):
        return self._side_length

    @property
    def perimeter(self):
        return self.sides * self.side_length

    @property
    def area(self):
        return ((self.side_length**2 * self._sides) 
                / (4 * math.tan(math.pi / self.sides)))

    def __repr__(self):
        return "<Polygon sides={}; length={}; perimeter={}; area={}>".format(
            self.sides, self.side_length, self.perimeter, self.area)


if __name__ == '__main__':
    poly = RegularPolygon(5, 7)
    print(poly)

Your problem is that you're not passing anything to __init__ , you're creating class level variables when you do this:

class Foo:
    x = 42
    y = input("Don't do this, this is bad")

These will only be called once per program* as well, so you'll never be able to provide different values, if that's something you need. If you want to pass arguments to a function, you do that when you create the instance of the class:

class Polygon:
    def __init__(self, num_of_sides, side_length):
        self._num_of_sides = num_of_sides
        self._side_length = side_length

As others have mentioned, private variables in Python aren't actually inaccessible, though there are ways to enforce that they are immutable, as jonrsharpe pointed out it his answer. You could also investigate __slots__ for other options.

* Per import, which is usually once per program, but there are ways you can get around that, but that's even worse. Unless you're going for an obfuscated Python contest, don't do that.

Here is a quick code review of your code (no math consideration):

You can inherit from object (to be compatible with Python 2):

class Polygon(object):

Simplify the parameters names, don't use double underscores:

    def __init__(self, sides, length):
        self.sides = sides
        self.length = length

Use the instance variable self.sides and self.length , drop the parameters:

    def perimeter(self):
        return self.sides * self.length

Replace tan() by math.tan()

    def area(self):
        return ((self.length ** 2) * self.sides) / (math.tan(4) * (math.pi / self.sides))

In your main() function:

(With Python 2, use raw_input instead of input .)

use = input("Enter in the sides and length of sides separated by a comma: ")
ans = use.split(",")

Convert string values to int

sides = int(ans[0])
length = int(ans[1])

p1 = Polygon(sides, length)

Use print() function to print the result

print(p1.perimeter())
print(p1.area())

Here is how I would write this:

from collections import namedtuple
from math import pi, tan

class Polygon(namedtuple('Polygon', 'sides,length')):

    PROMPT = 'Enter in the number and length of sides, separated by a comma'

    @property
    def perimeter(self):
        return self.sides * self.length

    @property
    def area(self):
        return (self.sides * (self.length ** 2)) / (4 * tan(pi / self.sides))

    @classmethod
    def from_input(cls):
        return cls(*map(int, input(cls.PROMPT).split(',')))

Why? Because:

  • inheriting from a namedtuple makes the instance immutable, so you can't reassign sides and length after initial creation, while giving you sensible equality comparison and __repr__ formatting for free:

     >>> square = Polygon(4, 1) >>> square Polygon(sides=4, length=1) >>> square.sides = 5 Traceback (most recent call last): File "python", line 1, in <module> AttributeError: can't set attribute 
  • Using @property means you can easily access the calculated attributes, again in a read-only way:

     >>> square.area 1.0000000000000002 >>> square.perimeter 4 >>> square.area = 7 Traceback (most recent call last): File "python", line 1, in <module> AttributeError: can't set attribute 
  • Using @classmethod keeps the logic for creating an object from user input within the class, where it belongs:

     >>> hexagon = Polygon.from_input() Enter in the number and length of sides, separated by a comma 6,2 >>> hexagon Polygon(sides=6, length=2) >>> hexagon.area 10.392304845413264 >>> hexagon.perimeter 12 

    In your current implementation, your input runs once when the class is defined, not when the user actually wants to create an instance.

Note : I've assumed you're using Python 3.x - if not you should use raw_input . Also your classes should inherit from object , when you're not using eg namedtuple .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM