#!/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)