Hoarded Homely Hints
Technical notes on software development, programming, operations and configuration of software.
Tuesday, May 21, 2013
Friday, April 19, 2013
Python str with custom truth values
I've run into multiple instances where I get a string from some external service like a config file or a database. There are values that I want to treat as true and others as false for the purposes of logic in my code. For example let's say I have a status string with a few possible values, some false and some true.
The thing is I don't want to hard code the truth values in the class definition. I want to pass them in to the creation of the derived class.
And now we have a function which creates a BooleanString class using a parameterized string as the comparable truth.
- "Yes", "on", "true"
- "No", "off", "false"
class BooleanString(str):
def __nonzero__(self):
return self.lower() in ('on', 'yes', 'true')
bool(BooleanString("YES")) # return True
bool(BooleanString("some other value")) # returns False
The thing is I don't want to hard code the truth values in the class definition. I want to pass them in to the creation of the derived class.
def mkboolstr(truth):
def __nonzero__(self):
return self.lower() in truth
return type('BooleanString', (str, ), dict(__nonzero__=__nonzero__))
BooleanString = mkboolstr(('on', 'yes', 'true'))
bool(BooleanString("TRUE")) # return True
bool(BooleanString("some other value")) # returns False
And now we have a function which creates a BooleanString class using a parameterized string as the comparable truth.
§
Labels:
class,
metaprogramming,
python,
type
Location:
Seattle, WA, USA
Python Expression Idiom: Merging Dictionaries
It's common in Python programming to need to merge 2 or more dictionaries together.
The first idiom is using the dict constructor. This idiom has it's limitations, however it will always work fine as long as the keys are all strings. Trying this with non-string keys will fail in Python 3.2 and later, and also fails in alternate Python implementations. The idiom itself is frowned upon, although I think for the wrong reasons.
There are also other choices which will work with any type of key. Unfortunately they require a tad more code.
UPDATE:
I have left out dict.update because it is only usable in a statement, not an expression. It also modifies a dictionary which may not be what I want to do. You can compare:
I prefer the expressions.
The first idiom is using the dict constructor. This idiom has it's limitations, however it will always work fine as long as the keys are all strings. Trying this with non-string keys will fail in Python 3.2 and later, and also fails in alternate Python implementations. The idiom itself is frowned upon, although I think for the wrong reasons.
d1 = dict(a=1, b=2, c=3) d2 = dict(c=4, d=5, e=6) # merging 2 dicts with the dict constructor merged_dict = dict(d1, **d2) # merging n dicts with the dict constructor merged_dict = reduce(lambda a, b: dict(a, **b), (d1, d1))
There are also other choices which will work with any type of key. Unfortunately they require a tad more code.
# merging n dicts with a generator comprehension
merged_dict = dict(i for iterator in (d1, d2) for i in iterator.iteritems())
# merging n dicts with dict comprehension
merged_dict = {k:v for iterator in (d1, d2) for k, v in iterator.iteritems()}
UPDATE:
I have left out dict.update because it is only usable in a statement, not an expression. It also modifies a dictionary which may not be what I want to do. You can compare:
def fn1(d1, d2):
d3 = d1.copy()
d3.update(d2)
return d3
# vs
return dict(d1, **d2)
# or
return {k:v for d in (d1, d2) for k, v in d.iteritems()}
I prefer the expressions.
§
Location:
Seattle, WA, USA
Thursday, April 18, 2013
Python Expression Idiom: Dict Slicing
As anyone who comes from Perl can tell you hash slicing is useful. Python dicts do not natively support slicing. One of the issues is slicing in Python seems limited to defined ranges, rather than an ad-hoc collection of values.
Take heart! There is an expression that has the same effect in Python as Perl's hash slicing:
Alternate solutions are usually more complicated expressions.
itemgetter provides a more simple expression for extracting multiple ad-hoc values from a dictionary.
Take heart! There is an expression that has the same effect in Python as Perl's hash slicing:
from operator import itemgetter
d = dict(a=1, b=2, c=3, d=4)
h, i, j, k = itemgetter('a', 'c', 'a', 'd')(d)
# h, i, j, k => 1, 3, 1, 4
Alternate solutions are usually more complicated expressions.
d = dict(a=1, b=2, c=3, d=4)
# conceptually more complicated expressions
h, i, j, k = (d[i] for i in ('a', 'c', 'a', 'd'))
h, i, j, k = map(d.get, ('a', 'c', 'a', 'd'))
itemgetter provides a more simple expression for extracting multiple ad-hoc values from a dictionary.
§
Location:
Seattle, WA, USA
Friday, December 21, 2012
Python: Encapsulating Exceptions with Context Managers
Try/except can often interrupt the flow of logic making code harder to read. Take for example the following piece of code:
import sys
class Car(object):
def create(self, color, stereo):
try:
vin = self._factory.make_car(color)
except FactoryColorError, err:
stacktrace = sys.exc_info()[2]
raise ValidationError(err.message), None, stacktrace
try:
self._customizer.update_car(vin, stereo)
except CustomizerError, err:
self._factory.remove_car(vin)
stacktrace = sys.exc_info()[2]
raise ValidationError(err.message), None, stacktrace
return vin
def update(self, vin, color, stereo):
try:
self._factory.update_car_color(vin, color)
except FactoryColorError, err:
stacktrace = sys.exc_info()[2]
raise ValidationError(err.message), None, stacktrace
try:
self._customizer.update_car(vin, stereo)
except CustomizerError, err:
stacktrace = sys.exc_info()[2]
raise ValidationError(err.message), None, stacktrace
return
A somewhat typical piece of code where the create and update are composed of multiple calls on the backend. So the logic is as follows:
- exception logic that changes third party exceptions into ValidationError
- rollback exception logic in create that removes the car if the stereo option is invalid
from contextlib import contextmanager
import sys
class Car(object):
@contextmanager
def _factory_error_handler(self):
try:
yield
except FactoryColorError, err:
stacktrace = sys.exc_info()[2]
raise ValidationError(err.message, None, stacktrace
@contextmanager
def _create_customizer_error_handler(self, vin):
try:
yield
except CustomizerError, err:
self._factory.remove_car(vin)
stacktrace = sys.exc_info()[2]
raise ValidationError(err.message), None, stacktrace
@contextmanager
def _update_customizer_error_handler(self, vin):
try:
yield
except CustomizerError, err:
stacktrace = sys.exc_info()[2]
raise ValidationError(err.message), None, stacktrace
def create(self, color, stereo):
with self._factory_error_handler():
vin = self._factory.make_car(color)
with self._create_customizer_error_handler(vin):
self._customizer.update_car(vin, stereo)
return vin
def update(self, vin, color, stereo):
with self._factory_error_handler():
self._factory.update_car_color(vin, color)
with self._update_customizer_error_handler(vin):
self._customizer.update_car(vin, stereo)
return
Refactoring the exception code to encapsulate the logic in it's own namespace in order to make the code more readable. The core logic is no longer obscured by the exception handling, in fact the handlers make both the core and the exception logic easy to read and understand. We are also able to de-dupe some of the exception logic through reuse of the _factory_error_handler. However, we can do better.
from contextlib import contextmanager
import sys
@contextmanager
def cleanup_error_handler(cleanup):
try:
yield
except Exception:
cleanup()
raise
def make_error_handler(catch, throw):
@contextmanager
def handler():
try:
yield
except catch, e:
stacktrace = sys.exc_info()[2]
raise throw(e.message), None, stacktrace
return handler
factory_error_handler = make_error_handler(FactoryError, ValidationError)
customizer_error_handler = make_error_handler(CustomizerError, ValidationError)
class Car(object):
def create(self, color, stereo):
with factory_error_handler():
vin = self._factory.make_car(color)
cleanup = partial(self._factory.remove_car, vin)
with cleanup_error_handler(cleanup), \
customizer_error_handler():
self._customizer.update_car(vin, stereo)
return vin
def update(self, vin, color, stereo):
with factory_error_handler():
self._factory.update_car_color(vin, color)
with customizer_error_handler():
self._customizer.update_car(vin, stereo)
return
We retained the readability of using the handlers, but extracted them completely from the class. The exception logic is now both encapsulated and decoupled from the specific class allowing reuse throughout the rest of the codebase.
§
Labels:
code reuse.,
context manager,
context object,
encapsulation,
exception,
exception handling,
python
Location:
Seattle, WA, USA
Thursday, December 20, 2012
Python Metaprogramming: Dynamically Adding Methods to Classes
Dynamic addition of methods and attributes to classes and instances.
Dynamic Class Method Addition
Occasionally you may need dynamically add a function as a method to a class. This is easily accomplished by assigning the function as an attribute of the class.
def fn(self):
return id(self), self, type(self)
# Traditional Class Definition
class A_Class(object):
def method_a(self):
return id(self), self, type(self)
instance = A_Class()
# Modify the class and add fn as a method
setattr(A_Class, 'method_b', fn)
# Call the traditionally defined method
instance.method_a()
# Call the dynamically added method
instance.method_b()
Dynamic Instance Method Addition
When you add the method to a class all instances can access it. If you only want a particular instance to have a method do this:from types import MethodType instance2 = A_Class() setattr(instance, fn.__name__, MethodType(fn, instance)) # Calls the fn method attached to the instance instance.fn() # Throws an exception instance2.fn()
§
Labels:
class,
meta programming,
metaprogramming,
oop,
python
Location:
Seattle, WA, USA
Friday, November 30, 2012
Configing Emacs: TRAMP with Putty
I've already written how to configure TRAMP in Emacs, but it only works on Mac and Linux. Unfortunately there are some who continue to use Windows as a desktop platform. While they may be second class netizens, they do not need to settle for second class remote editing.
Emacs is available for Windows. For secure remote editing you need ssh. Enter Putty, an excellent implementation of the SSH protocol for Windows. Putty comes with plink.exe which is the command line executable. Once you've installed Emacs and Putty on Windows you'll want to update the Path environment variable.
Once the Putty path has been appended, update your emacs config. Set your default tramp method to be plink and the tramp auto save directory to your username's temp directory.
This all you need to start remote editing with emacs using ssh. You can setup password-less logins to make it even easier. And if you have to go through an intermediary ssh server you can use transparent multihop with Putty.
Emacs is available for Windows. For secure remote editing you need ssh. Enter Putty, an excellent implementation of the SSH protocol for Windows. Putty comes with plink.exe which is the command line executable. Once you've installed Emacs and Putty on Windows you'll want to update the Path environment variable.
Once the Putty path has been appended, update your emacs config. Set your default tramp method to be plink and the tramp auto save directory to your username's temp directory.
(require 'tramp) (set-default 'tramp-auto-save-directory "C:\Users\<username>\AppData\Local\Temp") (set-default 'tramp-default-method "plink")
This all you need to start remote editing with emacs using ssh. You can setup password-less logins to make it even easier. And if you have to go through an intermediary ssh server you can use transparent multihop with Putty.
§
Location:
Seattle, WA, USA
Thursday, November 29, 2012
Python Metaprogramming: Dynamic Class and Metaclass Creation
Python has great metaprogramming capabilities.
Python classes are typically created using the class definition. Classes are used in much the same other classes in other OOP languages are used. They are used to define an instantiable object with associated methods and attributes. Classes are also one of the primary namespacing mechanisms in Python. The terms class and type are often used interchangeably in Python. Occasionaly you may need to dynamically create a class.
There are use cases for dynamically creating a class or type. Often it's a static definition doesn't meet your needs such as a type factory that needs to work on incoming types. You may need to create a type dynamically for testing purposes, or because it's required by a framework or library you need to use.
Metaclasses are a another kind of class. They are created in the same way as other classes except they derive from `type`. You can programmaticlly create a a metaclass as follows:
Python classes are typically created using the class definition. Classes are used in much the same other classes in other OOP languages are used. They are used to define an instantiable object with associated methods and attributes. Classes are also one of the primary namespacing mechanisms in Python. The terms class and type are often used interchangeably in Python. Occasionaly you may need to dynamically create a class.
There are use cases for dynamically creating a class or type. Often it's a static definition doesn't meet your needs such as a type factory that needs to work on incoming types. You may need to create a type dynamically for testing purposes, or because it's required by a framework or library you need to use.
NewClass = type('NewClass', (object, ), {})
Metaclasses are a another kind of class. They are created in the same way as other classes except they derive from `type`. You can programmaticlly create a a metaclass as follows:
def new(cls, name, bases, dct):
print "metaclass new", name
return type.__new__(cls, name, bases, dct)
def init(cls, name, bases, dct):
print "metclass init", name
super(type(cls), cls).__init__(name, bases, dct)
DynamicMetaClass = type('DynamicMetaClass',(type, ),dict(__init__=init, __new__=new))
X = DynamicMetaClass('X',(), dict(foo=lambda self:'foo'))
# returns metaclass new X
# metclass init X
§
Labels:
class,
meta programming,
metaclass,
metaprogramming,
oop,
python
Location:
Seattle, WA, USA
Wednesday, November 28, 2012
Python Metaprogramming: Dynamic Module Creation
Python has great metaprogramming capabilities.
Python modules are implicitly created with a file. It is one of the primary namespacing mechanisms in Python. Occasionally you may wan to dynamically create one.
There are a few use cases for dynamic module creation. The use case that comes up most frequently is modules created in the service of automated tests. Sometimes a module is needed as input and sometimes it's needed as a mock. Other use cases exist, but are more rare in my experience. One I have done recently is automatic module creation to create the appropriate modules a framework or library expects. There have been other instances as well, although infrequent. Regardless, it's a useful tool to have in your toolbox should the need ever arise.
Python modules are implicitly created with a file. It is one of the primary namespacing mechanisms in Python. Occasionally you may wan to dynamically create one.
There are a few use cases for dynamic module creation. The use case that comes up most frequently is modules created in the service of automated tests. Sometimes a module is needed as input and sometimes it's needed as a mock. Other use cases exist, but are more rare in my experience. One I have done recently is automatic module creation to create the appropriate modules a framework or library expects. There have been other instances as well, although infrequent. Regardless, it's a useful tool to have in your toolbox should the need ever arise.
from types import ModuleType
import sys
def make_module(new_module, doc="", scope=locals()):
"""
make_module('a.b.c.d', doc="", scope=locals()]) ->
* creates module (and submodules as needed)
* adds module (and submodules) to sys.modules
* correctly nests submodules as needed
"""
module_name = []
for name in new_module.split('.'):
m = ModuleType(name, doc)
parent_module_name = '.'.join(module_name)
if parent_module_name:
setattr(sys.modules[parent_module_name], name, m)
else:
scope[name] = m
module_name.append(name)
sys.modules['.'.join(module_name)] = m
return sys.modules['.'.join(module_name)]
§
Labels:
meta programming,
metaprogramming,
module,
oop,
python
Location:
Seattle, WA, USA
Friday, November 16, 2012
Python Class Decorator: Logging
Python class decorators provide powerful metaprogramming capabilities. Instead of a function, a class decorator takes a class and returns a class. The decorator can manipulate the incoming class, or simply create a new class and return it.
The following piece of code is an example of leveraging a decorator to do Aspect Oriented Programming. The classic example in AOP is the addition of logging to methods and functions. Rather than writing a function decorator, and adding the decorator to each function, you can choose to create a class decorator and logging by class.
The following piece of code is an example of leveraging a decorator to do Aspect Oriented Programming. The classic example in AOP is the addition of logging to methods and functions. Rather than writing a function decorator, and adding the decorator to each function, you can choose to create a class decorator and logging by class.
import logging
log = logging.getLogger(name)
from functools import wraps
from types import MethodType
def class_logging(klass):
"""
Class decorator that adds logging to all "public" class methods.
"""
method_p = lambda m: not m.startswith('_') and \
isinstance(getattr(klass, m), MethodType)
public_methods = filter(method_p, dir(klass))
for method_name in public_methods:
class_method = getattr(klass, method_name)
def helper(mname, method):
@wraps(method)
def wrapper(*a, **kw):
msg = '{0}({1}, {2})'.format(mname, a, kw)
log.debug(msg)
try:
response = method(*a, **kw)
except Exception, e:
error_message = 'no additional information'
if hasattr(e, 'message'):
error_message = e.message
msg = '{0} raised {1}, {2}'.format(mname, type(e), error_message)
log.debug(msg)
raise
else:
msg = '{0} returned {1}'.format(mname, response)
log.debug(msg)
return response
return wrapper
fn = MethodType(helper(method_name, class_method), None, klass)
setattr(klass, method_name, fn)
return klass
§
Location:
Seattle, WA, USA
Subscribe to:
Posts (Atom)
