The key in making great and growable systems is much more to design how its modules communicate rather than what their internal properties and behaviors should be.
— Alan Kay on Messaging
It's a way to request behavior from an entity in a distributed system.
To communicate via message is a 4-step process
This last step is method dispatching!!
It's the process to select which action depending on the the message.
def __postoffice__(message): recipient = message.recipient name = message.contents.name args = message.contents.args kwargs = message.contents.kwargs if not hasattr(recipient, name): raise AttributeError member = getattr(recipient, name) return member(*args, **kwargs)
It's the equivalent to
def __getattribute__(self, membername): """Get the value with the key <membername> inside self.__dict__; if not, look for the same key inside type(self).__dict__; if not, look for it through the class hierarchy. If not, call self.__getattr__(membername) if exists."""
Intercepts all the attemps to access a member before trying to retrieve it from the instance.
It's a fallback for when the member does not exist.
def __getattr__(self, membername): """If exists inside the instance, it's called when the membername was not found at all. It can return an object or raise AttributeError."""
Intercepts the attemps to access a member only if it does not exist.
It's the post office when trying to assign a value.
def __setattr__(self, membername, value): """Replace the entry in self.__dict__ for the membername with value. (Well, actually, it's a little bit more complicated)."""
Intercepts all the attemps to set a member with the specified value.
if not called explicitly, the magic methods are never called from the instance but from its class.
from types import MethodType as method class A(): pass a = A() a.__setattr__ = \ method(lambda : print('Doing nothing...'), a) a.test = 1
If I make a little typo, I want the API to be smart enough to figure out which member I'm referring and get it fixing the typo.
The idea is to use the most similar method without ambiguity when it's not found in the instance.
SequenceMatcher to calculate similarity:
from difflib import SequenceMatcher def similarity(n, m): return SequenceMatcher(None, n, m).ratio()
__getattr__() to the class.
def __getattr__(self, name): issimilar = lambda n: similarity(n, name) >= 0.8 matches = list(filter(issimilar, dir(self))) if not matches or len(matches) > 1: raise AttributeError() return getattr(self, matches)
Remeber it's only called if the member is not found.
Download fuzzyfy.py for an implementation.
From a base class, I want to derive another with partial access to its public members.
Provided a base class and a whitelist of members, only allow access to a member if it is in the whitelist.
So, as it's a metter of access, I need to intercept all access.
def __getattribute__(self, name): if name != '_' and name not in whitelist: raise RuntimeError() return base.__getattribute__(self, name)
def __setattr__(self, name, value): if name != '_' and name not in whitelist: raise RuntimeError() return base.__settattr__(self, name, value)
Download restricted.py for an implementation.
Deny the access to a member starting by
_if it's being used outside the class' (or subclasses') declarations.
When accessing a member, inspect the current stack to locate the line of code accessing the member and see if the line is part of the code for the class. If not, deny the access.
Use the module
inspect to get the sourcefile and
source lines from a class.
class A(): """ Source code """ def f(): print('Hello!') import inspect lines, offset = inspect.getsourcelines(A)
Copy to a file or it won't work.
Use in combination with
__mro__ to get the source
files and source blocks for all classes in the class hierarchy.
klass = type(obj) # ignore built-in object class klasses = list(klass.__mro__[:-1]) sources =  for k in klasses: sourcefile = inspect.getsourcefile(k) lines, offset = inspect.getsourcelines(k) end = offset + len(lines) sources.append(sourcefile, offset, end)
currentframe() to get a list of the current
def a(): b() def b(): c() def c(): import inspect frames = inspect.getouterframes( inspect.currentframe()) print(list(frames))
Each entry is a tuple including the sourcefile and line number.
Download protected.py for an implementation.
Jump to the infography about privates in Python.