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 fromobject
, when you're not using egnamedtuple
.
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.