Table of Contents
In Python, the identity operators is and is not are used to compare memory locations of two objects - not just their values. This makes them different
from the equality operators (== and !=), which check if two values are equal. Identity operators are particularly useful when dealing with immutable
data types, singleton objects like None, or comparing references in data structures. Understanding identity logic is essential when working with object-oriented
programming, function arguments, or performance optimization in Python.
The practice tasks below will help you understand how identity operators work with lists, strings, numbers, and built-in constants. By solving these problems, you'll gain clarity on how Python manages memory and how object comparison works behind the scenes.
To successfully complete the tasks in this section, you should review the following topics:
- Identity Operators in Python
- Numeric Data Types
- Sequence Types (for understanding mutable and immutable objects)
- Variables and Object References
- Difference between equality (
==) and identity (is) - Basics of memory allocation in Python
Beginner-Level Practice – Identity Operators
At the beginner level, your goal is to understand the difference between object equality and identity. You’ll explore how Python handles variable assignments and compares object
references in memory. These tasks will help you see that two variables may look the same in value but still not be the same in memory. This is a common point of confusion for
beginners who assume == and is are interchangeable. Mastering this difference early on will make debugging and writing safe conditionals easier in the
future.
Excercise 1. Comparing Lists and Copies
Write a program that creates a list of fruits and assigns it to a second variable. Then, make a shallow copy of the list using slicing. Use is and
== to compare the original list, the second reference, and the sliced copy. Print the results and explain what is equal and what is identical.
What this Task Teaches
This task highlights the key difference between object identity and value equality. You'll learn that two lists may have the same content but reside in different places in
memory. You'll also practice using is and == correctly in logical checks when working with data containers like lists.
Hints and Tips
Assign the list directly to one variable, then use slicing like [:] to make a shallow copy. Use == to check if contents are equal and
is to test whether they share the same identity.
- Use the
id()function to display memory locations - Compare the original and copied lists with
==andis - Print descriptive messages to make your output clear
Common Mistakes
Beginners often confuse == with is. While == checks if the values are the same, is checks whether both sides point to the
same object in memory. Using is to compare values can result in logic bugs, especially when working with mutable data like lists and dictionaries.
- Assuming copy is identical: Using slicing
[:]creates a new list - it’s equal in value, but not the same object. -
Using
isfor value checks: This works only in rare cases, like small integers or cached strings. - Not printing or labeling comparisons: Without clear print messages, it's hard to know what you're testing.
-
Skipping the
id()function:id()helps visualize the identity of each object. - Confusing behavior with mutability: Changes to one object might affect another only if they are truly identical.
Step-by-Step Thinking
Begin by creating a list and assigning it to two different variables - one by reference, one by slicing. Compare the three and check their behavior.
- Create a list:
fruits = ['apple', 'banana'] - Assign it to
same_list = fruits - Copy it with slicing:
copied_list = fruits[:] - Compare
fruits == same_listandfruits is same_list - Compare
fruits == copied_listandfruits is copied_list - Use
id()to display memory locations of each list - Print conclusions about identity vs. equality
How to Make it Harder
Try changing one element in each list and see which lists reflect the change. This reveals the behavior of references vs. independent copies.
- Modify an item in
same_listand check iffruitsis also updated - Modify
copied_listand observe if it affects the original - Try using
copy()method from thecopymodule
Excercise 2. Is None or Not?
Ask the user for their age. If they press Enter without typing anything, store None as their value. Then check whether the input is None using the
is operator. Print an appropriate message depending on whether the user provided an input or skipped it.
What this Task Teaches
This task teaches you how to use the is operator when working with special singleton values like None. It demonstrates the correct and safe way to
check for the absence of a value - a pattern commonly used in data validation, APIs, and input parsing in Python.
Hints and Tips
Use an if not age_input check to determine if the string is empty. If it is, assign None. Then compare using is None - not
== None.
- Use
input()to ask for age - Check if the response is empty:
if not value - If empty, assign
age = None - Then use
if age is Nonefor your condition
Common Mistakes
A very common mistake is using == None instead of is None. While this may work in some cases, the recommended and safer approach is always to use
identity comparison with singletons like None, True, and False.
-
Using
== None: Python recommends usingisforNonechecks. - Comparing to the wrong type: Comparing empty strings directly to
Nonecan cause confusion. - Incorrect assignment: Forgetting to assign
Noneexplicitly can make checks fail silently. - Mixing value checks and identity checks: You must decide whether you want to compare content or object.
-
Overusing
iselsewhere: Don’t start usingisfor strings or integers - it doesn’t always behave as expected.
Step-by-Step Thinking
Think of the logic as: “Did the user enter a value or not?” If not, use None. Then test the presence or absence with is.
- Use
input()to get user input - Check if input is empty - use
if not age_str - Assign
Noneif no input, else convert toint - Use
if age is Noneto check condition - Print message based on whether age was entered or not
How to Make it Harder
Use identity comparison in a loop that keeps asking for input until a valid number is entered. Add validation and re-prompt logic.
- Repeat input prompt until age is not
None - Add a message saying “Age is required!”
- Handle invalid numeric conversion using
try/except
Intermediate-Level Practice – Identity Operators
At the intermediate level, you'll dive deeper into how Python handles memory and references when working with custom objects, immutable values, and built-in constants. These
tasks will make you think more carefully about how assignments affect identity and how the is and is not operators behave in different contexts. You’ll
also explore scenarios where identity checks are more reliable than equality checks - particularly when working with None, booleans, and singletons. Understanding
these nuances will prepare you for cleaner code and fewer logic bugs in conditional logic and function arguments.
Excercise 1. Shared Config or Not?
Create a simple class called Config with one attribute. Then make two variables that point to the same Config object. Create a third variable that is
a separate but identical Config instance. Write comparisons using both is and == and explain which objects share memory identity and
which ones only match in value.
What this Task Teaches
This task demonstrates how user-defined objects behave with identity comparisons. It also teaches the difference between assigning the same reference vs. creating new instances. You’ll get a hands-on understanding of how classes and object memory addresses interact in Python.
Hints and Tips
Use a constructor (__init__) to set an attribute like theme. Don’t override __eq__ - just test the defaults.
- Use
cfg1 = Config()andcfg2 = cfg1to share a reference - Use
cfg3 = Config()to create a new instance - Compare
cfg1 is cfg2andcfg1 is cfg3 - Also test
cfg1 == cfg3and observe the result
Common Mistakes
Many developers mistakenly think that two objects with identical attributes are the same in memory. Unless they point to the exact same instance, Python treats them as separate objects. Forgetting this can cause bugs in caching, configuration sharing, or object tracking.
-
Misinterpreting
==as identity: Two objects can have equal attributes but still be different in memory. - Overwriting references: Accidentally assigning one variable over another may break shared logic.
-
Not printing
id(): It's crucial to visualize actual memory references when debugging. - Modifying one instance and expecting another to change: Only shared references will reflect the update.
- Assuming all instances behave like strings: Unlike immutable primitives, class instances behave differently.
Step-by-Step Thinking
Focus on creating two references to the same object, and one to a new object. Then compare both identity and equality.
- Define a class
Configwith athemeattribute - Assign
cfg1 = Config()and thencfg2 = cfg1 - Create another:
cfg3 = Config() - Use
isto compare cfg1/cfg2 and cfg1/cfg3 - Use
==to compare them as well - Print
id()of all instances - Print conclusion: which share memory and which don’t
How to Make it Harder
Add a method that modifies the instance and observe which objects reflect the change. Try defining __eq__ manually to make equality work across objects.
- Add
def set_theme(self, theme):and update the theme - Modify
cfg1and observe changes incfg2andcfg3 - Override
__eq__to allow equality by value
Excercise 2. None or Not? Handling Default Arguments
Create a function that takes an optional argument status=None. Inside the function, check if the user passed any value by comparing status is None.
Return a default message if no status is given. Otherwise, return a customized message. Then, test the function by calling it with different inputs.
What this Task Teaches
This task demonstrates how is is used in function arguments to check if the caller provided a value. It teaches the correct way to handle default parameters and
prevent logical errors when None is a valid argument. This is a common pattern used in real-world APIs and libraries.
Hints and Tips
Use status is None to determine whether to use a default response or a user-provided value. Remember, don’t check with ==.
- Define the function like:
def greet(status=None): - Inside the function:
if status is None: - Return “No status provided” or something similar
- If a status is passed, return “Status: X”
- Call function with and without arguments to test
Common Mistakes
Developers often forget that None must be compared using is. Using == might work but can break if custom objects override comparison
logic. Also, beginners may accidentally use mutable defaults instead of None.
-
Using
== Noneinstead ofis None: This is unreliable when custom__eq__is defined. - Returning nothing: Forgetting to return a value causes
Noneto be printed automatically. - Using mutable default args: Never use
[]or{}as default arguments. - Not testing both conditions: Always test with both passed and missing arguments.
- Incorrect print formatting: Forgetting f-strings or
+concatenation can break your message.
Step-by-Step Thinking
You need to detect whether the function was called with or without arguments. Use identity comparison for this.
- Define the function with
status=Nonein signature - Inside, use
if status is Noneto detect missing argument - Return a default string when nothing is passed
- Return a formatted string when value is passed
- Test the function with and without arguments
How to Make it Harder
Try allowing None to mean “unknown” and check for False, "", and other falsy values. Refactor logic with a sentinel object instead of
None.
- Use a custom object as the default argument (e.g.,
_UNSET = object()) - Detect true absence of a value, not just
None - Add support for additional value types (bool, str, etc.)
Advanced-Level Practice – Identity Operators
At the advanced level, identity operators are no longer just tools for comparing simple variables - they become part of logic validation in performance-critical systems, memoization, and singleton management. These tasks will challenge your understanding of how Python stores and reuses small objects like integers, strings, and tuples. You'll also explore cases where the `is` operator can produce misleading results due to internal optimizations. These exercises simulate real-world use cases like config caching and object pooling - scenarios where identity checks are more efficient than equality checks.
Excercise 1. Is That the Same Tuple?
You are given a function that returns the same tuple of default settings used in different parts of your application. Your task is to verify whether two returned tuples from two different calls refer to the same object in memory using identity comparison. Then, simulate a modification in one function call and check if the change affects the second.
What this Task Teaches
This task reveals how Python handles immutable data types and how tuple interning or caching can affect the result of identity comparison. You'll see how even when objects look the same, their memory references might differ - unless Python reuses them for optimization. Understanding when this reuse happens is key to writing high-performance code and debugging memory-related issues.
Hints and Tips
Tuples are immutable, so you can't modify them directly - but you can replace them. Python sometimes caches small, identical tuples. Pay attention to what values are stored and returned.
- Define a function
get_default_settings()that returns a tuple like("dark", True, 10) - Call the function twice and compare the results using
is - Use
id()to show memory reference - Try modifying the returned tuple by assigning a new one
- Check if other references are still identical
Common Mistakes
Developers may incorrectly assume that small immutable structures like strings or tuples are always the same object when they contain the same data. This can lead to bugs in cache validation or lazy evaluation logic. Interning works under certain conditions - but it's not universal.
- Assuming identical tuples are always the same object: Python may or may not reuse immutable objects depending on how they’re created.
- Modifying the tuple directly: Tuples are immutable - you can’t change individual elements without replacing the whole tuple.
-
Confusing equality with identity: Even though
tuple1 == tuple2might beTrue,tuple1 is tuple2could beFalse. -
Overusing
id()without context: Memory references differ with how the object is created - especially when not hardcoded. - Ignoring Python optimizations: The interpreter might optimize some constants; understand when it does and doesn't.
Step-by-Step Thinking
Your goal is to compare two identical-looking tuples returned from a function. First, inspect whether they are the same object in memory, and then test what happens when you alter one of them.
- Create a function that returns a tuple of values
- Call this function twice and assign to
aandb - Use
a is banda == bto compare - Print
id(a)andid(b)to verify memory location - Reassign one tuple and test
isagain - Log what changes in behavior, if any
How to Make it Harder
Return the tuple from a class method or a dynamically built tuple instead of a literal. Try using integers above 256 and strings longer than 20 characters to observe caching limits.
- Return a tuple created using concatenation:
("dark",) + ("theme",) - Compare this with a static
("dark", "theme") - Add large integers or unique strings and see if caching still applies
- Test behavior in loops or in class-based configuration systems
Excercise 2. Singleton Validator
Create a singleton object Sentinel and use it as a placeholder value in a function default. Inside the function, check whether the passed argument is the same as
the sentinel using the is operator. This pattern is often used in libraries to distinguish between a missing argument and None explicitly provided by
the user.
What this Task Teaches
You'll learn how identity checks play a key role in building reliable APIs and libraries. By using sentinel objects and checking with is, you ensure that the logic
clearly distinguishes between intentional None and a truly absent value. This concept is frequently used in professional Python projects.
Hints and Tips
Create the sentinel object as SENTINEL = object() at module level. In your function, check if arg is SENTINEL to decide how to respond.
- Define
SENTINEL = object()at the top - Write a function like
def process(value=SENTINEL): - Inside, check
value is SENTINEL - Return different outputs for unset, None, and other values
- Test calling the function with no argument, with
None, and with a string
Common Mistakes
Many developers use None as the default argument, which can create confusion when None is a valid user value. A unique sentinel object avoids this
ambiguity. Forgetting to use identity check here can cause logical errors in data processing and function behavior.
-
Using
Noneinstead of a unique sentinel: If users can passNone, then you can't distinguish it from default. -
Using
==instead ofis: Always use identity comparison for sentinels to avoid equality conflicts. - Not making sentinel global: If you define
object()inline, it will always be different per call. -
Returning
Noneambiguously: Always clearly document what the return means whenNoneis used. - Forgetting fallback logic: Make sure there's a valid branch when value is the sentinel.
Step-by-Step Thinking
The idea is to distinguish between three cases: no argument passed, None passed explicitly, and a meaningful value. Using a sentinel object helps with this.
- Define a global sentinel using
SENTINEL = object() - In your function, use
value=SENTINELin the signature - Inside, check
value is SENTINEL - Handle three cases: no arg,
None, and other values - Test the function with all three types of calls
How to Make it Harder
Extend this concept by building a config loader that can detect which fields were not set. You can even integrate this with class-level defaults and validators.
- Use multiple sentinels for different fields
- Store unset fields in a dict for later validation
- Raise errors if required fields are not set