Skip to content

Completed the about_classes koan - #21

Open
stevendes wants to merge 1 commit into
develfrom
about_classes
Open

Completed the about_classes koan#21
stevendes wants to merge 1 commit into
develfrom
about_classes

Conversation

@stevendes

Copy link
Copy Markdown
Owner

Completed and ready for review!

@stevendes
stevendes requested a review from glpugaJanuary 17, 2020 17:48

@AlejoAsdAlejoAsd left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some questions and a number of comments to help make some things a little bit more clear.

self.assertEqual(__, getattr(fido, "_name"))
self.assertEqual('Fido', getattr(fido, "_name"))
# getattr(), setattr() and delattr() are a way of accessing attributes
# by method rather than through assignment operators

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you think of a situation where you would use getattr instead of accessing the attribute directly?

Put in another way, what does getattr let you do that accessing directly does not?

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the main benefit is the fact that you can pass getattr a default value, so if for some reason the attribute it's not correctly taken, you can have other instead of going directly in a Exception.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that is one very good reason.

The other big advantage is that you can get the properties of things using strings. This gives you a lot of flexibility because you don't need to know the name of the parameter beforehand. As an example, here's a simple function that gets you the values of all properties you request.

In [1]: defget_properties(object, properties): ...: """  ...: Gets the  ...: :param object: Object from which to extract values.  ...: :param properties: Properties to extract values for. ...: """ ...: values= [] ...: forpropertyinproperties: ...: values.append(getattr(object, property, None)) ...: ...: returnvalues ...: In [2]: classTest(object): ...: def__init__(self, a, b, c): ...: self.a=a ...: self.b=b ...: self.c=c ...: In [3]: t=Test(1, 2, 3) In [4]: get_properties(t, ('a', 'c', 'error')) In [5]: print(v) [1, 3, None]

self.assertEqual('Fido', fido.__dict__["_name"])
# Yes, this works here, but don't rely on the __dict__ object! Some
# class implementations use optimization which result in __dict__ not
# showing everything.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is not mentioned, something important to understand is that Python class instances are basically dicts. You can check the __dict__ property to see and access the contents of a class instance.

Any attribute that start with double underscores have their names mangled (changed) and will not be accessible directly. You can print the contents of __dict__ to see their new names.

# Define a Test class>>>classTest(object):
... def__init__(self, test):
... self.__test=test
... # Get an instance of the Test class>>>t=Test('test')
# __test is not accessible.>>>t.__testTraceback (mostrecentcalllast):
File"<stdin>", line1, in<module>AttributeError: 'Test'objecthasnoattribute'__test'# Not even when accessing directly through __dict__>>>t.__dict__['__test']
Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>KeyError: '__test'# Checking __dict__ we can see that __test is stored as _Test__test instead>>>t.__dict__
{'_Test__test': 'test'}
# If we try to access t._Test__test we get our value>>>t._Test__test'test'

@@ -10,10 +10,10 @@ class Dog(object):

def test_instances_of_classes_can_be_created_adding_parentheses(self):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before I get deep into it, I want to set the terminology typically used when talking about classes. This is not Python specific, but it's important that we're using the same terminology so that the concepts are clearly understood.

In programming, you typically have variables and functions. There can be different types of variables (constants, static, etc.) and different types of functions (virtual, static, etc.), but the larger categories are variables and functions. Some programming languages make the distinction between functions and procedures based on if they return a value or not, respectively. We will not make this distinction but I thought I would mention it.

Classes use different words to refer to variables and functions. A variable that belongs to a class is called a property, and a function is called a method. Attribute is a way of generically saying "something that belongs or defines a class". Both properties and methods are considered attributes of a class.

CodeClass
variableproperty
function/proceduremethod

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't' know this!


name = property(get_name, set_name)

#Comment: I'm not that sure on how property works

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

property(getter, setter) is one of Python's way of implementing getters and setters. If you're not familiar with the concept of getters and setters, you can find an explanation here. The example is in Java but the same concepts apply.

Getters and setters allow you to have control on how class properties are accessed and manipulated and are typically used to expose private properties in a controlled way. This allows you to even apply some logic before setting an attribute.

As an example, consider a class with a property that should always be a string.

