Skip to content

Superficial Reflections programming

Der "läuft gegen"-Operator in C++

#include <iostream>

int main(int argc, char **argv)
{
    int x = 10;

    // x goes to 0
    while (x --> 0)
        std::cout << x << std::endl;
}

Getter und Setter mit Dekoratoren unter Python 2.6

class Spam(object):
    def __init__(self, value):
        self.value = value

    @property
    def spam(self):
        return self.value

    @spam.setter
    def spam(self, value):
        self.value = value

spam = Spam(42)
print spam.spam
spam.spam = 23
print spam.spam

Wegoptimierte Syntaxfehler

Manche Syntaxfehler können in CPython wegoptimiert werden:

>>> if 0:
...     yield
...
>>> if 1:
...     yield
  File "<input>", line 2
SyntaxError: 'yield' outside function (<input>, line 2)

tail call optimization in CPython

CPython unterstützt ja bekanntermaßen keine TCO von sich aus, weshalb es zahlreiche mehr oder weniger schöne Hacks gibt, die das CPython beibringen. Aber einen wirklich beeindruckenden habe ich eben in #python auf Freenode gesehen, geschrieben von habnabit. Und zwar wird der Bytecode geändert, CALL_FUNCTION wird zu JUMP_ABSOLUTE geändert.

Nachtrag: Das müsste eigentlich recht zuverlässig funktionieren, unter verschiedenen CPython-Versionen. Für das CALL_FUNCTION müssen die Argumente eh auf dem Stack liegen. Diese werden jetzt einfach mit STORE_FAST in die Namen geschrieben, die die Funktion entgegen nimmt, und dann wird wieder zum Anfang der Funktion gesprungen.

import inspect, pprint, types, dis, struct, opcode, array
short = struct.Struct('<H')

class Label(object):
    pass

class Code(object):
    @classmethod
    def from_code(cls, code_obj):
        self = cls()
        self.code_obj = code_obj
        self.names = list(code_obj.co_names)
        self.varnames = list(code_obj.co_varnames)
        self.consts = list(code_obj.co_consts)
        ret = []
        line_starts = dict(dis.findlinestarts(code_obj))
        code = code_obj.co_code
        labels = dict((addr, Label()) for addr in dis.findlabels(code))
        i, l = 0, len(code)
        extended_arg = 0
        while i < l:
            op = ord(code[i])
            if i in labels:
                ret.append(('MARK_LABEL', labels[i]))
            if i in line_starts:
                ret.append(('MARK_LINENO', line_starts[i]))
            i += 1
            if op >= opcode.HAVE_ARGUMENT:
                arg, = short.unpack(code[i:i + 2])
                arg += extended_arg
                extended_arg = 0
                i += 2
                if op == opcode.EXTENDED_ARG:
                    extended_arg = arg << 16
                    continue
                elif op in opcode.hasjabs:
                    arg = labels[arg]
                elif op in opcode.hasjrel:
                    arg = labels[i + arg]
            else:
                arg = None
            ret.append((opcode.opname[op], arg))
        self.code = ret
        return self

    def to_code(self):
        code_obj = self.code_obj
        co_code = array.array('B')
        co_lnotab = array.array('B')
        label_pos = {}
        jumps = []
        lastlineno = code_obj.co_firstlineno
        lastlinepos = 0
        for op, arg in self.code:
            if op == 'MARK_LABEL':
                label_pos[arg] = len(co_code)
            elif op == 'MARK_LINENO':
                incr_lineno = arg - lastlineno
                incr_pos = len(co_code) - lastlinepos
                lastlineno = arg
                lastlinepos = len(co_code)

                if incr_lineno == 0 and incr_pos == 0:
                    co_lnotab.append(0)
                    co_lnotab.append(0)
                else:
                    while incr_pos > 255:
                        co_lnotab.append(255)
                        co_lnotab.append(0)
                        incr_pos -= 255
                    while incr_lineno > 255:
                        co_lnotab.append(incr_pos)
                        co_lnotab.append(255)
                        incr_pos = 0
                        incr_lineno -= 255
                    if incr_pos or incr_lineno:
                        co_lnotab.append(incr_pos)
                        co_lnotab.append(incr_lineno)
            elif arg is not None:
                op = opcode.opmap[op]
                if op in opcode.hasjabs or op in opcode.hasjrel:
                    jumps.append((len(co_code), arg))
                    arg = 0
                if arg > 0xffff:
                    co_code.extend((opcode.EXTENDED_ARG,
                        (arg >> 16) & 0xff, (arg >> 24) & 0xff))
                co_code.extend((op,
                    arg & 0xff, (arg >> 8) & 0xff))
            else:
                co_code.append(opcode.opmap[op])

        for pos, label in jumps:
            jump = label_pos[label]
            if co_code[pos] in opcode.hasjrel:
                jump -= pos + 3
            assert jump <= 0xffff
            co_code[pos + 1] = jump & 0xff
            co_code[pos + 2] = (jump >> 8) & 0xff

        return types.CodeType(code_obj.co_argcount, code_obj.co_nlocals,
            code_obj.co_stacksize, code_obj.co_flags, co_code.tostring(),
            tuple(self.consts), tuple(self.names), tuple(self.varnames),
            code_obj.co_filename, code_obj.co_name, code_obj.co_firstlineno,
            co_lnotab.tostring(), code_obj.co_freevars, code_obj.co_cellvars)

    def const_idx(self, val):
        try:
            return self.consts.index(val)
        except ValueError:
            self.consts.append(val)
            return len(self.consts) - 1

