From https://gist.github.com/westurner/6f165149df59d697b997d305e9743dee#file-010-variables-ipynb :
# coding: utf-8 # # Python variables, references, aliases, garbage collection, scope # - Objective: teach CS variables, references, and aliases # - Objective: identify differences between symbolic/mathematical variables and CS variables # - Objective: teach without using partially-congruent/isomorphic metaphors that may later be confusing or limiting # # - Audience: # # - Concept: https://en.wikipedia.org/wiki/Variable_(computer_science) # - Concept: https://en.wikipedia.org/wiki/Variable_(mathematics) # - Concept: https://en.wikipedia.org/wiki/Variable (disambiguation) # - Concept: https://en.wikipedia.org/wiki/Reference_(computer_science) # # Resources: # # - https://www.google.com/search?q=variables+references+in+python # - https://www.safaribooksonline.com/library/view/python-in-a/0596001886/ch04s03.html # > A Python program accesses data values through references. A reference is a name that refers to the specific location in memory of a value (object). References take the form of **variables, attributes, and items**. In Python, a variable or other reference has no intrinsic type. # - https://www.google.com/search?q=variables+references+in+python+site%3Adocs.python.org # # - https://docs.python.org/3/glossary.html#term-reference-count # - https://docs.python.org/3/glossary.html#term-garbage-collection # # In Python, when you declare a variable ``A``, there is one reference to that allocated section of memory: its reference count is then 1. (When you call ``sys.getrefcount(A)``, ``sys.getrefcount`` is passed a reference to A, so it returns *2*. We'll ignore that one-off for purposes of explanation). # # If the refcount is 0 when the garbage collector runs, the memory will be freed. # # In Python, variable declaration and initialization are done in the same step. This both delcares the variable ``A`` and initializes it to a ``list`` containing the one character ``str`` '``A``': # ```python # A = ['A'] # refcount == 1 # ``` # # Lists are **mutable** in Python. Mutating the list does not change the refcount: # ```python # A = ['A'] # refcount == 1 # A.append('B') # refcount == 1 # ``` # # Referencing ``A`` in another ``list`` increments the refcount: # ```python # B = [A] # refcount(A) == 2 # ``` # # Deleting a variable decrements the refcount by one and removes the variable binding from the scope: # ```python # del A # refcount == 0 # ``` # # In[63]: A = ['A'] B = [A] assert refcount(A) == 2 assert refcount(B[0]) == 2 del A assert B == [['A']] assert refcount(B[0]) == 1 B.append(B) assert refcount(B) == 2 # In[53]: from sys import getrefcount def refcount(obj, msg=None): n = sys.getrefcount(obj) - 3 print((obj, n) if msg is None else (obj, n, msg)) return n A = ['A'] assert refcount(A) == 1 B = ['B'] assert refcount(B) == 1 B = A # refcount(['B']) == 0 assert refcount(A) == refcount(B) == 2 C = A.copy() + A[:] + ['C'] assert refcount(A) == 2 assert C == ['A', 'A', 'C'] D = None assert refcount(D, '!') > 0 # None = 1 def func(): assert A == B == ['A'] a = A assert refcount(a) == 3 # global A # SyntaxError: name 'A' is used prior to global declaration # A = a # (local a).refcount = 1, (global a).refcount = 2 #assert A == 3 assert refcount(C) == 1 c = C assert refcount(C) == refcount(c) == 2 c.append('here') assert c == ['A','A','C', 'here'] assert refcount(C) == 2 E = ['E'] assert refcount(E) == 1 D = [E] assert refcount(E) == 2 assert refcount(D) == 1 E.append(A) assert E == ['E', ['A']] assert refcount(A) == 4 return E e = func() assert refcount(e) == 1 assert refcount(A) == 3 # Memory allocation and garbage collection are orthogonal concepts to variable declaration and initialization. # Variable scope is a tangential concept. # # In C, there is no garbage collector: you must ``free`` declared variables in order to release the memory. In C++, variables are defined in a constructor method (like ``object.__init__()`` in Python) and freed in a destructor method (like ``object.__del__()`` in Python). In Java, there's a garbage collector, too. # # We usually don't ``del(variable)`` in Python because the garbage collector will free that memory anyway whenever it happens to run and the refcount is zero because the variable has fallen out of scope. # # In practice, we name global variables in ``ALL_CAPS`` (and may expect them to be constants). We wrap 'private' variable names with dunder (``__variable__``) so that other code can't modify those object attributes (due to 'name mangling'). Sometimes, we name variables with a single ``_underscore`` in order to avoid a 'variable name collision' with outer scopes (or to indicate, by convention, that a variable is a local variable) # # In practice, we try to avoid using globals because when or if we try to add threads (or port to C/C++), we're never quite sure whether one thread has modified that global; that's called a *race condition*. Some languages -- particularly functional languages like Haskell and Erlang -- only have mostly all immutable variables; which avoids race conditions (and the necessary variable locking that's slowing down Python GIL removal efforts). # # Is it a box or a bucket? # It's a smart pointer to an allocated section of RAM. # # When do we get a new box and throw an old one away? Is there a name for the box and the thing in the bucket? Does the bucket change size when? # # I think the box/bucket metaphor is confusing and limiting; but I've been doing this for a long time: it's a leaky abstraction. # # - https://en.wikipedia.org/wiki/Memory_leak # - https://en.wikipedia.org/wiki/Race_condition # - https://en.wikipedia.org/wiki/Smart_pointer # Commands to build this environment: # ```bash # conda create -n notebooks python==3.6 notebook pip # source activate notebooks # cd notebooks; mkdir -p src/notebooks; cd src/notebooks # jupyter-notebook & # jupyter-nbconvert --to python ./010-variables.ipng # ```
_______________________________________________ Edu-sig mailing list Edu-sig@python.org https://mail.python.org/mailman/listinfo/edu-sig