#!/usr/bin/env python
# coding: utf-8
# # Mutable vs. Immutable Objects
#
# ### DesertPy Lightning Talk, June 2015
#
# ### Ashley Anderson
# [@aganders3](twitter.com/aganders3) on Twitter or aganders3@gmail.com
# ## Everything is an object
# Every Python object has a type, identity, and *value*.
#
# The builtin function `type()` returns a `type` object.
# The builtin function `id()` returns the object's identity (an integer).
# Value is kind of vague, though.
# In[169]:
print 1, type(1), id(1)
foo = range(10)
print foo, type(foo), id(foo)
import sys
print sys, type(sys), id(sys)
def bar():
pass
print bar, type(bar), id(bar)
# ## Mutability is about *values*
#
# Think of an object's value as any and all of its attributes. For example, anything you'd type `self.` to get at from inside if you wrote the class yourself.
#
# Most custom classes are mutable. To create a simple immutable class, try using a [`namedtuple`](https://docs.python.org/2/library/collections.html#collections.namedtuple). Otherwise you can raise a `TypeError` from `__setattr__` to make your own immutable type.
# ### Immutable objects
# You can't change their values.
# The value of `x` is the same before and after `foo` is called.
#
# x = some_immutable
# foo(x)
# print x
#
# This is *kind of* like pass-by-value.
#
# Immutable objects include **numbers**, **strings**, **tuples**.
# In[120]:
def add_one(bar):
bar += 1
b = 5
print b, id(b)
add_one(b)
print b, id(b)
# ### Mutable objects
# You can change their values.
# The value of `y` *may be different* after `foo` is called.
#
# y = some_mutable
# foo(y)
# print y
#
# This is *kind of* like pass-by-reference.
#
# Mutable objects include **lists**, **dictionaries**, etc.
# In[121]:
def reverse_list(foo):
foo.reverse()
a = range(10)
print a, id(a)
reverse_list(a)
print a, id(a)
# ## But, wait...
# It's pretty easy to change the value of a number variable...
# In[151]:
foo = 42
print foo
foo += 1
print foo
foo = 42
print foo
# ## Nah...
# The assignment operator doesn't change the value of an object, it changes its identity.
# In[148]:
foo = 42
print foo, id(foo)
foo += 1
print foo, id(foo)
foo = "bar"
print foo, id(foo)
# ## Values get confusing when we have containers
#
# If you have an immutable container (e.g. a tuple) that holds mutable objects (e.g. lists), you can still mutate the contained objects without mutating the container.
# In[171]:
A = ([],[],[])
print A, id(A[0]), id(A)
A[0].append(5)
print A, id(A[0]), id(A)
A[1] = [5]
# ## Gotcha!
# Mostly you don't have to think about this stuff, but sometimes you do.
# In[1]:
def my_appending_function(element, initial_list=[]):
# do some stuff
initial_list.append(element)
return initial_list
# In[2]:
print my_appending_function((1,2,3,5), range(2))
print my_appending_function(10)
print my_appending_function(5)
# ## Mutable objects as default arguments
# These are defined *once* when the function is defined. This is a result of [`def` being an *executable statement* that binds the name of the function to the function object](http://effbot.org/zone/default-values.htm). All this has something to do with Python functions being "first class" objects.
# ## Mutable objects as deafault arguments
# So...instead of this:
# In[165]:
def my_appending_function(element, initial_list=[]):
# do some stuff
initial_list.append(element)
return initial_list
# Do this:
# In[166]:
def my_appending_function(element, initial_list=None):
if initial_list is None:
initial_list = []
# do some stuff
initial_list.append(element)
return initial_list
# ## Mutable objects as default arguments
#
# Now it does what you (may have) expected:
# In[77]:
def my_appending_function(number, initial_list=None):
if initial_list is None:
initial_list = []
# do some stuff
initial_list.append(number)
return initial_list
# In[167]:
print my_appending_function(10)
print my_appending_function(5)
print my_appending_function(10)
print my_appending_function(5)