Adventures in Machine Learning

Mastering Mutability: Understanding Variables and Objects in Python

Immutability vs. Mutability: Understanding Variables and Objects

As a Python developer, it is important to understand the concept of immutability and mutability when working with variables and objects.

Variables are crucial in Python, as they store values that can be used in various calculations and operations. When we create an object, we are essentially creating a construct that has a set of data and behaviors.

Objects have the following attributes: value, identity, and type. The value of an object is the data it holds, like a string or a number.

Identity refers to the unique identifier of the object, which is determined by the memory location that it occupies. Lastly, the type of an object defines its behavior.

For instance, a string has different properties than a number.

Mutable and Immutable Objects in Python

Some objects in Python are mutable, while others are immutable. A mutable object is an object whose state can be modified after it is created.

In contrast, an immutable object cannot be modified once it is created. Familiarizing ourselves with these differences is essential when working with large or complex data structures in Python.

For instance, lists are mutable objects. We can add or remove items from a list without creating a new list.

However, when we modify an item in a list, we are modifying the original list. This can have implications for memory and threads, especially when working with teams or large-scale projects.

On the other hand, an immutable object does not allow us to modify the original value. For instance, once we create a string or tuple, we cannot change its characters or elements.

As a result, when we manipulate the data stored in an immutable variable, we need to create a new object every time.

Considerations for Working With Mutable vs Immutable Objects

When deciding which type of object to use, we need to consider factors such as memory, threads, and reasoning. Since mutable objects can be modified in place, they can require less memory overall.

This means that if we’re working with a large dataset, it may be significantly faster to use mutable objects. However, their variable value can be altered inadvertently, which can cause unintended side effects.

When working with multithreaded applications, immutable objects can provide a safer and more stable environment. In particular, separate threads can manipulate different parts of an immutable object without risking a collision.

This can be important in applications that require high levels of concurrency.

Immutable Built-in Data Types in Python

Python offers a range of built-in data types that are immutable. These include numbers, booleans, strings, bytes, and tuples.

Numbers

Python supports integers, floating-point numbers, and complex numbers. These data types are immutable, meaning that the value of the object cannot be altered once created.

Numeric values can be assigned to variables, and different operations such as addition, subtraction, and multiplication can be performed on them.

Booleans

A boolean is a two-value data type that only allows True or False values.

Booleans are immutable, meaning that once created, their values cannot be changed.

They are commonly used in conditions, loops, and other decision-making processes.

Strings

Strings are one of the most commonly used immutable data types in Python. A string is an ordered sequence of characters.

Each character in the string is assigned a unique index, starting from 0. Like other immutable data types, once a string is created, it cannot be altered.

Bytes

A byte is an immutable sequence of bytes in the range of 0 to 255.

Bytes have an immutable character since any result that can be generated should also be in the byte range.

Tuples

Tuples are similar to lists; they contain an ordered set of values. However, once we have created a tuple, we cannot modify its values.

Tuples are preferred over lists when we are working with fixed-size collections of values. Unlike lists, tuples are immutable, making them safer and more predictable.

Conclusion

In conclusion, as a Python developer, understanding the differences between mutable and immutable objects is critical. Understanding the functionality and benefits of immutable objects is essential in producing efficient, reliable, and scalable code.

Immutable objects are widely used in Python, and with their specific characteristics, our code will be more stable and more secure. The more familiar we become with these data types and their use cases, the better we will be equipped to create optimized, scalable, and efficient solutions in Python.

Mutable Built-in Data Types in Python:

  • Lists
  • Dictionaries
  • Sets

When working with data structures in Python, it’s essential to understand the various data types available. While immutable data types such as numbers, booleans, strings, bytes, and tuples cannot be modified after creation, mutable data types allow you to modify the object after it has been created.

In this expansion article, we’ll take an in-depth look at some of Python’s mutable built-in data types, including lists, dictionaries, and sets.

Lists

Lists are one of the most commonly used built-in data types in Python. They are mutable and used to store an ordered collection of items.

