Given:
x = 1000
Python does the following:
int
object with value of 1000
x
x
to refer to the into 1000
object(?)Following above example, then:
x = 500
Python does the following:
int
object with value of 500
x
reference to point to new objectint
object can no longer be reached/referenced, so Python garbage collector will reclaim it at some pointx = 500
y = x
# x == 500 && y == 500
x = 3000
# x == 3000 && y == 500
id()
Given:
t = 5
t+= 2
t
referring to int 5
objectint 2
object without creating reference to itint 5
with int 2
to create int 7
t
to newly-created int 7
and remaining int
objects are garbage collected💡 IMPORTANT 💡
- Python objects show this behavior for all types
- core rule is that assignment operator only ever binds objects to names
- NEVER copies object to a value
Given:
r = [2, 4, 6]
s = r
s[1] = 17
s #=> [2, 17, 6]
r #=> [2, 17, 6]
s is r #=> True
think "assignment by reference"
Python doesn't have variables in sense of boxes holding value
instead, has named references to objects, which behave more like labels
Given:
p = [4, 7, 11]
q = [4, 7, 11]
p == q #=> True
p is q #=> False
==
tests "value equality"
is
tests "identity equality"
value equality and identity equality are fundamentally different concepts
comparison by value can be controlled programatically
identity comparison is unalterably defined by language
Given:
m = [9, 15, 24]
def modify(k):
k.append(39)
print("k =", k)
modify(m)
#=> k = [9, 15, 24, 39]
m
#=> [9, 15, 24, 39]
Given:
f = [14, 23, 37]
def replace(g):
g = [17, 28, 45]
print("g =", g)
replace(f)
#=> g = [17, 28, 45]
f
#=> [14, 23, 37]
Given:
def replace_contents(g):
g[0] = 17
g[1] = 28
g[2] = 45
print("g =", g)
f = [14, 23, 37]
replace_contents(f)
#=> g = [17, 28, 45]
f
#=> [17, 28, 45]
Given:
def f(d):
return d
c = [6, 10, 16]
e = f(c)
c is e #=> True
Simple example:
def banner(message, border='-'):
line = border * len(message)
print(line)
print(message)
print(line)
banner("Norwegian Blue")
#=> --------------
#=> Norwegian Blue
#=> --------------
banner("Sun, Moon, and Stars", "*")
#=> ********************
#=> Sun, Moon, and Stars
#=> ********************
banner("Sun, Moon, and Stars", border="*")
### in this case:
# first argument is called 'positional argument'
# second argument is called 'keyword argument'
banner(border="*", message="Sun, Moon, and Stars")
NOTE: all keyword arguments must be specified after keyword arguments
Example using time
library:
import time
time.ctime()
#=> 'Tue May 31 14:16:48 2022' (or whatever today's date is)
def show_default(arg=time.ctime()):
print(arg)
show_default()
#=> 'Tue May 31 14:17:01 2022'
show_default()
#=> 'Tue May 31 14:17:01 2022'
show_default()
#=> 'Tue May 31 14:17:01 2022'
### subsequent calls return same value
def
is statement executed at runtimedef
is executedGiven:
def add_spam(menu=[]):
menu.append("spam")
return menu
breakfast = ['bacon', 'eggs']
add_spam(breakfast)
#=> ['bacon', 'eggs', 'spam']
lunch = ['baked beans']
add_spam(lunch)
#=> ['baked beans', 'spam']
add_spam()
#=> ['spam']
add_spam()
#=> ['spam', 'spam']
add_spam()
#=> ['spam', 'spam', 'spam']
Solution is to always (when possible) use immutable objects for default values
def add_spam(menu=None):
if menu is None:
menu = []
menu.append("spam")
return menu
add_spam()
#=> ['spam']
add_spam()
#=> ['spam']
add_spam()
#=> ['spam']
def add(a, b):
return a + b
### dynamism examples
add(5, 7) #=> 12
add(3.1, 2.4) #=> 5.5
add("news", "paper") #=> 'newspaper'
add([1, 6], [21, 107]) #=> [1, 6, 21, 107]
### strength example
add("The answer is", 42)
#=> TypeError
💡 IMPORTANT 💡 generally, Python will not perform implicit conversions between types - exceptions being conversion of
if
statement andwhile
loop predicates tobool
Local
: inside current functionEnclosing
: inside enclosing functionsGlobal
: at top level of moduleBuilt-in
: in special builtins
moduleLEGB
ruleNOTE: scopes in Python do not correspond to source code blocks
- i.e. for
loops and similar structures demarcated by indentation do not introduce new nested scopes
see _demos/scopes/words.py
Module scope name bindings typically introduced by - import statements - function or class definitions Possible to use other objects at module scope - typically used for constants - can be used for variables
Each bound name in Local
scope
count = 0
def show_count():
print(count)
def set_count(c):
count = c
show_count() # tries to look up `count` in local namespace, doesn't find it, then looks up in next-most outer scope
#=> 0
set_count(5) # binds `5` to `count` in innermost namespace context (in this case, scope of the function)
show_count()
#=> 0
global
keyword to rebind global names into local namespacecount = 0
def show_count():
print(count)
def set_count(c):
global count
count = c
show_count()
#=> 0
set_count(5)
show_count()
#=> 5
☯️ Moment of Zen ☯️ "Special cases aren't special enough to break the rules" We follow patterns Not to kill complexity But to master it
In REPL env:
import words
type(words)
#=> <class 'module'>
dir(words)
#=> ['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'fetch_words', 'main', 'print_items', 'sys', 'urlopen']