Seite 1 von 1

doctest und yield

Verfasst: Donnerstag 17. Mai 2007, 17:35
von TheGrudge
Hi,

ich habe gerade ein Problem mit doctests. Ich definiere eine Funktion, die einen Generator zurückliefern soll.

Code: Alles auswählen

import os

def scanfolder(dst):
    """scan the given folder, return a list of all files

    dst must be a valid folder
        >>> scanfolder('a_folder_that_doesn_t_exist')
        Traceback (most recent call last):
            ...
        IOError: the specified folder does not exist

    return a generator
        >>> g = scanfolder("/home")
        >>> print g
        <generator object ...>
    """
    if not os.path.isdir(dst):
        raise IOError, "the specified folder does not exist"

    for root, dirs, files in os.walk(dst):
        for file in files:
            yield os.path.join(root, file)


if __name__ == '__main__':
    import doctest
    doctest.testmod(optionflags=doctest.ELLIPSIS)
Nun schlägt der eine Test aber immer wieder fehl, obwohl eine Exception kommen müsste wird ein Generator geliefert.
Failed example:
scanfolder('a_folder_that_doesn_t_exist')
Expected:
Traceback (most recent call last):
...
IOError: the specified folder does not exist
Got:
<generator object at 0xb7a56f2c>
**********************************************************************
1 items had failures:
1 of 3 in __main__.scanfolder
***Test Failed*** 1 failures.
Mache ich was falsch oder kann man sowas mit doctests nicht richtig prüfen?

Verfasst: Donnerstag 17. Mai 2007, 18:34
von BlackJack
Es liegt nicht am Test sondern am Code. Vielleicht solltest Du Doctest einfach mal glauben:

Code: Alles auswählen

Python 2.4.4c1 (#2, Oct 11 2006, 21:51:02)
[GCC 4.1.2 20060928 (prerelease) (Ubuntu 4.1.1-13ubuntu5)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from test import *
>>> scanfolder('a_folder_that_doesn_t_exist')
<generator object at 0xb7d0712c>

Verfasst: Donnerstag 17. Mai 2007, 19:19
von birkenfeld
Vielleicht hilft dir das weiter:

Code: Alles auswählen

def g():
    1/0
    yield None

x = g()
print "Generator erstellt"
x.next()
print "next() aufgerufen"

Verfasst: Freitag 18. Mai 2007, 17:47
von TheGrudge
Nicht wirklich. Soll das heißen es wird immer ein Generator geliefert, egal was ich vorher mache?

Verfasst: Freitag 18. Mai 2007, 17:50
von EnTeQuAk
Nicht wirklich. Soll das heißen es wird immer ein Generator geliefert, egal was ich vorher mache?
Japp. `yield` liefert immer ein generatorobjekt zurück. Nur die Werte des Objektes können verschieden sein. Es kann imprinziep alles(?) sein. Von Exceptions über None, bis hin zu normalen Werten wie Listen und Strings.

Code: Alles auswählen

In [10]: def g():
    yield [1, 2, 3, 5, 6]
    yield 1
    yield (5, 'la')
    raise Error("Einfach mal nen Fehler?")
    yield "und weiter gehts"
   ....: 

In [16]: generator = g()

In [17]: generator.next()
Out[17]: [1, 2, 3, 5, 6]

In [18]: generator.next()
Out[18]: 1

In [19]: generator.next()
Out[19]: (5, 'la')

In [20]: generator.next()
---------------------------------------------------------------------------
<class '__main__.Error'>                  Traceback (most recent call last)

/home/ente/<ipython console> in <module>()

/home/ente/<ipython console> in g()

<class '__main__.Error'>: Einfach mal nen Fehler?
Jedoch gehts nach einem Fehler nicht weiter. d.H, an den code ``""und weiter gehts"`` kommst du nicht mehr ran.

MfG EnTeQuAk

€dit: Den Beispielcode verbessert ;)

Verfasst: Freitag 18. Mai 2007, 17:53
von birkenfeld
TheGrudge hat geschrieben:Nicht wirklich. Soll das heißen es wird immer ein Generator geliefert, egal was ich vorher mache?
Ganz richtig. Der Code im Generator wird erst bei ersten Aufruf von `next()` von Anfang an ausgeführt.

Verfasst: Freitag 18. Mai 2007, 17:57
von keppla
TheGrudge hat geschrieben:Nicht wirklich. Soll das heißen es wird immer ein Generator geliefert, egal was ich vorher mache?
jein, das soll heissen, dass der Generator geliefert wird, der Code aber erst ausgeführt wird, wenn du Elemente aus dem Generator holst, und auch nur so weit, wie Elemente rausgeholt werden.

Dein Generator wird die exception beim ersten "next()" werfen.

Wenn du schon bei der erstellung die exception raisen willst, könntest du eine Funktion schreiben, die deinen generator zurückgibt.

Code: Alles auswählen

def g(arg):
  if not check(arg):
    raise Exception
  else:
    return _g(arg)

def _g(arg):
   ...
   yield something


Verfasst: Samstag 19. Mai 2007, 10:23
von TheGrudge
OK, danke an alle, mal wieder was gelernt!!!

Verfasst: Samstag 19. Mai 2007, 12:12
von birkenfeld
keppla hat geschrieben:

Code: Alles auswählen

def g(arg):
  if not check(arg):
    raise Exception
  else:
    return _g(arg)

def _g(arg):
   ...
   yield something
Etwas hässlich. Dafür sind innere Funktionen hervorragend geeignet:

Code: Alles auswählen

def g(arg):
    if not check(arg):
        raise Exception
    else:
        def g(arg):
             yield ...
        return g(arg)
[/code]