In [1]: classTest(object):
...: def__init__(self):
...: # This value should ALWAYS contain a string
...: self._string=''
...: ...: defget_string(self):
...: returnself._string
...: ...: defset_string(self, value):
...: """ ...: This setter makes sure that whatever value is set ...: to `self.string` is converted to a string. ...: """
...: self._string=str(value)
...: ...: string=property(get_string, set_string)
...: In [2]: # Create an instance
...: t=Test()
In [3]: # Check the type and contents of string
...: print(t.string.__class__.__name__, t.string)
('str', '')
In [4]: # Try to set the value of string to a number
...: t.string=1234In [5]: # Check the type and contents of string
...: # Notice that it's a string and not a number
...: print(t.string.__class__.__name__, t.string)
('str', '1234')

Notice that we're getting and setting the value of the private property self._string, but we never access that property directly; all our calls were to t.value and not t._value. Getters and setters act as guards that control how private properties are accessed.

As another example, you can create a private attribute whose setter always raises an error. This would prevent someone from directly changing the value of a property, and force them to use some other class method to update the value.

In [1]: classTest(object):
...: def__init__(self):
...: # This value should not be accessed directly
...: self._value=0
...: ...: defget_value(self):
...: returnself._value
...: ...: defset_value(self, value):
...: """ ...: The value should never be updated directly. ...: """
...: raiseException('self._value cannot be accessed directly.')
...: ...: defadd(self, amount):
...: """ ...: Increases this instance's value by a certain amount. ...: """
...: self._value+=amount
...: ...: defsubtract(self, amount):
...: """ ...: Decreases this instance's value by a certain amount. ...: """
...: self._value-=amount
...: ...: # Assign a getter and setter for self._value
...: value=property(get_value, set_value)
...: In [2]: # Create an instance
...: t=Test()
In [3]: # Check the initial contents of value
...: print(t.value)
0In [4]: # Try to assign a value directly
...: try:
...: # This will fail
...: t.value=100
...: exceptExceptionase:
...: print(str(e))
...: self._valuecannotbeaccesseddirectly.
In [5]: # Increase the value by 10
...: t.add(10)
...: print(t.value)
...: 10In [6]: # Decrease the value by 5
...: t.subtract(5)
...: print(t.value)
...: 5

Python doesn't really have the concept of private attributes. Prefixing an attribute with an underscore only suggests that it should not be accessed directly, but does not prevent someone from doing so. The guards we set in the previous example can easily be bypassed by accessing the private attribute t._value directly.

In [1]: classTest(object):
...: def__init__(self):
...: # This value should not be accessed directly
...: self._value=0
...: ...: defget_value(self):
...: returnself._value
...: ...: defset_value(self, value):
...: """ ...: The value should never be updated directly. ...: """
...: raiseException('self._value cannot be accessed directly.')
...: ...: defadd(self, amount):
...: """ ...: Increases this instance's value by a certain amount. ...: """
...: self._value+=amount
...: ...: defsubtract(self, amount):
...: """ ...: Decreases this instance's value by a certain amount. ...: """
...: self._value-=amount
...: ...: # Assign a getter and setter for self._value
...: value=property(get_value, set_value)
...: In [2]: # Create an instance
...: t=Test()
In [3]: # Access the private value directly
...: t._value=1000
...: print(t.value)
...: 1000

Coming back to property, it provides a way of generating an object that acts as a getter and setter for a property. property(<getter function>, <setter function>) returns an object that calls the getter function when the value is accessed, and the setter function when a value is assigned to it.

# Accessing a `property` object calls the getter function`t.value`calls`t.get_value()`# Assigning to a `property` object calls the setter function`t.value = 100`calls`t.set_value(100)`

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great explanation!

#
return __
name = str(self._name)
return name

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is correct. You can also simply return the casted value.

def__str__(self):
returnstr(self._name)

fido = self.Dog6("Fido")

self.assertEqual(__, fido.get_self()) # Not a string!
self.assertEqual(fido, fido.get_self()) # Not a string!

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it clear why fido and fido.get_self() are equal?

Will the following pass?

fido1=self.Dog6("Fido")
fido2=self.Dog6("Fido")
self.assertEqual(fido1, fido2.get_self())

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fido is the object created and the method get_self returns self, which refers again to the object itself.

self.assertEqual(fido1, fido2.get_self()) would output an error because the fido2.get_self()" is reffering to the object fido2, which is different from the object fido1`, even if they are come from the same class.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right!


self.assertEqual(__, str("STRING"))
self.assertEqual(__, repr("STRING"))
#In the last one I'm not sure why the "" appears twice No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This page goes into the differences between str and repr. Give it a read and let me know if it clear things for you. If not, we can discuss about it.

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand it, but we can discuss it next to see if what I understand it's correct, haha.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@stevendes@AlejoAsd