The short answer is no. There will always be a way to somehow access the object and update it.
Although, there are ways to discourage the mutation of the object. Here are a few.
Pass an immutable object
If some data should not be updated passed some point in the execution of your program, you should make it an immutable object.
my_data = []
populate_my_data(my_data) # this part of the code mutates 'my_data'
my_final_data = tuple(my_data)
Since my_final_data is a tuple, it cannot be mutated. Be aware that mutable objects contained in my_final_data can still be mutated.
Create a view on the object
Instead of passing the object itself, you could provide a view on the object. A view is some instance which provides ways to read your object, but not update it.
Here is how you could define a simple view on a list.
class ListView:
def __init__(self, data):
self._data = data
def __getitem__(self, item):
return self._data[item]
The above provides an interface for reading from the list with __getitem__, but none to update it.
my_list = [1, 2, 3]
my_list_view = ListView(my_list)
print(my_list_view[0]) # 1
my_list_view[0] = None # TypeError: 'ListView' object does not support item assignment
Once again, it is not completely impossible to mutate your data, one could access it through the field my_list_view._data. Although, this design makes it reasonnably clear that the list should not be mutated at that point.