You can add or remove items from a list, sort a list, and perform many other operations on them. You can create a list by enclosing the items in square brackets, separated by commas.

For instance, to create a list of numbers, we can use the following code:

numbers = [1, 2, 3, 4, 5]

We can append an item to a list using the .append() method, like this:

numbers.append(6)

Or we can insert a new item to the beginning of the list, like this:

numbers.insert(0, 0)

We can also remove an item from a list using the .remove() method, like this:

numbers.remove(3)

Dictionaries

A dictionary is a mutable data type that stores a collection of key-value pairs. While the keys need to be unique, the values can be any arbitrary object.

You can add, remove, or modify the key-value pairs in a dictionary. You can create a dictionary by enclosing comma-separated key-value pairs in curly braces.

For instance, to create a dictionary of students’ grades, we can use the following code:

grades = {'Alice': 90, 'Bob': 80, 'Charlie': 70}

We can add a new key-value pair to a dictionary like this:

grades['David'] = 75

We can also remove a key-value pair using the del keyword, like this:

del grades['Charlie']

Sets

A set is a data type that stores an unordered collection of unique items. Like a list, a set is mutable, but unlike a list, it has no fixed order.

Sets are commonly used to perform mathematical set operations such as intersection, union, and difference. You can create a set by enclosing the items in curly braces, separated by commas.

For instance, to create a set of unique numbers, we can use the following code:

numbers = {1, 2, 3, 4, 5}

We can add an item to a set using the .add() method, like this:

numbers.add(6)

We can remove an item from a set using the .remove() method, like this:

numbers.remove(3)

Opposite Variations of Sets and Bytes: Frozen Sets and Byte Arrays

Python provides opposite variations for two of its mutable data types: frozen sets and byte arrays.

Frozen Sets

A frozen set is an immutable set.

Once a frozen set is created, you cannot add or remove items from it. Frozen sets are useful in cases where you need hashable objects for use as members of dictionaries or other sets.

You can create a frozen set from an existing set by calling the frozenset() method, like this:

numbers = {1, 2, 3, 4, 5}
frozen_numbers = frozenset(numbers)

Byte Arrays

A byte array is a mutable variation of the byte type, which is an immutable sequence of bytes in the range of 0 to 255. Byte arrays are used when we need to manipulate binary data.

You can create a byte array from an existing byte string by calling the bytearray() method, like this:

byte_string = b'hello'
byte_array = bytearray(byte_string)

You can also modify individual bytes in a byte array by accessing them with an index, like a list:

byte_array[0] = ord('H')

Conclusion

In conclusion, Python provides several mutable built-in data types that allow you to modify the objects once they have been created.

Lists, dictionaries, and sets are the most commonly used mutable data types in Python.

In addition, Python provides opposite variations of mutable built-in data types such as frozen sets and byte arrays, which may come in handy when manipulating hashes or binary data. Knowing how to use these data types and their methods will help you create more efficient and maintainable code in Python.

Mutability in Built-in Types: A Summary

As we have seen in the previous sections, mutability and immutability are central concepts in Python. Python provides mutable built-in data types such as lists, dictionaries, and sets, which have properties that allow manipulation or modification of their contents.

When working with mutable data types in Python, there are several gotchas to watch out for. Understanding these common pitfalls can help you avoid unexpected behaviors and save time in debugging and optimization.

Aliasing Variables

Aliasing occurs when two or more variables refer to the same object in memory. If one variable is changed, the others will reflect the change.

This can lead to unexpected behaviors when you are dealing with mutable objects such as lists. Consider the following example:

list1 = [1,2,3]
list2 = list1
list1.append(4)

print(list2)

The output will be [1,2,3,4] because both variables point to the same list object.

Mutating Arguments in Functions

It is essential to be careful when using mutable objects as function arguments. If the object is modified inside the function, it will affect the original object.

Consider the following example:

def add_items(items, item):
    items.append(item)
list1 = [1,2,3]
add_items(list1, 4)

print(list1)

The output will be [1, 2, 3, 4] since the function modified the original list object.

Using Mutable Default Values