def tail_call(func):
    code = Code.from_code(func.func_code)
    func_name = func.__name__
    if func_name in code.varnames:
        raise SyntaxError('"%s" was found as a local variable in the function' %
            func_name)
    try:
        name_idx = code.names.index(func_name)
    except IndexError:
        raise SyntaxError('"%s" not found in function\'s global names' %
            func_name)
    last_idx = 0
    func_start = Label()
    code.code.insert(0, ('MARK_LABEL', func_start))
    while True:
        try:
            lglobal_idx = code.code.index(('LOAD_GLOBAL', name_idx), last_idx)
        except ValueError:
            break

        if code.code[lglobal_idx - 1][0] != 'MARK_LINENO':
            last_idx = lglobal_idx + 1
            continue

        try:
            return_idx = code.code.index(('RETURN_VALUE', None), lglobal_idx)
        except ValueError:
            raise SyntaxError('"return" not found in function after "%s"' %
                func_name)

        if (return_idx != len(code.code) - 1
                and code.code[return_idx + 1][0] != 'MARK_LINENO'):
            last_idx = return_idx + 1
            continue

        if code.code[return_idx - 1][0] in ('CALL_FUNCTION_VAR',
                'CALL_FUNCTION_KW', 'CALL_FUNCTION_VAR_KW'):
            raise SyntaxError('calling with *a and/or **kw is unsupported')

        if code.code[return_idx - 1][0] != 'CALL_FUNCTION':
            last_idx = return_idx + 1
            continue

        if code.code[return_idx - 1][1] & 0xff00:
            raise SyntaxError('calling with keyword arguments is unsupported')

        arg_names, _, _, defaults = inspect.getargspec(func)
        n_args = code.code[return_idx - 1][1]
        if defaults is None:
            defaults = ()
        if n_args + len(defaults) < len(arg_names):
            raise SyntaxError('not enough arguments provided')

        new_bytecode = []
        if n_args < len(arg_names):
            new_bytecode.extend(
                ('LOAD_CONST', code.const_idx(d))
                for d in defaults[n_args - len(arg_names):])
        new_bytecode.extend(
            ('STORE_FAST', code.varnames.index(arg))
            for arg in reversed(arg_names))
        new_bytecode.append(('JUMP_ABSOLUTE', func_start))
        code.code[return_idx - 1:return_idx + 1] = new_bytecode
        del code.code[lglobal_idx]

    func.func_code = code.to_code()
    return func

def factorial(n, acc=1):
    if n <= 0:
        return acc
    return factorial(n - 1, n * acc)

dis.dis(factorial)
factorial = tail_call(factorial)
print
dis.dis(factorial)

print factorial(10000)

eval in Erlang

Der Code vom erl_eval-Modul von Erlang/OTP, der fun-Ausdrücke implementiert (kann man in lib/stdlib/src/erl_eval.erl finden):

%% This is a really ugly hack!
F =
case length(element(3,hd(Cs))) of
    0 -> fun () -> eval_fun(Cs, [], En, Lf, Ef) end;
    1 -> fun (A) -> eval_fun(Cs, [A], En, Lf, Ef) end;
    2 -> fun (A,B) -> eval_fun(Cs, [A,B], En, Lf, Ef) end;
    3 -> fun (A,B,C) -> eval_fun(Cs, [A,B,C], En, Lf, Ef) end;
    4 -> fun (A,B,C,D) -> eval_fun(Cs, [A,B,C,D], En, Lf, Ef) end;
    5 -> fun (A,B,C,D,E) -> eval_fun(Cs, [A,B,C,D,E], En, Lf, Ef) end;
    6 -> fun (A,B,C,D,E,F) -> eval_fun(Cs, [A,B,C,D,E,F], En, Lf, Ef) end;
    7 -> fun (A,B,C,D,E,F,G) ->
       eval_fun(Cs, [A,B,C,D,E,F,G], En, Lf, Ef) end;
    8 -> fun (A,B,C,D,E,F,G,H) ->
       eval_fun(Cs, [A,B,C,D,E,F,G,H], En, Lf, Ef) end;
    9 -> fun (A,B,C,D,E,F,G,H,I) ->
       eval_fun(Cs, [A,B,C,D,E,F,G,H,I], En, Lf, Ef) end;
    10 -> fun (A,B,C,D,E,F,G,H,I,J) ->
       eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J], En, Lf, Ef) end;
    11 -> fun (A,B,C,D,E,F,G,H,I,J,K) ->
       eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K], En, Lf, Ef) end;
    12 -> fun (A,B,C,D,E,F,G,H,I,J,K,L) ->
       eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L], En, Lf, Ef) end;
    13 -> fun (A,B,C,D,E,F,G,H,I,J,K,L,M) ->
       eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M], En, Lf, Ef) end;
    14 -> fun (A,B,C,D,E,F,G,H,I,J,K,L,M,N) ->
       eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N], En, Lf, Ef) end;
    15 -> fun (A,B,C,D,E,F,G,H,I,J,K,L,M,N,O) ->
       eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O], En, Lf, Ef) end;
    16 -> fun (A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P) ->
       eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P], En, Lf, Ef) end;
    17 -> fun (A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q) ->
       eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q], En, Lf, Ef) end;
    18 -> fun (A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R) ->
       eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R], En, Lf, Ef) end;
    19 -> fun (A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S) ->
       eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S],
                En, Lf, Ef) end;
    20 -> fun (A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T) ->
       eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T],
                En, Lf, Ef) end;
    _Other ->
        erlang:raise(error, {'argument_limit',{'fun',Line,Cs}},
                     stacktrace())

Rubber Duck method of debugging

  1. Beg, borrow, steal, buy, fabricate or otherwise obtain a rubber duck (bathtub variety)

  2. Place rubber duck on desk and inform it you are just going to go over some code with it, if that's all right.

  3. Explain to the duck what you code is supposed to do, and then go into detail and explain things line by line

  4. At some point you will tell the duck what you are doing next and then realise that that is not in fact what you are actually doing. The duck will sit there serenely, happy in the knowledge that it has helped you on your way.

Works every time. Actually, if you don't have a rubber duck you could at a pinch ask a fellow programmer or engineer to sit in.

Quelle.