Welcome, guest | Sign In | My Account | Store | Cart

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.

Python, 114 lines
  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[0]:
        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[0])
    if len(srclines) > 1:
        dst += get_indent_string(srclines[1]) + "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. :)

5 comments

aspn 16 years, 10 months ago  # | flag

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.

Sandor

Michele Simionato 16 years, 10 months ago  # | flag

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.

                      Michele Simionato
Kevin Dahlhausen 16 years, 10 months ago  # | flag

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.

Jon Tidell 13 years, 4 months ago  # | flag

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.

David Lambert 13 years, 1 month ago  # | flag

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:

$ autoself < module.py.PREPY > module.py

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:

class C:
    def method(*args,**kwargs):
        #self = args[0]

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".