Design by Contract funktioniert nicht

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Clython
User
Beiträge: 151
Registriert: Samstag 21. August 2004, 13:58
Wohnort: Schweiz, BE-2500

Freitag 21. Juli 2006, 12:24

Hallo Leute

ich bin gerade daran ein grösseres Programm zu schreiben und möchte dafür den Design by Contract Ansatz benutzen. Ich habe dazu von http://www.nongnu.org/pydbc/ eines der Module runtergeladen, die den Approach unter Python implementieren.

Der Testcode, der zu dem Projekt geliefert wird, sieht so aus:

Code: Alles auswählen

# The contents of this file may be used and copied without restriction
# Copyright (c) Daniel Arbuckle, 2002

import os

# if os.environ['PY_DBC'] is nonexistant or empty, no contract 
# checking will be performed. The usual way to set this would be
# from the command shell as an environment variable.
# if contract checking is not enabled, it imposes NO overhead
# during the execution of your program
os.environ['PY_DBC'] = 'true' 
import dbc 

__metaclass__ = dbc.DBC
                            
class Foo:
    # You can also turn on DBC for a specific class by specifying the __metaclass__ attribute:
    # __metaclass__ = dbc.DBC
    
    # Postcondition on a special name
    def __init____post(self, ret):
	assert hasattr(self, 'a')
	
    def __init__(self):
	self.a = 1
    
    # Class invariant, checked each time a member is called or an attribute accessed
    def __invar(self): 
	assert self.a
    
    # Precondition for function foo, checked just before foo
    # Preconditions are passed the same arguments as the called function (including varargs and keyword args)
    def foo__pre(self, b): 
	# If a condition fails, it should raise an exception of some sort. assert() is a good way to do that
	assert not isinstance(b, str) 
	
    def foo(self, b):
	self.a = b
    
    # Postcondition for foo, checked just after foo
    # Postconditions are passed the return value of the call
    def foo__post(self, ret): 
	assert self.a < 5

f = Foo()             # calls __init__, then calls __invar
f.foo(2)              # calls foo__pre, foo, foo__post and __invar
# f.foo('bar')        # would trigger the assert in foo__pre
# f.foo(10)           # would trigger the assert in foo__post
# f.foo(0)            # would trigger the assert in __invar

f.a = 3               # assigns the value, then checks __invar
f.a = 7               # __invar doesn't check the cap on a. be careful to put your obligations in the right place
# f.a = 0             # would trigger the assert in __invar
Mein Code sieht so aus:

Code: Alles auswählen

#! /usr/bin/python
# -*- coding: utf8

import pexpect, os, string, re, codecs, dbc, os.path
from BeautifulSoup import BeautifulSoup

class CANXML:
    """This class provides all the functionality to read, write and extract information from CAN projects XML-Files."""
    
    def __init__(self, STDENC=u"utf8", AMLENC=None, IMLENC=None, CMLENC=None):
        self.stdenc = STDENC
        if not AMLENC:
            self.amlenc=self.stdenc
        else:
            self.amlenc=AMLENC
        if not IMLENC:
            self.imlenc=self.stdenc
        else:
            self.imlenc=IMLENC
        if not CMLENC:
            self.cmlenc=self.stdenc
        else:
            self.cmlenc=CMLENC
        self.amltags = (u"zeitung", u"autor", u"datum", u"rubrik", u"seite", u"titel", u"text")
    def __init____post(self, ret):
        print "__init__post"
        assert self.stdenc and isinstance(self.stdenc, unicode)
        assert self.amlenc and isinstance(self.amlenc, unicode)
        assert self.imlenc and isinstance(self.imlenc, unicode)
        assert self.cmlenc and isinstance(self.cmlenc, unicode)
    def __invar(self):
        assert self.stdenc and isinstance(self.stdenc, unicode)
        assert self.amlenc and isinstance(self.amlenc, unicode)
        assert self.imlenc and isinstance(self.imlenc, unicode)
        assert self.cmlenc and isinstance(self.cmlenc, unicode)
        assert self.amltags and isinstance(self.amltags, (tuple. list))
    def loadAML__pre(self, file):
        assert isinstance(file, unicode)
    def loadAML(self, file):
        f = codecs.open(file, "r", self.amlenc)
        self.aml = f.read()
        f.close()
        if isinstance(self.aml, unicode):
            pass
        else:
            f = codecs.open(file, "r", self.stdenc)
            self.aml = f.read()
            f.close()
            if isistance(self.aml, unicode):
                pass
            else:
                raise "EncodingError: Set proper encoding for AML files!"
        return self.aml
    def loadAML__post(self, ret):
        assert isinstance(self.aml, unicode)
    def AML2dict__pre(self, aml):
        # Implement dtd-validation here!
        assert isinstance(aml, unicode)
    def AML2dict(self, xml):
        delpatterns = (u"<dl>.*?</dl>", u"<br />", u"<sm>.*?</sm>", u"<nt>.*?</nt>", u"<zt>.*?</zt>", u"<ut>", u"<ld>", u"</ld>")
        patterns = u"<(.+?)>.*?</"
        soup = BeautifulSoup(xml)
        self.dictionary = {}
        for tag in self.amltags:
            if not tag == "text":
                thing = unicode(soup(tag)[0])
                remover = "</?%s>" % tag
                thing = re.sub(remover, "", thing)
            else:
                thing = unicode(soup(tag)[0])
                remover = "</?%s>" % tag
                thing = re.sub(remover, "", thing)
