GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing python-patterns.guide — The Global Object Pattern The Global Object Pattern¶Verdict Like several other scripting languages, Python parses the outer level of each module as normal code. Un-indented assignment statements, expressions, and even loops and conditionals will execute as the module is imported. This presents an excellent opportunity to supplement a module’s classes and functions with constants and data structures that callers will find useful — but also offers dangerous temptations: mutable global objects can wind up coupling distant code, and I/O operations impose import-time expense and side effects. Every Python module is a separate namespace.
A module like Separate namespaces are crucial to making a programming language tractable. If Python modules were not separate namespaces, you would be unable to read or write Python code by keeping your attention focused on the module in front of you — a line of code might use, or accidentally conflict with, a name defined anywhere else in the Standard Library or a third-party module you have installed. Upgrading a third-party module could break your entire program if the new version defined a new global that conflicted with yours. Programmers who are forced to code in a language without namespaces soon find themselves festooning global names with prefixes, suffixes, and extra punctuation in a desperate race to keep them from conflicting. While every function and class is, of course, an object — in Python, everything is an object — the Module Global pattern more specifically refers to normal object instances that are given names at the global level of a module. Two patterns use Module Globals but are important enough to warrant their own articles:
This article will cover some other common cases. The Constant Pattern¶Modules often assign useful numbers, strings, and other values to names in their global scope. The Standard Library includes many such assignments, from which we can excerpt a few examples. January = 1 # calendar.py WARNING = 30 # logging.py MAX_INTERPOLATION_DEPTH = 10 # configparser.py SSL_HANDSHAKE_TIMEOUT = 60.0 # asyncio.constants.py TICK = "'" # email.utils.py CRLF = "\r\n" # smtplib.py Remember that these are “constants” only in the sense that the objects themselves are immutable. The names can still be reassigned. import calendar calendar.January = 13 print(calendar.January) 13 Or deleted, for that matter. del calendar.January print(calendar.January) Traceback (most recent call last): ... AttributeError: module 'calendar' has no attribute 'January' In addition to integers, floats, and strings, constants also include immutable containers like tuples and frozen sets: all_errors = (Error, OSError, EOFError) # ftplib.py bytes_types = (bytes, bytearray) # pickle.py DIGITS = frozenset("0123456789") # sre_parse.py More specialized immutable data types also serve as constants: _EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc) # datetime On rare occasions,
a module global
which the code clearly never intends to modify
uses a mutable data structure anyway.
Plain mutable sets
are common in code that pre-dates the invention of the # socket.py _blocking_errnos = { EAGAIN, EWOULDBLOCK } # locale.py windows_locale = { 0x0436: "af_ZA", # Afrikaans 0x041c: "sq_AL", # Albanian 0x0484: "gsw_FR",# Alsatian - France ... 0x0435: "zu_ZA", # Zulu } Constants are often introduced as a refactoring:
the programmer notices that the same value These advantages are weighty enough that a constant is sometimes introduced even for a value that’s used only once, hoisting a literal that was hidden deep in the code up into visibility as a global. Some programmers place constant assignments close to the code that use them; others put all constants at the top of the file. Unless a constant is placed so close to its code that it will always be in view of human readers, it can be more friendly to put constants at the top of the module for the easy reference of readers who haven’t yet configured their editors to support jump-to-definition. Another kind of constant is not directed inwards,
towards the code in the module itself,
but outwards as part of the module’s advertised API.
A constant like You might expect that a constant intended for the module’s own use, but not intended for callers, would always start with an underscore to mark it private. But Python programmers are not consistent in marking constants private, perhaps because the cost of needing to keep a constant around forever because a caller might have decided to start using it is smaller than the cost of having a helper function or class’s API forever locked up. Import-time computation¶Sometimes constants are introduced for efficiency, to avoid recomputing a value every time code is called. For example, even though math operations involving literal numbers are in fact optimized away in all modern Python implementations, developers often still feel more comfortable making it explicit that the math should be done at import time by assigning the result to a module global: # zipfile.py ZIP_FILECOUNT_LIMIT = (1 << 16) - 1 When the math expression is complicated, assigning a name also enhances the code’s readability. As another example,
there exist special floating point values
that cannot be written in Python as literals;
they can only be generated by passing a string to the float type.
To avoid calling # encoder.py INFINITY = float('inf') A constant can also capture the result of a conditional to avoid re-evaluating it each time the value is needed — as long, of course, as the condition won’t be changing while the program is running. # shutil.py COPY_BUFSIZE = 1024 * 1024 if _WINDOWS else 16 * 1024 My favorite example of computed constants in the Standard Library
is the It turns out? I was wrong. The Without any special access to language internals, it does what anyone else would do to learn what type functions have. It creates a function, then asks its type: # types.py def _f(): pass FunctionType = type(_f) On the one hand,
this makes the Dunder Constants¶A special case of constants defined at a module’s global level are “dunder” constants whose names start and end with double underscores. Several Module Global dunder constants are set by the language itself.
For the official list,
look for the “Modules” subheading in the Python Reference’s section on
the standard type hierarchy.
The two encountered most often are here = os.path.dirname(__file__) Beyond the dunder constants set by the language runtime,
there is one Python recognizes if a module chooses to set it:
if Even though most modules never plan to modify Beyond these official dunder constants,
some modules add even more.
Assignments to names like Beware that there does not seem to be agreement,
even within the Standard Library,
about what type # bz2.py __author__ = "Nadeem Vawda <[email protected]>" # inspect.py __author__ = ('Ka-Ping Yee <[email protected]>', 'Yury Selivanov <[email protected]>') Why not VERSION = "1.3.0" # etree/ElementTree.py version = "0.20" # sax/expatreader.py version = "0.9.0" # tarfile.py To avoid the inconsistencies surrounding
these informal and ad-hoc metadata conventions,
a package that expects to be installed with The Global Object Pattern¶In the full-fledged Global Object pattern, as in the Constant pattern, a module instantiates an object at import time and assigns it a name in the module’s global scope. But the object does not simply serve as data; it is not merely an integer, string, or data structure. Instead, the object is made available for the sake of the methods it offers — for the actions it can perform. The simplest Global Objects are immutable. A common example is a compiled regular expression — here are a few examples from the Standard Library: escapesre = re.compile(r'[\\"]') # email/utils.py magic_check = re.compile('([*?[])') # glob.py commentclose = re.compile(r'--\s*>') # html/parser.py HAS_UTF8 = re.compile(b'[\x80-\xff]') # json/encoder.py Compiling a regular expression as a module global is a good example of the more general Global Object pattern. It achieves an elegant and safe transfer of expense from later in a program’s runtime to import time instead. The tradeoffs are:
This list of tradeoffs is about the same, by the way, if you move a regular expression out into a class attribute instead of moving it all the way out to the global scope. When I finally get around to writing about Python and classes, I’ll link from here to further thoughts on class attributes. Global Objects that are mutable¶But what about Global Objects that are mutable? They are easiest to justify when they wrap system resources
that are by their nature also global to an operating system process.
One example in the Standard Library itself is the Now,
it is arguable whether your program
should really be writing new values into its environment as it runs.
If you’re launching a subprocess
that needs an environment variable adjusted,
the # os.py environ = _createenviron() Through this global object, the various routines, and perhaps threads, in a Python program coordinate their access to this process-wide resource. Any change: import os os.environ['TERM'] = 'xterm' — will be immediately visible to any other part of the program that reads that environment key: >>> os.environ['TERM'] 'xterm' The problems with coupling distant parts of your codebase, and even unrelated parts of different libraries, through a unique global object are well known.
The Standard Library has more examples of the Mutable Global pattern — both public globals and private ones litter its modules. Some correspond to unique resources at the system level: # Lib/multiprocessing/process.py _current_process = _MainProcess() _process_counter = itertools.count(1) Others correspond to no outside resource but instead serve as single points of coordination for a process-wide activity like logging: # Lib/logging/__init__.py root = RootLogger(WARNING) Third-party libraries can supply dozens of more examples, from global HTTP thread pools and database connections to registries of request handlers, library plugins, and third-party codecs. But in every case, the Mutable Global courts all of the dangers listed above in return for the convenience of putting a resource where every module can reach it. My advice, to the extent that you can, is to write code that accepts arguments and returns values computed from them. Failing that, try passing database connections or open sockets to code that will need to interact with the outside world. It is a compromise for code that finds itself stranded from the resources it needs to resort to accessing a global. The glory of Python, of course, is that it usually makes even anti-patterns and compromises read fairly elegantly in code. An assignment statement at the global level of a module is as easy to write and read as any other assignment statement, and callers can access the Mutable Global through exactly the same import statement they use for functions and classes. Import-time I/O¶Many of the worst Global Objects are those that perform file or network I/O at import time. They not only impose the cost of that I/O on every library, script, and test that need the module, but expose them to failure if a file or network is not available. Library authors have an unfortunate tendency to make assumptions like
“the file Even when faced with this possibility,
a module author might still try to defend their import-time I/O:
“But delaying the I/O until after import time
simply postpones the inevitable —
if the system doesn’t have
For all of these reasons, it’s best for your global objects to wait until they’re first called before opening files and creating sockets — because it’s at the moment of that first call that the library knows the main program is now up and running, and knows that its services are in fact definitely needed in this particular run of the program. I’ll admit that, when my package needs to load a small data file that’s embedded in the package itself, I do sometimes break this rule. |
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |