Writing code like this can be very repetitive:
class c: def __init__(self, memberVariableNumberOne, memberVariableNumberTwo): self. memberVariableNumberOne = memberVariableNumberOne self. memberVariableNumberTwo = memberVariableNumberTwo
The above can be changed to:
class c: def __init__(self, memberVariableNumberOne, memberVariableNumberTwo): AssignMemberVariablesFromParameters()
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 | import inspect
def AssignMemberVariablesFromParameters(exclude=None, onlyInclude=None):
"""Assign member variables in the caller's object from the caller's parameters.
The caller should be a method associated with an object. Keyword arguments
are supported, but variable arguments are not since they don't have names.
exclude is an optional iterable that specifies names to be explicitly
excluded. If the optional iterable onlyInclude is specified,
parameters / member variables not in onlyInclude will be ignored.
>>> class c:
... def __init__(self, a, b, c=3, **kwargs):
... AssignMemberVariablesFromParameters()
...
... def ignore_a_b(self, a, b, c, d):
... AssignMemberVariablesFromParameters(exclude=['a', 'b'])
...
... def ignore_c_d(alternateNameForSelf, a, b, c, d):
... AssignMemberVariablesFromParameters(onlyInclude=['a', 'b'])
>>> x = c(1, 2, d=4)
>>> (x.a, x.b, x.c, x.d)
(1, 2, 3, 4)
>>> x.ignore_a_b(10, 20, 30, 40)
>>> (x.a, x.b, x.c, x.d)
(1, 2, 30, 40)
>>> x.ignore_c_d(100, 200, 300, 400)
>>> (x.a, x.b, x.c, x.d)
(100, 200, 30, 40)
>>> class c:
... __slots__ = ['a', 'b', 'c']
...
... def __init__(self, a, b):
... AssignMemberVariablesFromParameters()
>>> x = c(1, 2)
>>> (x.a, x.b)
(1, 2)
"""
args, varargs, varkw, defaults = inspect.getargvalues(inspect.stack()[1][0])
self = args[0]
if exclude == None:
exclude = []
else:
exclude = [arg for arg in exclude]
if onlyInclude == None:
onlyInclude = [arg for arg in args if arg != self]
if varkw:
onlyInclude += [arg for arg in defaults[varkw].keys() if arg != self]
for arg in onlyInclude:
if arg not in exclude:
if arg in defaults:
value = defaults[arg]
elif varkw and arg in defaults[varkw]:
value = defaults[varkw][arg]
else:
value = None
exec 'defaults[self].%s = %s' % (arg, 'value')
|
This does have the potential disadvantage of introducing magic into code which always creates the possibility of subtle bugs. To minimize this possibility it is recommended that the use of this function be made quite obvious (e.g., by always making it the first line of methods in which it is used).
The advantage of this magic is that member variable names are typed once instead of three times, reducing the likelihood of misspellings and making it easier to rename member variables.
This implementation requires the inspect module which was added in Python 2.1.
another solution to the same problem. By coincidence, I recently wrote a similar function. My solution aims to be simpler and more obvious (less magic) to the reader who encounters a usage. Here it is, with my docstrings:
More Pythonic alternative to using exec. Instead of
a more Pythonic alternative might be
Here's a trimmed version with a suggested name change. I think the name __update__ works here for two reasons:
The leading and trailing double underscores let users know that there's something special happening, and
what this method is doing is updating the dictionary of an instance with another dictionary made up of key/value pairs from __init__'s parameter list, i.e., something like this is going on:
self.__dict__.update(initparamsdict)
So, since what we're doing is updating a dictionary, in a special way, and the method to do that is called 'update', it seems like '__update__' would be a reasonable name.
def __update__(include=[], exclude=[]):
some testing
class c:
x = c(1,2,d=4)
print "x.__dict__ = %s"%x.__dict__
x.ignore_a_b(10,20,30,40)
print "x.__dict__ = %s"%x.__dict__
x.ignore_c_d(100,200,300,400)
print "x.__dict__ = %s"%x.__dict__
outputs
x.__dict__ = {'a': 1, 'c': 3, 'b': 2, 'd': 4}
x.__dict__ = {'a': 1, 'c': 30, 'b': 2, 'd': 40}
x.__dict__ = {'a': 100, 'c': 30, 'b': 200, 'd': 40}
Here's a somewhat more explicit version:
def params(include=[], exclude=[]):
This is used as follows:
class c:
This method is less concise, but it makes it easier to understand what you're trying to do - add attributes to self's __dict__ using the names and values supplied in __init__'s parameter list. Plus, you have the added benefit of a generally useful method "params()".
I like it - I've switched to this approach in my own code.
One thing to watch out for with this approach is that new style classes that make use of __slots__ don't have a __dict__ attribute.
Some relections after long term use. After using this in quite a bit of code over the past 18 months I've noticed some pitfalls. I still like the idea of not having to type (or mis-type) instance variable names 3 times in the constructor, but these pitfalls should be weighed against that advantage to see if you like the balance.
First, when inspecting old code it is sometimes nice to search for self.x to determine where the instance variable x was initialized. After scratching my head for a while as to why I can only find self.x being used and not being initialized, I eventually remember that this recipe is in use. This isnt a huge deal, but it can be distracting when youre trying to find a bug.
Also, the inspect module is not terribly fast at this kind of thing. This isnt a problem for instantiating objects a few times, but the time to run AssignMemberVariablesFromParameters can dominate the creation time of small objects. For example take this code:
Profiling this on Python 2.3.3 on Windows XP indicates that over 95% of the total run time is spent inside inspect.stack. So if you have a coordinate class and you need to create thousands of coordinates then you should keep an eye on how this approach impacts your performance requirements.
Finally, source code analysis tools like PyChecker dont do well with this approach. Because of the magic involved, it appears that the parameters are never used and that the instance variables are never initialized.
A more generally applicable version of techniques like this can be found in:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/201195