diff -r ace52be8da89 Doc/library/os.rst --- a/Doc/library/os.rst Sat Mar 23 16:06:06 2013 -0700 +++ b/Doc/library/os.rst Sun Mar 24 15:51:29 2013 -0700 @@ -1497,7 +1497,10 @@ in all other circumstances, they will be of type ``str``. This function can also support :ref:`specifying a file descriptor - `; the file descriptor must refer to a directory. + `; the file descriptor must refer to a directory. The + file descriptor will remain open and be rewound on return. + + For an iterable that does not build up a list see :func:`scandir`. .. note:: To encode ``str`` filenames to ``bytes``, use :func:`~os.fsencode`. @@ -1784,6 +1787,35 @@ The *dir_fd* parameter. +.. function:: scandir(path='.') + + Return an iterator over the names of the entries in the directory given by + *path*. Iteration is in arbitrary order and does not include the special + entries ``'.'`` and ``'..'`` even if they are present in the directory. + + *path* may be either of type ``str`` or of type ``bytes``. If *path* + is of type ``bytes``, the filenames returned will also be of type ``bytes``; + in all other circumstances they will be of type ``str``. + + This function can also support :ref:`specifying a file descriptor + `; the file descriptor must refer to a directory. The + file descriptor will remain open and be rewound after iteration ends. + + Prefer this method to :func:`listdir` to avoid loading the entire + directory contents into memory at once. + + If there is a problem accessing path an OSError will be raised + either at iterator construction time or during iteration. Callers + should be prepared for both. + + .. note:: + To encode ``str`` filenames to ``bytes``, use :func:`~os.fsencode`. + + Availability: Unix, Windows. + + .. versionadded: 3.4 + + .. function:: stat(path, *, dir_fd=None, follow_symlinks=True) Perform the equivalent of a :c:func:`stat` system call on the given path. diff -r ace52be8da89 Lib/os.py --- a/Lib/os.py Sat Mar 23 16:06:06 2013 -0700 +++ b/Lib/os.py Sun Mar 24 15:51:29 2013 -0700 @@ -146,6 +146,7 @@ _add("HAVE_FCHMOD", "chmod") _add("HAVE_FCHOWN", "chown") _add("HAVE_FDOPENDIR", "listdir") + _add("HAVE_FDOPENDIR", "scandir") _add("HAVE_FEXECVE", "execve") _set.add(stat) # fstat always works _add("HAVE_FTRUNCATE", "truncate") diff -r ace52be8da89 Lib/test/test_os.py --- a/Lib/test/test_os.py Sat Mar 23 16:06:06 2013 -0700 +++ b/Lib/test/test_os.py Sun Mar 24 15:51:29 2013 -0700 @@ -798,8 +798,9 @@ os.fstat(rootfd) # redundant check os.stat(rootfd) - # check that listdir() returns consistent information - self.assertEqual(set(os.listdir(rootfd)), set(dirs) | set(files)) + # check that listdir() & scandir() return consistent information + self.assertEqual(set(os.listdir(rootfd)), set(dirs)|set(files)) + self.assertEqual(set(os.scandir(rootfd)), set(dirs)|set(files)) def test_fd_leak(self): # Since we're opening a lot of FDs, we must be careful to avoid leaks: @@ -1328,18 +1329,32 @@ def tearDown(self): shutil.rmtree(self.dir) - def test_listdir(self): + def _test_scandir_or_listdir(self, dir_func): expected = self.unicodefn - found = set(os.listdir(self.dir)) + found = set(dir_func(self.dir)) self.assertEqual(found, expected) # test listdir without arguments current_directory = os.getcwd() try: os.chdir(os.sep) - self.assertEqual(set(os.listdir()), set(os.listdir(os.sep))) + self.assertEqual(set(dir_func()), set(dir_func(os.sep))) finally: os.chdir(current_directory) + def test_listdir(self): + self._test_scandir_or_listdir(os.listdir) + + def test_scandir(self): + self._test_scandir_or_listdir(os.scandir) + + def test_scandir_no_reuse(self): + scandir_iter = os.scandir(self.dir) + empty = [x for x in scandir_iter if not x] + self.assertEqual([], empty) + # A scandir iterator cannot be reused. + with self.assertRaisesRegex(RuntimeError, '.*already started.*'): + list(scandir_iter) + def test_open(self): for fn in self.unicodefn: f = open(os.path.join(self.dir, fn), 'rb') @@ -1575,7 +1590,7 @@ os.chdir(level2) link = os.path.join(level2, "link") os.symlink(os.path.relpath(file1), "link") - self.assertIn("link", os.listdir(os.getcwd())) + self.assertIn("link", os.scandir(os.getcwd())) # Check os.stat calls from the same dir as the link self.assertEqual(os.stat(file1), os.stat("link")) @@ -2047,6 +2062,7 @@ (os.open, filename, os.O_RDONLY), (os.rename, filename, filename), (os.rmdir, filename), + (os.scandir, filename), (os.startfile, filename), (os.stat, filename), (os.unlink, filename), @@ -2107,6 +2123,14 @@ self.assertEqual(expected, actual) +class ReprTests(unittest.TestCase): + def test_scandir_repr(self): + self.assertEqual('os.scandir()', repr(os.scandir())) + self.assertIn('os.scandir(b', repr(os.scandir(b'/'))) + self.assertIn('os.scandir(', repr(os.scandir('/'))) + self.assertIn('/', repr(os.scandir('/'))) + + class OSErrorTests(unittest.TestCase): def setUp(self): class Str(str): @@ -2139,6 +2163,17 @@ (self.filenames, os.stat,), (self.filenames, os.unlink,), ] + + # Explicitly test os.scandir() regardless of if os.listdir() + # is implemented using it or not. + def iter_over_scandir(path=None): + if path is None: + return list(os.scandir()) + # scandir may raise its OSError on first iteration rather + # than on iterator construction. Forcing iteration accepts + # either behavior. + return list(os.scandir(path)) + if sys.platform == "win32": funcs.extend(( (self.bytes_filenames, os.rename, b"dst"), @@ -2155,10 +2190,12 @@ # fails with ERROR_FILE_NOT_FOUND (2), instead of # ERROR_PATH_NOT_FOUND (3). (self.unicode_filenames, os.listdir,), + (self.unicode_filenames, iter_over_scandir,), )) else: funcs.extend(( (self.filenames, os.listdir,), + (self.filenames, iter_over_scandir,), (self.filenames, os.rename, "dst"), (self.filenames, os.replace, "dst"), )) @@ -2200,7 +2237,9 @@ try: func(name, *func_args) except OSError as err: - self.assertIs(err.filename, name) + self.assertIs(err.filename, name, + msg="{} did not report " + "the correct filename".format(func)) else: self.fail("No exception thrown by {}".format(func)) @@ -2232,6 +2271,7 @@ ExtendedAttributeTests, Win32DeprecatedBytesAPI, TermsizeTests, + ReprTests, OSErrorTests, RemoveDirsTests, ) diff -r ace52be8da89 Modules/posixmodule.c --- a/Modules/posixmodule.c Sat Mar 23 16:06:06 2013 -0700 +++ b/Modules/posixmodule.c Sun Mar 24 15:51:29 2013 -0700 @@ -661,10 +661,7 @@ static void path_cleanup(path_t *path) { - if (path->cleanup) { - Py_DECREF(path->cleanup); - path->cleanup = NULL; - } + Py_CLEAR(path->cleanup); } static int @@ -3235,6 +3232,19 @@ #endif +#if defined(MS_WINDOWS) && !defined(HAVE_OPENDIR) +#define _WINDOWS_WITHOUT_OPENDIR +#endif + +/* TODO(gps): Replace this C listdir implementation with the following + * Python code in Lib/os.py once we are confident in os.scandir: + * + * def listdir(path=None): + * """docstring from below""" + * if path is None: + * return list(os.scandir()) + * return list(os.scandir(path)) + */ PyDoc_STRVAR(posix_listdir__doc__, "listdir(path='.') -> list_of_filenames\n\n\ @@ -3249,20 +3259,17 @@ the file descriptor must refer to a directory.\n\ If this functionality is unavailable, using it raises NotImplementedError."); -#if defined(MS_WINDOWS) && !defined(HAVE_OPENDIR) +#ifdef _WINDOWS_WITHOUT_OPENDIR static PyObject * _listdir_windows_no_opendir(path_t *path, PyObject *list) { - static char *keywords[] = {"path", NULL}; PyObject *v; HANDLE hFindFile = INVALID_HANDLE_VALUE; BOOL result; WIN32_FIND_DATA FileData; char namebuf[MAX_PATH+5]; /* Overallocate for \\*.*\0 */ - char *bufptr = namebuf; /* only claim to have space for MAX_PATH */ Py_ssize_t len = sizeof(namebuf)-5; - PyObject *po = NULL; wchar_t *wnamebuf = NULL; if (!path->narrow) { @@ -3404,14 +3411,14 @@ return list; } /* end of _listdir_windows_no_opendir */ -#else /* thus POSIX, ie: not (MS_WINDOWS and not HAVE_OPENDIR) */ - -static PyObject * -_posix_listdir(path_t *path, PyObject *list) +#else /* POSIX */ + +static PyObject * +_posix_listdir(path_t *path) { int fd = -1; - PyObject *v; + PyObject *v, *list = NULL; DIR *dirp = NULL; struct dirent *ep; int return_str; /* if false, return bytes */ @@ -3424,6 +3431,11 @@ fd = dup(path->fd); Py_END_ALLOW_THREADS + /* TODO: This should set CLOEXEC on fd while duping using dup3 when + * available at compile time in libc and runtime in the kernel. + * fcntl F_DUPFD may also be an option? See issue17036. + * EINTR and EBUSY handling will also then be required. */ + if (fd == -1) { list = posix_error(); goto exit; @@ -3498,8 +3510,10 @@ exit: if (dirp != NULL) { Py_BEGIN_ALLOW_THREADS - if (fd > -1) + if (fd > -1) { + /* Leave the file we dup()ed from at the starting position. */ rewinddir(dirp); + } closedir(dirp); Py_END_ALLOW_THREADS } @@ -3511,9 +3525,8 @@ static PyObject * posix_listdir(PyObject *self, PyObject *args, PyObject *kwargs) { + static char *keywords[] = {"path", NULL}; path_t path; - PyObject *list = NULL; - static char *keywords[] = {"path", NULL}; PyObject *return_value; memset(&path, 0, sizeof(path)); @@ -3529,15 +3542,513 @@ return NULL; } -#if defined(MS_WINDOWS) && !defined(HAVE_OPENDIR) - return_value = _listdir_windows_no_opendir(&path, list); -#else - return_value = _posix_listdir(&path, list); +#ifdef _WINDOWS_WITHOUT_OPENDIR + return_value = _listdir_windows_no_opendir(&path); +#else + return_value = _posix_listdir(&path); #endif path_cleanup(&path); return return_value; } + +PyDoc_STRVAR(posix_scandir__doc__, +"scandir(path='.') -> iterable of filenames\n\n\ +Returns an iterator over the names of files in the directory.\n\ +Iteration is in arbitrary order. It will not include the special\n\ +entries '.' and '..' even if they are present in the directory.\n\ +\n\ +path can be specified as either str or bytes. If path is bytes,\n\ + the filenames returned will also be bytes; in all other circumstances\n\ + the filenames returned will be str.\n\ +On some platforms, path may also be specified as an open file descriptor;\n\ + the file descriptor must refer to a directory.\n\ + If this functionality is unavailable, using it raises NotImplementedError."); + + +typedef struct { + PyObject_HEAD + path_t path; + int started; /* bool: Has iteration started? */ +#ifdef _WINDOWS_WITHOUT_OPENDIR + HANDLE h_find_file; + int last_error; + int first_file; /* bool: Was FindFirstFile just called? */ + union { + WIN32_FIND_DATA narrow; /* Used when path.narrow; returns bytes.*/ + WIN32_FIND_DATAW wide; /* Used when !path.narrow; return str.*/ + } file_data; + union { + char *narrow; + wchar_t *wide; + } namebuf; +#else /* POSIX */ + DIR *dirp; + char *name; /* The pathname if we have one. */ + int dir_fd; /* The dup'ed fd being used by fdopendir or -1. */ + int return_str; /* bool: Return a str or bytes? */ +#endif +} scandir_object; + + +PyTypeObject PyScanDir_Type; + +/* Forward declare these as they are used by iter and iternext. */ +#ifdef _WINDOWS_WITHOUT_OPENDIR +static void windows_scandir_cleanup(scandir_object *scan); +#else +static void posix_scandir_cleanup(scandir_object *scan); +#endif + + +static PyObject * +scandir_repr(scandir_object *scan) +{ + if (scan->path.object == NULL) { + return PyUnicode_FromString("os.scandir()"); + } + return PyUnicode_FromFormat("os.scandir(%R)", scan->path.object); +} + + +static scandir_object * +scandir_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + static char *keywords[] = {"path", NULL}; + scandir_object *scan; + path_t path; + + memset(&path, 0, sizeof(path)); + path.function_name = "scandir"; + path.nullable = 1; +#ifdef HAVE_FDOPENDIR + path.allow_fd = 1; + path.fd = -1; +#endif + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O&:scandir", keywords, + path_converter, &path)) { + return NULL; + } + + scan = PyObject_New(scandir_object, type); + if (scan == NULL) { + return NULL; + } + scan->path = path; + scan->started = 0; + +#ifdef _WINDOWS_WITHOUT_OPENDIR + scan->h_find_file = INVALID_HANDLE_VALUE; + scan->last_error = ERROR_SUCCESS; + scan->first_file = 1; + /* The +5's are so we can append "\\*.*\0" */ + if (path.narrow) { + Py_ssize_t len; + char *namebuf; + scan->namebuf.narrow = PyMem_Malloc(MAX_PATH+5); + namebuf = scan->namebuf.narrow; + if (namebuf == NULL) { + PyObject_Del(scan); + PyErr_NoMemory(); + return NULL; + } + len = path->length; + strcpy(namebuf, path->narrow); + if (len > 0) { + char ch = namebuf[len-1]; + if (ch != SEP && ch != ALTSEP && ch != ':') + namebuf[len++] = '/'; + strcpy(namebuf + len, "*.*"); + } + } else { + wchar_t *po_wchars; + wchar_t *wnamebuf; + Py_ssize_t len; + + if (!path->wide) { /* Default arg: "." */ + po_wchars = L"."; + len = 1; + } else { + po_wchars = path->wide; + len = wcslen(path->wide); + } + scan->namebuf.wide = PyMem_Malloc((len+5)*sizeof(wchar_t)); + if (scan->namebuf.wide == NULL) { + PyObject_Del(scan); + PyErr_NoMemory(); + return NULL; + } + wnamebuf = scan->namebuf.wide; + wcscpy(wnamebuf, po_wchars); + if (len > 0) { /* Append "\\*.*" */ + wchar_t wch = wnamebuf[len-1]; + if (wch != L'/' && wch != L'\\' && wch != L':') + wnamebuf[len++] = L'\\'; + wcscpy(wnamebuf + len, L"*.*"); + } + } +#else /* POSIX APIs */ + scan->dirp = NULL; + scan->dir_fd = -1; + if (path.fd == -1 && path.narrow) { + scan->name = path.narrow; + scan->return_str = !PyBytes_Check(path.object); + } else { + if (path.fd == -1) { + scan->name = "."; /* Default arg: "." */ + } else { + scan->name = NULL; + } + scan->return_str = 1; + } +#endif + + /* We're keeping a reference for the life of this scandir instance. */ + Py_XINCREF(scan->path.object); + return scan; +} + + +static PyObject * +posix_scandir(PyObject *self, PyObject *args, PyObject *kwargs) +{ + return (PyObject *)scandir_new(&PyScanDir_Type, args, kwargs); +} + + +#ifdef _WINDOWS_WITHOUT_OPENDIR +static scandir_object * +windows_scandir_iter(scandir_object *scan) +{ + if (scan->started) { + PyErr_SetString(PyExc_RuntimeError, + "Iteration over this instance already started."); + return NULL; + } + scan->started = 1; + if (!scan->path.narrow) { + Py_BEGIN_ALLOW_THREADS + scan->h_find_file = FindFirstFileW( + scan->namebuf.wide, &scan->file_data.wide); + Py_END_ALLOW_THREADS + } else { + Py_BEGIN_ALLOW_THREADS + scan->h_find_file = FindFirstFile( + scan->namebuf.narrow, &scan->file_data.narrow); + Py_END_ALLOW_THREADS + } + if (scan->h_find_file == INVALID_HANDLE_VALUE) { + scan->last_error = GetLastError(); + /* File not found means StopIteration on the first iternext. */ + if (scan->last_error != ERROR_FILE_NOT_FOUND) { + return path_error(&scan->path); + } + } + Py_INCREF((PyObject *)scan); + return scan; +} +#define scandir_iter windows_scandir_iter +#else /* POSIX */ + +static scandir_object * +posix_scandir_iter(scandir_object *scan) +{ + if (scan->started) { + PyErr_SetString(PyExc_RuntimeError, + "Iteration over this instance already started."); + return NULL; + } + scan->started = 1; +#ifdef HAVE_FDOPENDIR + if (scan->name == NULL) { + assert(scan->path.fd != -1); + /* closedir() closes the FD, so we duplicate it. */ + Py_BEGIN_ALLOW_THREADS + scan->dir_fd = dup(scan->path.fd); + + /* TODO: This should set CLOEXEC on fd while duping using dup3 when + * available at compile time in libc and runtime in the kernel. + * fcntl F_DUPFD may also be an option? See issue17036. + * EINTR and EBUSY handling will also then be required. */ + + if (scan->dir_fd == -1) { + Py_BLOCK_THREADS + posix_error(); + return NULL; + } + + scan->dirp = fdopendir(scan->dir_fd); + Py_END_ALLOW_THREADS + } + else +#endif + { + Py_BEGIN_ALLOW_THREADS + scan->dirp = opendir(scan->name); + Py_END_ALLOW_THREADS + } + + if (scan->dirp == NULL) { + path_error(&scan->path); + return NULL; + } + + Py_INCREF((PyObject *)scan); + return scan; +} +#define scandir_iter posix_scandir_iter +#endif + + +#ifdef _WINDOWS_WITHOUT_OPENDIR +static PyObject * +windows_scandir_iternext(scandir_object *scan) +{ + PyObject *name; + BOOL result; + + switch (scan->last_error) { + case ERROR_SUCCESS: + break; + case ERROR_FILE_NOT_FOUND: /* Empty directory from FindFirstFile. */ + case ERROR_NO_MORE_FILES: + goto stop_iteration; /* StopIteration */ + default: + SetLastError(scan->last_error); /* For path_error to use. */ + path_error(&scan->path); + goto stop_iteration; + } + + if (!scan->first_file) { +read_next_dir_entry: + Py_BEGIN_ALLOW_THREADS + if (scan->path.narrow) { + result = FindNextFile(scan->h_find_file, &scan->file_data.narrow); + } else { + result = FindNextFileW(scan->h_find_file, &scan->file_data.wide); + } + Py_END_ALLOW_THREADS + + if (!result) { + scan->last_error = GetLastError(); + /* FindNextFile sets error to ERROR_NO_MORE_FILES if + it got to the end of the directory. */ + if (scan->last_error != ERROR_NO_MORE_FILES) { + path_error(path); + } + goto stop_iteration; /* StopIteration or exception. */ + } + } else { + /* FindFirstFile was called in windows_scandir_iter. */ + scan->first_file = 0; + } + + if (scan->path.narrow) { + /* Skip over . and .. */ + if (strcmp(scan->file_data.narrow.cFileName, ".") != 0 && + strcmp(scan->file_data.narrow.cFileName, "..") != 0) { + name = PyBytes_FromString(scan->file_data.narrow.cFileName); + } else { + goto read_next_dir_entry; + } + } else { + /* Skip over . and .. */ + if (wcscmp(scan->file_data.wide.cFileName, L".") != 0 && + wcscmp(scan->file_data.wide.cFileName, L"..") != 0) { + name = PyUnicode_FromWideChar( + scan->file_data.wide.cFileName, + wcslen(scan->file_data.wide.cFileName)); + } else { + goto read_next_dir_entry; + } + } + if (name == NULL) { + goto stop_iteration; + } + + /* TODO: Support returning a namedtuple of WIN32_FIND_DATA? issueXXXXX */ +#if 0 + if (name != NULL && scan->return_tuple) { + ... windows has a lot of stat-like info in WIN32_FIND_DATA + return namedtuple; + } +#endif + + return name; + +stop_iteration: + windows_scandir_cleanup(scan); + return NULL; +} +#define scandir_iternext windows_scandir_iternext +#else /* POSIX */ + +static PyObject * +posix_scandir_iternext(scandir_object *scan) +{ + PyObject *name; + struct dirent *ep; + unsigned int name_len; + + errno = 0; + Py_BEGIN_ALLOW_THREADS +read_next_dir_entry: + ep = readdir(scan->dirp); + if (ep == NULL) { + Py_BLOCK_THREADS + if (errno != 0) { + path_error(&scan->path); + } + goto stop_iteration; + } + name_len = NAMLEN(ep); + if (name_len > 0 && ep->d_name[0] == '.' && + (name_len == 1 || + (ep->d_name[1] == '.' && name_len == 2))) { + /* Skip "." and ".." entries. */ + goto read_next_dir_entry; + } + Py_END_ALLOW_THREADS + + if (scan->return_str) { + name = PyUnicode_DecodeFSDefaultAndSize(ep->d_name, name_len); + } else { + name = PyBytes_FromStringAndSize(ep->d_name, name_len); + } + if (name == NULL) { + goto stop_iteration; + } + + /* TODO Support returning a namedtuple with dirent info? issueXXXXX */ +#if 0 + if (name != NULL && scan->return_tuple) { + unsiged long long d_ino = ep->d_ino; + unsigned char d_type = 0; + d_type = ep->d_type; /* TODO surround with ifdef re: dirent.d_type */ + return Py_BuildValue("KBN", d_ino, d_type, name); + } +#endif + + return name; + +stop_iteration: + posix_scandir_cleanup(scan); + return NULL; +} +#define scandir_iternext posix_scandir_iternext +#endif + + +#ifdef _WINDOWS_WITHOUT_OPENDIR +static void +windows_scandir_cleanup(scandir_object *scan) +{ + if (scan->h_find_file != INVALID_HANDLE_VALUE) { + HANDLE old_handle = scan->h_find_file; + scan->h_find_file = INVALID_HANDLE_VALUE; + Py_BEGIN_ALLOW_THREADS + FindClose(old_handle); + Py_END_ALLOW_THREADS + } + if (scan->path.narrow) { + if (scan->namebuf.narrow != NULL) { + PyMem_Free(scan->namebuf.narrow); + scan->namebuf.narrow = NULL; + } + } else { + if (scan->namebuf.wide != NULL) { + PyMem_Free(scan->namebuf.wide); + scan->namebuf.wide = NULL; + } + } +} + +#else + +static void +posix_scandir_cleanup(scandir_object *scan) +{ + if (scan->dirp != NULL) { + DIR *old_dirp = scan->dirp; + scan->dirp = NULL; + Py_BEGIN_ALLOW_THREADS + Py_BLOCK_THREADS + if (scan->dir_fd > -1) { + /* dir_fd gets closed by closedir. This will reset path.fd that + * this fd is a dup() of back to its starting position. */ + Py_UNBLOCK_THREADS + rewinddir(old_dirp); + } else { + Py_UNBLOCK_THREADS + } + closedir(old_dirp); + Py_END_ALLOW_THREADS + } +} +#endif + + +static void +scandir_dealloc(scandir_object *scan) +{ +#ifdef _WINDOWS_WITHOUT_OPENDIR + windows_scandir_cleanup(scan); +#else + posix_scandir_cleanup(scan); +#endif + Py_CLEAR(scan->path.object); + path_cleanup(&scan->path); + PyObject_Del(scan); +} + + +PyDoc_STRVAR(scandir_doc, + "scandir(path) - An iterator over the filenames in path."); + + +PyTypeObject PyScanDir_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "scandir", /* Name of this type */ + sizeof(scandir_object), /* Basic object size */ + 0, /* Item size for varobject */ + (destructor)scandir_dealloc, /* tp_dealloc */ + NULL, /* tp_print */ + NULL, /* tp_getattr */ + NULL, /* tp_setattr */ + NULL, /* tp_reserved */ + (reprfunc)scandir_repr, /* tp_repr */ + NULL, /* tp_as_number */ + NULL, /* tp_as_sequence */ + NULL, /* tp_as_mapping */ + NULL, /* tp_hash */ + NULL, /* tp_call */ + NULL, /* tp_str */ + NULL, /* tp_getattro */ + NULL, /* tp_setattro */ + NULL, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + scandir_doc, /* tp_doc */ + NULL, /* tp_traverse */ + NULL, /* tp_clear */ + NULL, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + (getiterfunc)scandir_iter, /* tp_iter */ + (iternextfunc)scandir_iternext, /* tp_iternext */ + NULL, /* tp_methods */ + NULL, /* tp_members */ + NULL, /* tp_getset */ + NULL, /* tp_base */ + NULL, /* tp_dict */ + NULL, /* tp_descr_get */ + NULL, /* tp_descr_set */ + 0, /* tp_dictoffset */ + NULL, /* tp_init */ + NULL, /* tp_alloc */ + (newfunc)scandir_new, /* tp_new */ +}; + + #ifdef MS_WINDOWS /* A helper function for abspath on win32 */ static PyObject * @@ -10386,6 +10897,9 @@ {"rmdir", (PyCFunction)posix_rmdir, METH_VARARGS | METH_KEYWORDS, posix_rmdir__doc__}, + {"scandir", (PyCFunction)posix_scandir, + METH_VARARGS | METH_KEYWORDS, + posix_scandir__doc__}, {"stat", (PyCFunction)posix_stat, METH_VARARGS | METH_KEYWORDS, posix_stat__doc__},