Reputation: 615
I'm trying to understand OOP (specifically, in Python 3). Here's a basic class template:
class Lines:
"""
Arguments: list of coordinates
"""
def __init__(self, points):
self.x1 = points[0]
self.y1 = points[1]
self.x2 = points[2]
self.y2 = points[3]
I pass starting and ending (x,y) coordinates in a list to the class.
However, I want to also add a length, slope, and y-intercept attribute to the objects of this class Lines
(Note: I do not want them as methods). Here are a couple ways I found to do so-
class Lines:
def __init__(self, points):
self.x1 = points[0]
self.y1 = points[1]
self.x2 = points[2]
self.y2 = points[3]
##Create length, slope, and y_intercept attributes
self.length = round(((self.x2-self.x1)**2 + (self.y2-self.y1)**2) ** 0.5, 2)
self.slope = round((self.y2 - self.y1)/(self.x2-self.x1),1)
self.y_intercept = self.y1 - self.slope*self.x1
class Lines:
def __init__(self, points):
self.x1 = points[0]
self.y1 = points[1]
self.x2 = points[2]
self.y2 = points[3]
@property
def length(self):
return round(((self.x2-self.x1)**2 + (self.y2-self.y1)**2) ** 0.5, 2)
@property
def slope(self):
return round((self.y2 - self.y1)/(self.x2-self.x1),1)
@property
def y_intercept(self):
return self.y1 - self.slope*self.x1
I don't prefer the first method (using init()
) because it just looks a little cumbersome to fit calculation-based code like that in there. My understanding is that the init method is just there to initialize the attributes of the object and not for much more than that.
I can use the property decorator and then access the objects' slopes etc like so
line1 = Lines([0,0,2,4])
line1.slope
However, when I print line1.__dict__
, they are not listed in the attributes available.
What I really want to know is if there other (more commonly-used, Pythonic) ways to set attributes to the objects(ex: slope) based on the initial attributes(ex: x1,y1). I thought this would be a very common problem (i.e., having a set of basic attributes as input and then setting other more advanced attributes based on them to the same object) but I've not found not much on it on here. I'm sure I'm missing some simple yet elegant solution here.
Thank you in advance. I hope I was clear with my question.
Upvotes: 1
Views: 74
Reputation: 191
You can do that in a couple ways, maybe they are not "common", but they are still Pythonic
.
__dict__
Consider this simple code:
class Lines:
pass
line1 = Lines()
Now, I want to change the attribute xy
, I mean set it as 10
. Once Python is dynamic and everything in Python is an object, I can do it, look:
class Lines:
pass
line1 = Lines()
line1.xy = 10
print(f"line1.xy = {line1.xy}") #Output: line1.xy = 10
What? How can I set an attribute that doesn't exist?! It simple, the __dict__
. It stores every attribute that is setted through the instance.
class Lines:
pass
line1 = Lines()
line1.xy = 10
print(f"xy = {line1.xy}") #Output: xy = 10
print(f"__dict__ = {line1.__dict__}") #Output: __dict__ = {'xy': 10}
So, if __dict__
stores attributes that was setted through the instance, how is the __dict__
state before I set any attribute?! As you can deduce, it starts empty.
So you should remember that the __dict__
doesn't show the attributes available, but the attributes that was setted, and it doesn't matter if those attributes exist or not in the main class
However, when I print
line1.__dict__
, they are not listed in the attributes available.
In fact all attributes in __dict__
are available through it, but if you try to get any other attribute that is not in __dict__
, but was definided in the class you can get it. HOW? Take a look:
class Lines:
x1 = 3
x2 = 7
line1 = Lines()
print(f"__dict__ = {line1.__dict__}") #Output: __dict__ = {}
print(f"x1 = {line1.x1}") #Output: x1 = 3
print(f"x2 = {line1.x2}") #Output: x2 = 7
print(f"__dict__ = {line1.__dict__}") #Output: __dict__ = {}
line1.x1 = 9 #Setting x1, so it will be stored at __dict__
print(f"__dict__ = {line1.__dict__}") #Output: __dict__ = {'x1': 9}
print(f"x1 = {line1.x1}") #Output: x1 = 9
print(f"x2 = {line1.x2}") #Output: x2 = 7
print(f"test = {line1.test}") #Output: AttributeError
What is python doing here?! Python first goes to __dict__
and try to find the attributes x1
and x2
, so if it doesn't find them there, then it goes to the main class
, if it finds them, it returns them for you, if not Python Raise an AttributeError
.
__dict__
is awesome, it enables you to do lots of things. Since any attribute value is stored in __dict__
you can get its value directly by it, look:
example = line1.__dict__['x1']
print(f"__dict__['x1'] = {example}") #Output: __dict__['x1'] = 9
The discurss about pythonic code is really insteresting, but we have to ask ourselves if those code are python or not, what I mean if some code are python and there is no "improvisation"
, so it's pythonic, even if that code are not so commum, for instance the methods getattr()
, setattr()
and delattr()
, some times the use of them are more insterreing than the "dotted notation"
, because they are more reliable to understand, especially when you have a deep dive in what are doing and you are debugging. (At Least I think like that)
So everything goes around what you are doing and and what you want to do. Imagine you have a high quality code and you use not so common functions, isn't your code Pythonic?! To be clear imagine you are writing a count that is increased every time a transaction is did, so instead you use the common way count += 1
, you go beyond and use the itertools.count()
, without doubt you have a high quality code, even if generally people don't use that module, you can see more about it here: itertools — Functions creating iterators for efficient looping.
So let's go to the code: as you can see it's another way to solve this problem.
class Lines:
def __init__(self, points):
self.x1 = points[0]
self.y1 = points[1]
self.x2 = points[2]
self.y2 = points[3]
self.length = self._length()
self.slope = self._slope()
self.y_intercept = self._y_intercept()
def _length(self):
return round(((self.x2-self.x1)**2 + (self.y2-self.y1)**2) ** 0.5, 2)
def _slope(self):
return round((self.y2 - self.y1)/(self.x2-self.x1),1)
def _y_intercept(self):
return self.y1 - self.slope*self.x1
line1 = Lines([0,0,2,4])
print(line1.length)
print(line1.slope)
print(line1.y_intercept)
Your solution using Read-Only property
are really interesting, actually it even enables to you work with cache too (if we write, of course!), so you don't need calc you attributes length
, slope
and y_intercept
every time you call those variables, you can calc them values only when you change the value of the attribute points
.
I hope help you, bye!
Don't use "improvisation", use Python.
Upvotes: 2