People used to statically typed languages coming to Python often complain that you have to use "self" (or whichever name you want, but self is most common) to refer to a method or variable in that object. You also have to explicitly pass a reference to the method's object in every call to it. Many people new to Python are annoyed by this and feels that it forces a lot of unnecessary typing. This recipe presents a method which makes "self" implicit.
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 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
import sys, inspect from types import FunctionType def magic(): """ Returns a string which represents Python code that copies instance variables to their local counterparts. I.e: var = self.var var2 = self.var2 """ s = "" for var, value in inspect.getmembers(sys._getframe(1).f_locals["self"]): if not (var.startswith("__") and var.endswith("__")): s += var + " = self." + var + "\n" return s def outdent_lines(lines): """ Removes the outer indentation on the method's source code. The extra indentation comes from the fact that the method is defined within a class and therefore has an extra level of indentation around itself. """ outer_ws_count = 0 for ch in lines: if not ch.isspace(): break outer_ws_count += 1 return [line[outer_ws_count:] for line in lines] def insert_self_in_header(header): return header[0:header.find("(") + 1] + "self, " + \ header[header.find("(") + 1:] def get_indent_string(srcline): """ Determines and returns how the line passed in is indented. That information is used by rework() so that it can determine how much indentation to use to insert lines into the source. """ indent = "" for ch in srcline: if not ch.isspace(): break indent += ch return indent def rework(func): """ rework() modifies the functions source code by first adding self as the first argument in the parameter list and then adding 'exec(magic())' as the first line of the function body. It does this by reading the functions source code and then creating a new modified function definition with exec() and then returns the new function. """ srclines, line_num = inspect.getsourcelines(func) srclines = outdent_lines(srclines) dst = insert_self_in_header(srclines) if len(srclines) > 1: dst += get_indent_string(srclines) + "exec(magic())\n" for line in srclines[1:]: dst += line dst += "new_func = eval(func.__name__)\n" exec(dst) return new_func class LocalVarsMC(type): """ A metaclass that transform all instance methods by applying rework() on them all. Some of the implementation is borrowed from http://www.xs4all.nl/~thomas/python/conveniencytypes.py. """ def __init__(self, name, bases, attrs): super(LocalVarsMC, self).__init__(name, bases, attrs) ## Creates an unbound super object supername = "_%s__super" % name if hasattr(self, supername): raise TypeError, "Conflicting classname " + supername setattr(self, supername, super(self)) try: for attr, value in attrs.items(): if isinstance(value, FunctionType): setattr(self, attr, rework(value)) except IOError: print "Couldn't read source code - it wont work." sys.exit() class LocalsFromInstanceVars(object): """ Inherit from this class to make it work. """ __metaclass__ = LocalVarsMC import math ## ------- testcode --------- class Vector3d(LocalsFromInstanceVars): def __init__(x, y, z): self.x, self.y, self.z = x, y, z def length(): return math.sqrt(x*x + y*y + z*z) def dummy(): pass if __name__ == "__main__": print "-"*20 v = Vector3d(5, 4, 3) print v.length() v.x = 10 print v.length() print v.dummy()
The recipe works using a metaclass that substitutes all function implementations in the class by recompiling their source code. The first change changes the function definition so that in the Vector3d example "def length()" becomes "def length(self)." That has the obvious side effect of making it impossible to add class or static methods to the class, but hopefully, that's not to important. :) If it is important, one alternative is to not subclass LocalsFromInstanceVars and instead wrap the methods you do not want to write "self." in individually:
.class Foo: . def method(): . pass . method = rework(method)
The second change makes it so that on each invokation of a method in the object all names attached to the object will be copied to that method's local namespace. The added code for length() is:
.def length(self): . _LocalsFromInstanceVars__super = self._LocalsFromInstanceVars__super . _Vector3d__super = self._Vector3d__super . dummy = self.dummy . length = self.length . x = self.x . y = self.y . z = self.z
With that you can then REFER to the instance members without using "self.", but you cannot assign to them. Setting x = 45 for example, will not have any effect on the object.
Another limitation is that oneliner methods cannot be used in classes that subclass LocalsFromInstanceVars. On my Python (2.3.4) they confused the inspect module and made the getsourcelines() function behave strangely.
With all those limitations (and more bugs I havent thought of) and with the general consensus that using "self" is a Good Thing , this code should probably not be used for anything other than demonstration purpouses. If even that, but it was fun to write. :)
Disagree with the approach. I strongly disagree with these kind of recipes. Python has its own way for doing things and a Python programmer should adopt those views right form the beginning. Letting people programming in Python the way they do in [other language] is the worst thing you can do. Of course, we have to make a strong distinction between things like this which can make your code look like [other language] and between extending Python with features from [other language]. The latter can be useful.
you are courageous. I did something similar a couple of years ago (inspect.getsource + metaclass) but I never had the courage to publish it ;-)
BTW, the textwrap module has a dedent function which you may want to use.
Please don't publicize this recipe. I was thinking of adding a comment similar to Sandor's. There is always some uncomfortableness when encountering change. That's the pain of growoth. This recipe is like apsirin that helps the symptom but not the root cause. Although I must say I do find it clever. But it really is not doing people new to the language justice. If you are learning python then learn python and the philosophy of python, one is better served by staying with the legacy language that they already know.
Excellent recipe! I used python a number of years ago before I discovered Ruby. The biggest issue I had with the language was that the OO-bit felt like a late add-on and having to repeatably declare and send "self" to an objects own methods felt Python was as much an OO language as C was.
Object oriented programming in C also requires explicit sending of a context object to a function, same as Python and that is just annoying when you try to envision you're playing with a bunch of objects.
The only thing I miss from Python was the speed which, truth be told, Ruby has a complete lack of.
I like shorthand. I'd adopt this sort of writing by creating a flex/bison preprocessor that transforms a non-python file into a .py. Something like this:
And of course I'd have a custom version of python that knows to search for dates on .PREPY
I usually don't use the somewhat irrelevant construct:
I sort of agree with the anonymous Sandor---my solution shows I wouldn't call the file a ".py". But, if you can write it in python it's pythonic! My problem with making self implicit by your means, if I understand how it works, is that it recompiles class methods every time it loads even the .pyc file. I almost always avoid "exec" and "eval".