This is an example of metaclass-as-code-generator. The metaclass writes the requested get/set methods automatically.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | #!/usr/bin/python
# Helpers
def _addMethod(fldName, clsName, verb, methodMaker, dict):
"""Make a get or set method and add it to dict."""
compiledName = _getCompiledName(fldName, clsName)
methodName = _getMethodName(fldName, verb)
dict[methodName] = methodMaker(compiledName)
def _getCompiledName(fldName, clsName):
"""Return mangled fldName if necessary, else no change."""
# If fldName starts with 2 underscores and does *not* end with 2 underscores...
if fldName[:2] == '__' and fldName[-2:] != '__':
return "_%s%s" % (clsName, fldName)
else:
return fldName
def _getMethodName(fldName, verb):
"""'_salary', 'get' => 'getSalary'"""
s = fldName.lstrip('_') # Remove leading underscores
return verb + s.capitalize()
def _makeGetter(compiledName):
"""Return a method that gets compiledName's value."""
return lambda self: self.__dict__[compiledName]
def _makeSetter(compiledName):
"""Return a method that sets compiledName's value."""
return lambda self, value: setattr(self, compiledName, value)
class Accessors(type):
"""Adds accessor methods to a class."""
def __new__(cls, clsName, bases, dict):
for fldName in dict.get('_READ', []) + dict.get('_READ_WRITE', []):
_addMethod(fldName, clsName, 'get', _makeGetter, dict)
for fldName in dict.get('_WRITE', []) + dict.get('_READ_WRITE', []):
_addMethod(fldName, clsName, 'set', _makeSetter, dict)
return type.__new__(cls, clsName, bases, dict)
if __name__ == "__main__":
class Employee:
__metaclass__ = Accessors
_READ_WRITE = ['name', 'salary', 'title', 'bonus']
def __init__(self, name, salary, title, bonus=0):
self.name = name
self.salary = salary
self.title = title
self.bonus = bonus
b = Employee('Joe Test', 40000, 'Developer')
print 'Name:', b.getName()
print 'Salary:', b.getSalary()
print 'Title:', b.getTitle()
print 'Bonus:', b.getBonus()
b.setBonus(5000)
print 'Bonus:', b.getBonus()
class ReadOnly:
__metaclass__ = Accessors
_READ = ['__data']
def __init__(self, data):
self.__data = data
ro = ReadOnly('test12345')
print 'Read-only data:', ro.getData()
class WriteOnly:
__metaclass__ = Accessors
_WRITE = ['_data']
def __init__(self, data):
self._data = data
wo = WriteOnly('test67890')
print 'Write-only data:', wo._data
wo.setData('xzy123')
print 'Write-only data:', wo._data
|
Metaclasses provide some of the code-generation power of Lisp macros. You can change the language without having to hack the compiler.
This recipe handles all the leading-underscore name variations correctly.
A caveat: The metaclass doesn't do any creation/initialization of the actual data attributes. You should handle that as you normally would, i.e. in an __init__ method.
Related recipes:
Generating get/set methods using closures: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/259111
Simple read only attributes with meta-class programming: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/197965
using naming conventions. This is the implementation of a similar idea that I use in SQLObject ( http://sqlobject.org ). This function simply looks for appropriately-named methods, and creates properties based on that.
While it's just a builder function, that function can be used in a metaclass fairly easily to automatically call the function with the new class as an argument. To make a read-only attribute, you simply don't create a _set_attr method for that attribute.
overloading accessors... In this example you cannot overload the generated accessors. You ca do this by adding a line to the _addMethod function:
then: