• Skip to primary navigation
  • Skip to main content
bucket-of-data-transparent

bucketofdata.com

Pouring Data into Actionable Insights

  • Show Search
Hide Search

Objects in Python Have Types, Identities and Functionality

Silver Spade · November 18, 2024 · Leave a Comment

In the context of real, normal daily life, an object is a material thing that can be seen and touched. You use your eyes to process information about the object (known as perception) and hands to interact with it.

Python, like the real world, also works with objects. An object in computer science is a data structure that can be perceived and interacted with. We define a blueprint for it known as a class. In this blueprint we declare its properties and abilities, or functionality, so it can be perceived, we can be aware of it and know it exists. We can operate on (or interact with) these declarations using methods.

The difference between objects in the real world and Python is that the properties and abilities for objects in the real world, such as the seed of a tree or the rocks by the soil, are taken for granted. We expect a seed to grow into a tree by adding water to it (which would have been a method to alter the tree’s height) but we cannot expect a rock to swim.

The origination of real-world objects and their qualities and abilities is an existential issue that is out of the scope of this article…

But in Python, we are able to declare the object’s existence, its qualities and abilities. We can create swimming rocks:

# swimmingrocks.py
# For more information on the 'self' parameter,
# refer to 9.3.4 in the docs: https://docs.python.org/3/tutorial/classes.html#method-objects

class SwimmingRock:
    """A collection of rocks that can swim."""
    
    # This is a constructor.
    # It builds the rock's properties when it's instantiated!
    # If no operands are passed, the object assumes the default assignments.
    def __init__(self, color='Gray', swim_stroke='Front crawl'):
        self.color = color
        self.swim_stroke = swim_stroke

    # This is a procedure.
    # It can change the rock's color.
    # This couldn't be possible in the real world, but can be in Python!
    def change_color(self, new_color):
        self.color = new_color

    # This is a function.
    # It's a procedure that returns a value.
    def talk(self, say=''):
        # I wouldn't want my rock talking.
        # No matter what value is passed for the parameter say,
        # The function always returns an empty string!
        return ''

# By including this common conditional, the code will only run
# if the file is run as is (i.e., python swimmingrocks.py)
# and not as an imported module (i.e., import swimmingrocks).
if __name__ == '__main__':
    my_pet_rock = SwimmingRock()
    
    # This is equivalent to SwimmingRock.talk(my_pet_rock)
    # as documented in 9.3.4 on the 'self' parameter.
    print(my_pet_rock.talk())

When the object is created (such as in my_pet_rock = SwimmingRock()), the interpreter recognizes this is an instance of an object. It is one of many swimming rocks.

Another example of a swimming rock would be my_other_pet_rock = SwimmingRock(swim_stroke='Backstroke'). It’s a gray rock that knows how to do the backstroke.1

Types of Objects And Their Abilities to Change

Notice that an integer object such as 1 will always be 1; otherwise, 1 could be 2, or 3, or anything that’s not 1, which is not true.

But a list of integers such as [1, 2, 3] doesn’t always have to be so; once assigned to a variable, it can change to [0, 2, 3].

If an object becomes something else, but still retains its identity, or location in the computer’s memory, then it is said to be mutable: It has the ability to mutate, or change.

One can tell an object is mutable by the following conditions:

  1. Same location in memory
  2. Different value

You can test for these conditions at the same time by using the id() function and changing the value of the variable:

a = 1  # a points to the integer object 1
print(str(a) + ' location in memory is ' + str(id(a)))
a = 2  # a points to the integer object 2
print(str(a) + ' location in memory is ' + str(id(a)))

b = [1, 2, 3]
print(str(b) + ' location in memory is ' + str(id(b)))

b[0] = 0  # The first item in the list now points to the integer object 0
# but b's address itself has not been re-assigned.

print(str(b) + ' location in memory is ' + str(id(b)))

# Result:
'''
1 location in memory is 11753896
2 location in memory is 11753928
[1, 2, 3] location in memory is 140178569810432
[0, 2, 3] location in memory is 140178569810432
'''

The function id(a) returns a‘s identity, or location (in memory). Notice that the variable a changes in id.2

Another way to look at this behavior is that 1 and 2 are different objects:

a = 1
print(str(id(a)) + ' is ' + str(a) + "'s identity")
a = 2
print(str(id(a)) + ' is ' + str(a) + "'s identity")
b = a
print(str(id(b)) + ' is ' + str(b) + "'s identity")

# Result:
'''
11753896 is 1's identity
11753928 is 2's identity
11753928 is 2's identity
'''

Since Python passes values by reference rather than copy on assignment, b’s address equals a’s address. They both refer to the integer object 2!

If Python passed by value, then b would be a copy of a, and it would have had a different id, or address.

Sequences In Sequences Are Mutable

Tuples are like lists because they represent sequences of objects. However, attempting to “mutate” a tuple will result in an error:

new_tuple = (1, 2, 3)
new_tuple[0] = 5  # Attempting to modify the tuple

#Result:
'''
Traceback (most recent call last):
    File "/main.py", line 2, in <module>
        new_tuple[0] = 5
        ~~~~~~~~~^^^
TypeError: 'tuple' object does not support item assignment
'''

Tuples are said to be immutable because the location of each item cannot change. Another immutable type is a string. In fact, trying this with strings or ints will result in the same error.

This leads to interesting behavior as referenced in the documentation:

