Code with globals() and locals():
import builtins
s = "hello"
x = 10
original_len = builtins.len # Backup built-in len
print("Globals at the start:")
print(globals().keys()) # Show top-level names
#for name, value in list(globals().items()):
#print(f"{name}: {value}")
def len(y):
print("Inside custom len()")
print("Locals inside len():")
print(locals()) # Show local variables inside len
def inner():
print("Inside inner function")
print("Locals inside inner():")
print(locals()) # Show inner's local scope
inner()
return y
print(f"\nString is '{s}' and length is: {original_len(s)}") # Use built-in len
print("\nCalling custom len:")
print(len(x)) # Calls your custom len
print("\nGlobals at the end:")
print(globals().keys()) # Show global namespace again
globals() shows all names defined at the module (global) level.
locals() inside a function shows only that function's local variables.
Each function (even nested ones) has its own local namespace.
Sample Output:
Globals at the start:
dict_keys(['__name__', '__doc__', ..., 's', 'x', 'original_len', 'len', 'builtins'])
String is 'hello' and length is: 5
Calling custom len:
Inside custom len()
Locals inside len():
{'y': 10}
Inside inner function
Locals inside inner():
{}
10
Globals at the end:
dict_keys([... 's', 'x', 'original_len', 'len', ...])
Why can’t we use built in len without backing it up:
You can use the built-in len() — but only until you overwrite it in your code.
Once you write:
def len(y):
...
You've redefined len in your current (global) namespace. From that point on, when you call len(),
you're calling your version, not the built-in one.
What Actually Happens?
When you call len(s), Python looks for len in this order:
1. Local scope (inside a function)
2. Enclosing scopes (if it's a nested function)
3. Global scope (your script or module)
4. Built-in scope (builtins.len)
So if your code has a global len, Python never looks further into the built-in len.
Why We Do This:
We save the original len like this:
original_len = builtins.len
So that even after redefining len, we can still access the real one:
print(original_len("hello")) # Safely calls the built-in version
Example:
print(len("hello")) # built-in len
def len(x):
return "I shadowed the built-in!"
print(len("hello")) # now calls your custom version!
Output:
5
I shadowed the built-in!
Best Practice:
If you must override a built-in save a reference first:
original_len = len # or builtins.len
note: But in real-world projects, avoid naming functions or variables the same as built-ins (len,
sum, list, etc.).
What is __name__ in Python?
__name__ is a special built-in variable in Python that indicates how a Python script is being used
— either:
Run directly as a script, or
Imported as a module in another script.
When You Run a Script Directly
If you run your Python file directly (e.g., python myfile.py), Python sets:
__name__ = "__main__"
This means: "This script is the main program being executed."
When You Import the Script
If the file is imported from another script, like:
import myfile
Then:
python
CopyEdit
__name__ = "myfile"
So it reflects the module name, not "__main__".
Why Is This Useful?
It allows you to control what code runs when the file is used as a script vs. when it's imported:
def greet():
print("Hello!")
if __name__ == "__main__":
greet() # This will only run when the file is executed directly
Summary:
Situation __name__ Value
Run directly as script "__main__"
Imported as module "module_name"
Keys(),Values(),items():
In Python, a view object is a dynamic view of the dictionary’s entries—meaning it reflects
changes made to the dictionary.
When you call methods like .keys(), .values(), or .items() on a dictionary, they return view objects.
Types of view objects in a dictionary:
1. dict.keys() – Returns a view object of all keys.
2. dict.values() – Returns a view object of all values.
3. dict.items() – Returns a view object of all key-value pairs as tuples.
These are not lists, but special objects that:
Can be iterated over like lists.
Reflect real-time changes to the dictionary.
Are more memory efficient than copying data.
Example:
my_dict = {'a': 1, 'b': 2}
keys_view = my_dict.keys()
print(keys_view) # dict_keys(['a', 'b'])
my_dict['c'] = 3
print(keys_view) # dict_keys(['a', 'b', 'c']) – reflects the change
So, a view object is just a live, read-only window into the dictionary’s keys, values, or items.
What does it mean that it’s an object?
In Python, everything is an object, including view objects. That means:
A view object is a specific instance of a class (dict_keys, dict_values, or dict_items).
It supports methods and properties like any object.
You can check its type:
my_dict = {'x': 1}
print(type(my_dict.keys())) # <class 'dict_keys'>
So, my_dict.keys() returns an object of type dict_keys.
What does it mean that it’s dynamic?
“Dynamic” means the view object automatically updates to reflect any changes in the dictionary after
the view was created.
✅ Example:
my_dict = {'a': 1, 'b': 2}
keys_view = my_dict.keys() # view object created
print(keys_view) # dict_keys(['a', 'b'])
my_dict['c'] = 3 # dictionary is changed
print(keys_view) # dict_keys(['a', 'b', 'c']) – view updated!
You did not call .keys() again — but the keys_view reflected the change. That’s why it’s dynamic: it stays
linked to the dictionary, not a snapshot copy.
Summary:
Feature Explanation
Object Instance of dict_keys, dict_values, or dict_items.
Dynamic Always reflects the current state of the dictionary.
Not a list But can be converted with list() if needed.
What does "Everything is an object" mean in Python?
In Python, everything you create—whether it’s a number, string, list, function, or class—is an object
of some class (also called a type).
✅ Example:
x = 10
print(type(x)) # <class 'int'>
s = "hello"
print(type(s)) # <class 'str'>
lst = [1, 2, 3]
print(type(lst)) # <class 'list'>
Each of these is an object belonging to a class (like int, str, list).
So what is an object?
An object is something that:
Has data/state (e.g., the value 10)
Has behavior/methods (e.g., .upper() for strings, .append() for lists)
For example:
s = "hello"
print(s.upper()) # Outputs: HELLO
Here, "hello" is a string object, and .upper() is a method it has.
🔹 Why is this powerful?
Because you can treat everything in the same way:
You can pass numbers, strings, and even functions as arguments.
You can store anything in variables or lists.
You can create your own objects (using classes).
Summary:
Concept Meaning
Everything is an object Every value in Python is an instance of some class.
Object Has data and methods.
The blueprint/type of the object.
Class
Class Object
# Define a simple class like Python does for its objects
class MyString:
def __init__(self, text):
self.data = text # internal data
def upper(self):
return self.data.upper() # method acting on data
# Create an object of MyString
s = MyString("hello")
# Accessing the internal data
print ("Data:", s.data) # Output: hello
# Using the method
print("Method (upper):", s.upper()) # Output: HELLO
Explanation:
self.data holds the actual content — similar to how "hello" is stored in a str object.
upper() is a method, just like built-in string objects have.
The object s behaves like a simple version of a real Python string object.