Having mutable objects as default arguments in functions can lead to unexpected behaviors. The default value is created once and then reused whenever the function is called.

If the default argument is a mutable object, any changes made to it will affect all future calls to the function.

def func(a=[]):
    a.append('hello')
    return a
print(func())   # outputs ['hello']
print(func())   # outputs ['hello', 'hello']

In this example, the function does not receive a list each time it is called.

Instead, the list is created only once and is reused every time the function is called.

Making Copies of Lists

It is crucial to make copies of lists instead of changing the original list object.

You can create a shallow copy of a list using the slicing operator. Consider the following example:

list1 = [1,2,3]
list2 = list1[:]
list1.append(4)

print(list2)

The output will be [1,2,3] since we created a new list object using the slice operator.

Getting None From Mutator Methods

Methods that change the contents of mutable objects often do not return a value or return None. Consider the list.pop() method:

list1 = [1, 2, 3]
popped_item = list1.pop()
print(popped_item)   # outputs 3

print(list1)         # outputs [1, 2]

In this example, we pop an item from a list and store the value in a variable. The pop() method pops the last item off the list and returns it.

If we did not store the value in a variable, the item would be lost.

Storing Mutable Objects in Tuples

Tuples are immutable objects, but they can contain mutable objects such as lists. If a list is mutated within a tuple, it could lead to unexpected behavior.

Consider the following example:

my_tuple = ([1,2], 'hello')
my_tuple[0].append(3)

print(my_tuple)

The output will be ([1, 2, 3], 'hello') since we appended an item to the list object within the tuple.

Concatenating Many Strings

When concatenating many strings, it is important to use the .join() method instead of the += operator.

Consider the following example:

test_chars = ['a', 'b', 'c']
# Using the '+=' operator
result = ''
for char in test_chars:
    result += char

print(result)
# Using the .join() method
result = ''.join(test_chars)

print(result)

In this example, the .join() method is much faster than using the += operator because it creates a single string object instead of creating multiple string objects for each concatenation.

Conclusion

In conclusion, understanding mutability and how it affects built-in types in Python is crucial for efficient coding. When dealing with mutable data types such as lists, dictionaries, and sets, it is essential to consider the potential gotchas, such as aliasing variables, mutating arguments in functions, using mutable default values, storing mutable objects in tuples, and concatenating many strings.

By keeping these common issues in mind, Python developers can write more efficient, more secure, and more maintainable code.

Mutability in Custom Classes

In Python, we can also create our own custom classes with mutable attributes and methods. When creating custom classes, it’s important to understand how mutability affects these classes and how to control mutability to prevent unintended behaviors.

Mutability of Classes and Instances

Classes are mutable objects in Python, meaning we can modify their attributes and methods after the class has been defined. This can be useful when implementing new features or updating current functionality.

Instances of custom classes are also mutable, meaning we can modify their attributes and methods, even after they have been instantiated. This can be useful when working with complex objects that require frequent modifications.

To illustrate this concept, consider the following example:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
person1 = Person("Alice", 25)
print(person1.name)   # Output: "Alice"
person1.name = "Bob"
print(person1.name)   # Output: "Bob"

In this example, we create a custom class called Person with two attributes, name and age. We then instantiate a new Person object called person1 and change the value of name from "Alice" to "Bob".

This illustrates the mutability of instances of custom classes.

Mutability of Attributes

In custom classes, attributes can be mutable or immutable, just like built-in data types. Mutable attributes, such as lists, can be modified even after they have been created.

On the other hand, immutable attributes, such as strings, cannot be changed once they have been created. It’s essential to consider the mutability of attributes when designing custom classes, as it can affect the behavior of the object in unexpected ways.

For instance, consider the following example:

class ShoppingCart:
    def __init__(self, items=[]):
        self.items = items
        
shopping_cart1 = ShoppingCart()
shopping_cart2 = ShoppingCart()
shopping_cart1.items.append("item1")
shopping_cart2.items.append("item2")
print(shopping_cart1.items)   # Output: ["item1", "item2"]
print(shopping_cart2.items)   # Output: ["item1", 

Popular Posts