a = [1]
b = [2]
c = [3]

my_tuple = (a, b, c)
print(str(my_tuple) + "'s location is " + str(id(my_tuple)))

a[0] = 4  # The list's address remains the same, so we are not altering the contents of the tuple.
          # However, the first item in the list changes! That's because lists are mutable.
          # (Same location in memory, different value...)
print(str(my_tuple) + "'s location is still " + str(id(my_tuple)))

# Result:
'''
([1], [2], [3])'s location is 140307513412800
([4], [2], [3])'s location is still 140307513412800
'''

In a tuple, it doesn’t matter what the list’s contents are. As long as the list remains in the same place, the tuple’s contents can appear to change, even though they don’t.

Tuples of Tuples, of course, remain immutable, and so do their contents.

Python is Object-Based And Object-Oriented

The power of object-oriented programming – coding blueprints for objects that can interact with each other – comes with essential elements:

  1. Inheritance
    • Polymorphism
  2. Composition

No language can be deemed object-oriented without these two fundamental implementations.

Inheritance: All Objects Come From Other Objects

I object to the amount of times that word was used, but OOP is an abstract concept. Sometimes, that involves abstract sentences (like the above heading).

Everything in Python is an object. Even the built-in types such as integers int and strings str come from the base Object class.

The objects we work with in Python can give rise to more objects with the same properties – and more. The only exception is the Object class, which is the root of all objects.

The __init__() function you saw earlier in the first code block example of the SwimmingRocks class was inherited from Object!

A Concrete Example

If the swimming rock was able to talk, then it’d be a talking swimming rock. But we also have to respect the fact that there’s swimming rocks that couldn’t talk to begin with – the originals.

So we have the original SwimmingRock and the TalkingSwimmingRock that inherits the properties of the original, along with more:

# swimmingrocks.py

class SwimmingRock():
    ...  # stuff from earlier

# This type of object can inherit all properties and abilities of SwimmingRock!
class TalkingSwimmingRock(SwimmingRock):

    # However, you must call the __init__ method of the parent class.
    def __init__(self):
        super().__init__()
        
# If super().__init__() is NOT called, the superclass variables won't be defined for the instance, and inheritance won't work.

Polymorphism: Inherited Functions Can Yield Different Results

The original SwimmingRock can talk, but it will always yield silence when it attempts to do so.

The TalkingSwimmingRock will also be able to talk as it inherits, or takes on, the original blueprint. However, this time the function returns the variable that you passed into it:

class TalkingSwimmingRock(SwimmingRock):

    def talk(self, say=''):  # It's the same function name and signature, but...
        return say  # It returns the variable say, not an empty string!
        # This is known as function overriding, a type of polymorphism.

The function changed forms: Even though it looks the same on the surface, its implementation changed to return a different value.

The function now has multiple forms and will return different values depending on whether it’s a SwimmingRock or a TalkingSwimmingRock.3

Composition: Different Types of Objects With the Same Ability

The rocks can talk, but so can other objects such as desks, chairs and trees:

class Desk():

    ... # stuff

    def talk(self, say='thud'):
        return say
        
class Chair():

    ... # stuff

    def talk(self, say='creak'):
        return say
        
class Tree():

    ... # stuff

    def talk(self, say='wisp'):
        return say

Writing the talk function for any object we want to grant talking abilities to is improper. We don’t want to re-invent the wheel over and over. Besides, unintentional human errors occur when copying word-by-word (I even made a typo writing this sentence).

Instead, we can implement a Mouth blueprint, initialize those types of objects and pass them into any others that are built to talk:

class Mouth():

    def __init__(self, say=''):
        self.say = say
        
    def talk(self):
        return self.say
        
class Desk():

    def __init__(self, mouth=None):
        self.mouth = mouth
        
    def talk(self):
        if isinstance(self.mouth, Mouth): # This object must be of type Mouth
            return self.mouth.talk()
        else:
            return ''
        
        
if __name__ == '__main__':

    desk_mouth = Mouth('thud')
    my_desk = Desk(desk_mouth)
    print(my_desk.talk()) # outputs 'thud'

This looks more complex and intricate than writing the simple functions for each class. However, suppose we wanted to add functionality for the mouth where the objects can now eat.

First of all, you would report the developer to the mental asylum, because objects cannot eat. Next, you would cut him or her some slack because anything is possible in Python.

After these steps are complete, you would realize that changing the behavior for the objects would be tedious because there are many that now need to be able to eat.

Since we added Mouths to the above code, now we only need to change the behavior for one class: Mouth. That change will cascade to every class composed of a Mouth object, such as desk.

In the end, it makes for much cleaner, more understandable, modular, and magnificent code.

And that is the beauty of composition.

  1. It may seem obvious, but it’s comfortable to have more than one rock that can swim. Imagine if only one instance represented all swimming rocks. Then any change would affect all swimming rocks, because there’s only one representation for all instances. ↩︎
  2. Being is not the same as equaling. You can always be something else, but you can’t equal something else. ↩︎
  3. We as Python developers take this for granted, but polymorphism is not enabled by default in other languages. In C++, the virtual keyword is typed before a function to enable this behavior; otherwise, the class and its derived subclass(es) will only use the original (base) functionality. ↩︎

Reader Interactions

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

bucketofdata.com

Copyright © 2025 · Monochrome Pro on Genesis Framework · WordPress