Share
Embed
Transcript
80 slides
-
"Namespaces are one honking great idea -- let's do more
of those!" - Tim Peters, The Zen of Python -
What do we mean, "namespaces"? What do namespaces look like?
How are they used? How do they work? -
A concept: a set of independently identifiable expectations + a
unique (within set) label for each member -
A concept: a set of independently identifiable expectations + a
unique (within set) label for each member context + name => value -
Nested namespaces? with nesting without nesting spam spam sys.version_info sys_version_info
os.path.exists() os_path_exists() -
Namespaces in C? PyObject Py_TYPE PyDict_SetItem PyErr_SetString Py_None Py .
Object Py . TYPE Py . Dict . SetItem Py . Err . SetString Py . None -
A Type for sys.implementation • Python implementation specifics: PEP 421
• the type must communicate the nature of the namespace -
A Type for sys.implementation • Python implementation specifics: PEP 421
• the type must communicate the nature of the namespace • dict too soft -
A Type for sys.implementation • Python implementation specifics: PEP 421
• the type must communicate the nature of the namespace • dict too soft • namedtuple too hard -
A Type for sys.implementation • Python implementation specifics: PEP 421
• the type must communicate the nature of the namespace • dict too soft • namedtuple too hard • object just right... -
A Type for sys.implementation • Python implementation specifics: PEP 421
• the type must communicate the nature of the namespace • dict too soft • namedtuple too hard • object just right... • types.SimpleNamespace -
Communicate the nature of your namespace! mapping-based VOLATILE attribute-based STABLE
property-based STATIC -
Important Mappings: • obj.__dict__ • execution locals, globals, and builtins
• sys.modules • os.environ -
Some available types: • dict • collections.defaultdict • collections.OrderedDict •
configparser.ConfigParser • shelve.Shelf • types.MappingProxyType • weakref.WeakKeyDictionary • weakref.WeakValueDictionary -
How they work: obj[key] obj[key] = value del obj[key] key
in obj obj.__getitem__(key) obj.__setitem__(key, val) obj.__delitem__(key) obj.__contains__(key) -
How they work: obj[key] obj[key] = value del obj[key] key
in obj collections.abc.(Mutable)Mapping obj.__getitem__(key) obj.__setitem__(key, val) obj.__delitem__(key) obj.__contains__(key) -
class ReadOnlyProxy(Mapping): def __init__(self, mapping): self._mapping = mapping def __len__(self):
return len(self._mapping) def __iter__(self): return iter(self._mapping) def __getitem__(self, key): return self._mapping[key] -
Gems: • dict(mapping, **other_mapping) • d.update(mapping, **other_mapping) • dict.from_keys() •
dict.setdefault() • str.formatmap() • operator.itemgetter() • threading.local -
property-based containers (static namespaces) • a proper name aliases an
underlying key • the key is associated with a value • the names and values are fixed in place (i.e. read-only) -
Example: >>> sys.version_info sys.version_info(major=3, minor=4, micro=0, releaselevel='alpha', serial=0) >>> os.times()
posix.times_result(user=0.09, system=0.04, children_user=0.0, children_system=0.0, elapsed=17248877.17) -
namedtuple: context + names Spam = namedtuple('Spam', "a b c")
class Spam(tuple): def __new__(cls, a, b, c): return super().__new__(cls, a, b, c) @property def a(self): return self[0] ... -
class ReadOnlyProxy: def __init__(self, obj): self._obj = obj @property def
spam(self): return self._obj.spam @property def eggs(self): return self._obj.eggs -
How they work: obj.name obj.name = value del obj.name obj.__getattribute__(name)
obj.__setattr__(name, val) obj.__delattr__(name) -
How they work: obj.name obj.name = value del obj.name obj.__getattribute__(name)
obj.__setattr__(name, val) obj.__delattr__(name) -
How __getattribute__() works: 1. use a "data" descriptor, if there
is one 2. pull from the instance, if it has __dict__ 3. use a "non-data" descriptor, if there is one 4. use __getattr__(), if there is one 5. raise AttributeError -
How __getattribute__() works: 1. use a "data" descriptor, if there
is one 2. pull from the instance, if it has __dict__ 3. use a "non-data" descriptor, if there is one 4. use __getattr__(), if there is one 5. raise AttributeError -
How __getattribute__() works: 1. use a "data" descriptor, if there
is one 2. pull from the instance, if it has __dict__ 3. use a "non-data" descriptor, if there is one 4. use __getattr__(), if there is one 5. raise AttributeError MRO! -
How __getattribute__() works: 1. use a "data" descriptor, if there
is one 2. pull from the instance, if it has __dict__ 3. use a "non-data" descriptor, if there is one 4. use __getattr__(), if there is one 5. raise AttributeError Method Resolution Order! -
class ReadOnlyProxy: def __init__(self, obj): self._obj = obj def __getattr__(self,
name): return getattr(self._obj, name) -
Some available types: • object • types.SimpleNamespace • argparse.Namespace •
multiprocessing.Manager.Namespace • xmlrpc.client.ServerProxy • unittest.Mock -
Communicate the nature of your namespace! mapping-based VOLATILE attribute-based STABLE
property-based STATIC -
Objects • are namespace containers • have namespaces (mostly) •
ergo, as container, proxies a namespace -
Objects • are namespace containers • have namespaces (mostly) •
ergo, as container, proxies a namespace >>> ns = SimpleNamespace() >>> ns_ns = vars(ns) >>> ns.__dict__ is ns_ns True -
Objects • are namespace containers • have namespaces (mostly) •
ergo, as container, proxies a namespace • base object type has slots • assignment/definition: "bind" object to namespace -
Object access helpers: • getattr() • setattr() • delattr() •
hasattr() wrapper around getattr() • dir() calls obj.__dir__() • vars() exposes namespace* • inspect.getattr_static() • inspect.getmembers() -
__dict__ • most builtins don't have one • key exception:
function objects -
__dict__ • most builtins don't have one • key exception:
function objects • module namespaces exposed as __dict__ -
__dict__ • most builtins don't have one • key exception:
function objects • module namespaces exposed as __dict__ • read-only proxy of class namespaces exposed as __dict__ • use metaclass __prepare__() to customize the namespace used during class definition -
__dict__ vs. __slots__ • slots are turned into descriptors •
__dict__ is disabled -
Name lookup: • explicit lookup happens relative to containers •
Method Resolution Order (MRO) • lookup on class vs. on objects • special method lookup -
Name lookup: • explicit lookup happens relative to containers •
Method Resolution Order (MRO) • lookup on class vs. on objects • special method lookup • implicit lookup happens relative to execution namespaces... -
Scope (a.k.a. implicit lookup): 1. locals • function execution namespace
• use vars() or locals() 2. non-locals (closures) 3. globals • module execution namespace • use vars() or globals() 4. builtins • interpreter execution namespace • use builtins or __builtins__ -
sys.modules >>> import mordor Traceback (most recent call last): File
".../mordor.py", line 14, in <module> raise ImportError(boromir_says) ImportError: One does not simply import Mordor. -
Interpreter "top-level" namespaces: • implicit execution-globals: builtins • explicit execution-globals:
sys.modules ◦ access via import statement -
Interpreter "top-level" namespaces: • implicit execution-globals: builtins • explicit execution-globals:
sys.modules ◦ access via import statement • id() -
APIs: mapping-based • see collections.abc.Mapping property-based • use properties +
"private" attributes attribute-based • __getattribute__() and __getattr__() • __setattr__() • __delattr__() -
Key object attributes: • __dict__ • __dir__() • __slots__() •
__mro__ (for classes) -
Helpers: • globals() • locals() • vars() • dir() •
getattr() • setattr() • delattr() • hasattr() • inspect.getattr_static() • id() -
• namespaces: context + name => value • containers: ◦
mapping-based (volatile) ◦ property-based (static) ◦ attribute-based (stable) • pervasive in Python • rich APIs • rich tools for objects