2008/09/05/19.08

written by Martin Häcker on

Python != Funktionales Programmieren

--- zumindest scheint es so.

Das hier zum Beispiel:

#!/usr/bin/env python
# encoding: utf-8

def counter_generator():
    current_value = 0
    def counter():
        current_value += 1
        return current_value
    return counter

import unittest
class ClosureTest(unittest.TestCase):
    def test_counter(self):
        self.assertEquals(1, counter_generator()())

if __name__ == '__main__':
    unittest.main()

Wenn man das ausführt dann kriegt man den wenig aussagekräftigen Fehler UnboundLocalError: local variable 'current_value' referenced before assignment. Oder mit anderen Worten: "Wiebidde?"

Kommentiert man current_value += 1 aus, funktioniert das ganze (ok, bis auf den dann fehlschlagenden Testcase).

Immerhin kommt man dem Problem dann auf die Spur (ich und Felix haben das leider erst herausgefunden nachdem wir auf #python nachgefragt haben...) Python kann nämlich seine Scopes nicht vernünftig. Das heißt, man kriegt keinen schreibenden Zugriff auf die Variablen eine Funktion weiter draußen - nur lesend. Nur dass die Fehlermeldung natürlich total mist ist.

Grah.

Interessanterweise geht diese Lösung auch nicht

#!/usr/bin/env python
# encoding: utf-8

def counter_generator():
    workaround = object()
    workaround.current_value = 0
    def counter():
        workaround.current_value += 1
        return workaround.current_value
    return counter

import unittest
class ClosureTest(unittest.TestCase):
    def test_counter(self):
        self.assertEquals(1, counter_generator()())

if __name__ == '__main__':
    unittest.main()

Für hinweise wieso bin ich dankbar.

Vielleicht kriegt Python dass ja irgendwann mal hin. In der Zwischenzeit hilft dieser Workaround:

#!/usr/bin/env python
# encoding: utf-8

class AttributableDict(dict):
    def __getattr__(self, attribute_name):
        return self[attribute_name]
    def __setattr__(self, attribute_name, value):
        self[attribute_name] = value

def counter_generator():
    workaround = AttributableDict()
    workaround.current_value = 0
    def counter():
        workaround.current_value += 1
        return workaround.current_value
    return counter

import unittest
class ClosureTest(unittest.TestCase):
    def test_counter(self):
        self.assertEquals(1, counter_generator()())

if __name__ == '__main__':
    unittest.main()