##                for d in delpatterns:
##                    thing = re.sub(d, u" ", thing)
##                thing = re.sub(u"</ut>", u" . ", thing)
            self.dictionary.update( { tag : thing.strip() } )
        return self.dictionary
    def AML2dict__post(self, ret):
        assert isinstance(self.dictionary, dict)
        for key in self.dictionary:
            assert isinstance(self.dictionary[key], unicode)
    
    def dict2AML__pre(self, dic, file, OUTENC=None): 
        print "doing dict2AML_pre"        
        assert isinstance(dic, dict)
        assert isinstance(file, unicode)
        assert isinstance(OUTENC, (unicode, None.__class__))
        for key in self.amltags:
            assert dic.has_key(key)
    def dict2AML(self, dic, file, OUTENC=None):
        xmlcode = """<?xml version="1.0" encoding="%(enc)s" standalone="yes"?>
<artikel>
    <zeitung>
        %(zeitung)s
    </zeitung>
    <autor>
        %(autor)s
    </autor>
    <datum>
        %(datum)s
    </datum>
    <rubrik>
        %(rubrik)s
    </rubrik>
    <seite>
        %(seite)s
    </seite>
    <titel>
        %(titel)s
    </titel>
    <text>
        %(text)s
    </text>
</artikel>"""
        self.amlfile = file
        if not OUTENC:
            enc = self.stdenc
        else:
            enc = OUTENC
        dic["enc"] = enc
        try:
            f = codecs.open(self.amlfile, "w", enc)
            code = xmlcode % dic
            f.write(code)
            f.close()
        except "IOError":
            raise "IOError: Could not write file"
        except:
            raise "dict2amlError: Something went wrong while writing the xml code"
    def dict2aml__post(self, ret):
        os.path.exists(self.amlfile)
        # Implement some check for correct format here
if __name__ == "__main__":
    os.environ['PY_DBC'] = 'true'
##    os.system("echo $PY_DBC")
    __metaclass__ = dbc.DBC
    canxml = CANXML(AMLENC=u"latin_1")
    stuff = canxml.loadAML(u"/home/william/Coding/can/Input/test/nzz_2003-08-18_0002772.xml")
    dictionary = canxml.AML2dict(stuff)
    canxml.dict2AML(1, "test.xml", "latin_1")
    print dictionary
Eigentlich sollte ja der assert in dict2AML die Tatsache auffangen, dass ein Integer übergeben wird und nicht ein Dictionary, aber irgendwie funktioniert das ganze Modul in meinem Code nicht. Was habe ich übersehen (das ganze wurde mehr oder weniger 1 zu 1 vom Beispiel übernommen...)
Leonidas
Administrator
Beiträge: 16024
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Freitag 21. Juli 2006, 12:31

Funktioniert das Beispiel bei dir?

Für Preconditions und Postconditions kannst du ja auch andere Dinge nutzen, zum Beispiel Dekoratoren oder PJEs PyDispatch (Beispiel).
My god, it's full of CARs! | Leonidasvoice vs Modvoice
Clython
User
Beiträge: 151
Registriert: Samstag 21. August 2004, 13:58
Wohnort: Schweiz, BE-2500

Freitag 21. Juli 2006, 12:37

Das ist ja das verwirrende, das Beispiel funktioniert tadellos :?
Leonidas
Administrator
Beiträge: 16024
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Freitag 21. Juli 2006, 12:55

Du weißt schon, dass das ziemlich unpythonic ist, was du da machst?

Und wenn du es einfach mal mit einem kleinerem Programm probierst und es stufenweise größer machst?
My god, it's full of CARs! | Leonidasvoice vs Modvoice
Clython
User
Beiträge: 151
Registriert: Samstag 21. August 2004, 13:58
Wohnort: Schweiz, BE-2500

Freitag 21. Juli 2006, 13:01

Was sollte daran unpythonic sein? Ausserdem finde ich diese Art der Implementation schlauer, als das was als PEP kursiert, wo die Tests in den Kommentar geschrieben werden

http://en.wikipedia.org/wiki/Design_by_contract#Python
Leonidas
Administrator
Beiträge: 16024
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Freitag 21. Juli 2006, 13:26

Clython hat geschrieben:Was sollte daran unpythonic sein?
Es macht Duck Typing sehr schwer bis hin zu unmöglich. Du implementierst genau das, für was Guido mit seiner "Optional Static Typing"-Idee viel Kritik kassiert hat.
My god, it's full of CARs! | Leonidasvoice vs Modvoice
BlackJack

Freitag 21. Juli 2006, 13:30

Es ist unpythonic auf bestimmte Typen zu prüfen. Warum darf es zum Beispiel nur ein `dict` sein bei `dict2AML`? Du schliesst damit jedes andere Objekt aus, das sich zwar wie ein Dictionary verhält, aber nicht von der Klasse abgeleitet ist. Das gleiche gilt z.B. für `file` Objekte. Wenn irgendwo ein `file` übergeben werden kann, dann erwarte ich das man auch ein StringIO Objekt stattdessen nehmen kann. Das geht nicht, wenn explizit auf `file` getestet wird.

Schon mal versucht die `__metaclass__` Zuweisung oben in das Modul zu schreiben? Ich denke die sollte gebunden sein bevor die ``class`` Anweisung ausgeführt wird.

Und etwas das mir ins Auge gesprungen ist (Aua) ;-)

Code: Alles auswählen

self.dictionary.update( { tag : thing.strip() } )
# =>
self.dictionary[tag] = thing.strip()
Clython
User
Beiträge: 151
Registriert: Samstag 21. August 2004, 13:58
Wohnort: Schweiz, BE-2500

Freitag 21. Juli 2006, 13:33

Ich seh den Bezug nicht ganz. Alles was ich damit machen will, ist sichergehen, dass die richtige Art von Objekt an eine Methode geschickt wird. Das hat mit Guido's Vorschlag nur am Rande zu tun, da ich den Typ nicht fixiere, sondern nur überprüfe (halt Design by Contract). Das ist überhaupt nicht unpythonic!
Leonidas
Administrator
Beiträge: 16024
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Freitag 21. Juli 2006, 13:45

Clython hat geschrieben:Ich seh den Bezug nicht ganz. Alles was ich damit machen will, ist sichergehen, dass die richtige Art von Objekt an eine Methode geschickt wird.
Nein, du gehst damit sicher, dass genau der Typ daran übergeben wird, den du dir vorstellst und kein anderer, der aber ebenso richtig sein kann.

Zum Beispiel willst du testen das ein Objekt ein dict() ist. Warum kann man dort kein eigenes Objekt angeben, welches sich wie ein Dict verhält, aber noch modifiziert ist? Wenn es sich doch genauso verhält wie ein dict ("quacks like a duck") gibt es doch keinen Grund das auszuschliessen?
My god, it's full of CARs! | Leonidasvoice vs Modvoice
Clython
User
Beiträge: 151
Registriert: Samstag 21. August 2004, 13:58
Wohnort: Schweiz, BE-2500

Freitag 21. Juli 2006, 13:53

Okay, das macht Sinn. Was aber nur heisst, dass meine Tests unpythonic sind. Dann muss ich halt Testen, ob das Objekt eine __getitem__-Methode hat. Der scheiss funktioniert trotzdem immer noch nicht. Grrrrrrrrrrr :x
Antworten