gh-155358: Use named attributes with pwd and grp modules (#155362)

vstinner <[email protected]> Mon, 10 Aug 2026 14:30:52 -0400 (EDT)
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/f8cfa0cd593a0034628e36bd5b5e082af6541a78
commit: f8cfa0cd593a0034628e36bd5b5e082af6541a78
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-08-10T20:30:40+02:00
summary:

gh-155358: Use named attributes with pwd and grp modules (#155362)

* Replace pwd[0] with pwd.pw_name
* Replace pwd[2] with pwd.pw_uid
* Replace grp[0] with grp.gr_name
* Replace grp[2] with grp.gr_gid
* Replace pwd[3] with pwd.pw_gid

files:
M Doc/library/os.rst
M Lib/getpass.py
M Lib/http/server.py
M Lib/netrc.py
M Lib/shutil.py
M Lib/tarfile.py
M Lib/test/support/smtpd.py
M Lib/test/test_getpass.py
M Lib/test/test_os/test_posix.py
M Lib/test/test_pwd.py
M Lib/test/test_shutil.py
M Lib/test/test_tarfile.py
M Tools/c-analyzer/c_common/fsutil.py

diff --git a/Doc/library/os.rst b/Doc/library/os.rst
index 0a4a02c45b533bd..fceadde7df6bf48 100644
--- a/Doc/library/os.rst
+++ b/Doc/library/os.rst
@@ -452,7 +452,7 @@ process and user.
    process.  For most purposes, it is more useful to use
    :func:`getpass.getuser` since the latter checks the environment variables
    :envvar:`LOGNAME` or :envvar:`USERNAME` to find out who the user is, and
-   falls back to ``pwd.getpwuid(os.getuid())[0]`` to get the login name of the
+   falls back to ``pwd.getpwuid(os.getuid()).pw_name`` to get the login name of the
    current real user id.
 
    .. availability:: Unix, Windows, not WASI.
diff --git a/Lib/getpass.py b/Lib/getpass.py
index cfbd63dded6cc19..b9eec4c57abc97c 100644
--- a/Lib/getpass.py
+++ b/Lib/getpass.py
@@ -428,7 +428,7 @@ def getuser():
 
     try:
         import pwd
-        return pwd.getpwuid(os.getuid())[0]
+        return pwd.getpwuid(os.getuid()).pw_name
     except (ImportError, KeyError) as e:
         raise OSError('No username set in the environment') from e
 
diff --git a/Lib/http/server.py b/Lib/http/server.py
index 095b5744bd12fc6..a74773bf8a12d47 100644
--- a/Lib/http/server.py
+++ b/Lib/http/server.py
@@ -1013,7 +1013,7 @@ def nobody_uid():
     except ImportError:
         return -1
     try:
-        nobody = pwd.getpwnam('nobody')[2]
+        nobody = pwd.getpwnam('nobody').pw_uid
     except KeyError:
         nobody = 1 + max(x[2] for x in pwd.getpwall())
     return nobody
diff --git a/Lib/netrc.py b/Lib/netrc.py
index a28ea297df894b6..e9b5538d2c4399d 100644
--- a/Lib/netrc.py
+++ b/Lib/netrc.py
@@ -15,7 +15,7 @@ def _can_security_check():
 def _getpwuid(uid):
     try:
         import pwd
-        return pwd.getpwuid(uid)[0]
+        return pwd.getpwuid(uid).pw_name
     except (ImportError, LookupError):
         return f'uid {uid}'
 
diff --git a/Lib/shutil.py b/Lib/shutil.py
index 94617ec296f5087..ce6969d6a4bf5a9 100644
--- a/Lib/shutil.py
+++ b/Lib/shutil.py
@@ -983,7 +983,7 @@ def _get_gid(name):
     except KeyError:
         result = None
     if result is not None:
-        return result[2]
+        return result.gr_gid
     return None
 
 def _get_uid(name):
@@ -1001,7 +1001,7 @@ def _get_uid(name):
     except KeyError:
         result = None
     if result is not None:
-        return result[2]
+        return result.pw_uid
     return None
 
 def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
diff --git a/Lib/tarfile.py b/Lib/tarfile.py
index d12bd15aa2d2319..dc5c3a59744cbc4 100644
--- a/Lib/tarfile.py
+++ b/Lib/tarfile.py
@@ -2282,14 +2282,14 @@ def gettarinfo(self, name=None, arcname=None, fileobj=None):
         if pwd:
             if tarinfo.uid not in self._unames:
                 try:
-                    self._unames[tarinfo.uid] = pwd.getpwuid(tarinfo.uid)[0]
+                    self._unames[tarinfo.uid] = pwd.getpwuid(tarinfo.uid).pw_name
                 except KeyError:
                     self._unames[tarinfo.uid] = ''
             tarinfo.uname = self._unames[tarinfo.uid]
         if grp:
             if tarinfo.gid not in self._gnames:
                 try:
-                    self._gnames[tarinfo.gid] = grp.getgrgid(tarinfo.gid)[0]
+                    self._gnames[tarinfo.gid] = grp.getgrgid(tarinfo.gid).gr_name
                 except KeyError:
                     self._gnames[tarinfo.gid] = ''
             tarinfo.gname = self._gnames[tarinfo.gid]
@@ -2837,12 +2837,12 @@ def chown(self, tarinfo, targetpath, numeric_owner):
             if not numeric_owner:
                 try:
                     if grp and tarinfo.gname:
-                        g = grp.getgrnam(tarinfo.gname)[2]
+                        g = grp.getgrnam(tarinfo.gname).gr_gid
                 except KeyError:
                     pass
                 try:
                     if pwd and tarinfo.uname:
-                        u = pwd.getpwnam(tarinfo.uname)[2]
+                        u = pwd.getpwnam(tarinfo.uname).pw_uid
                 except KeyError:
                     pass
             if g is None:
diff --git a/Lib/test/support/smtpd.py b/Lib/test/support/smtpd.py
index 6537679db9ad24f..9800332a27f86cf 100755
--- a/Lib/test/support/smtpd.py
+++ b/Lib/test/support/smtpd.py
@@ -862,7 +862,7 @@ def parseargs():
         except ImportError:
             print('Cannot import module "pwd"; try running with -n option.', file=sys.stderr)
             sys.exit(1)
-        nobody = pwd.getpwnam('nobody')[2]
+        nobody = pwd.getpwnam('nobody').pw_uid
         try:
             os.setuid(nobody)
         except PermissionError:
diff --git a/Lib/test/test_getpass.py b/Lib/test/test_getpass.py
index 272414a62048561..23f8a328506c6ee 100644
--- a/Lib/test/test_getpass.py
+++ b/Lib/test/test_getpass.py
@@ -39,10 +39,13 @@ def test_username_falls_back_to_pwd(self, environ):
         expected_name = 'some_name'
         environ.get.return_value = None
         if pwd:
+            class User:
+                pass
             with mock.patch('os.getuid') as uid, \
                     mock.patch('pwd.getpwuid') as getpw:
                 uid.return_value = 42
-                getpw.return_value = [expected_name]
+                getpw.return_value = User()
+                getpw.return_value.pw_name = expected_name
                 self.assertEqual(expected_name,
                                  getpass.getuser())
                 getpw.assert_called_once_with(42)
diff --git a/Lib/test/test_os/test_posix.py b/Lib/test/test_os/test_posix.py
index f3d67027ad37277..1cc8b5d7b1c6165 100644
--- a/Lib/test/test_os/test_posix.py
+++ b/Lib/test/test_os/test_posix.py
@@ -1322,8 +1322,8 @@ def _create_and_do_getcwd(dirname, current_path_length = 0):
     @unittest.skipUnless(hasattr(pwd, 'getpwuid'), "test needs pwd.getpwuid()")
     @unittest.skipUnless(hasattr(os, 'getuid'), "test needs os.getuid()")
     def test_getgrouplist(self):
-        user = pwd.getpwuid(os.getuid())[0]
-        group = pwd.getpwuid(os.getuid())[3]
+        user = pwd.getpwuid(os.getuid()).pw_name
+        group = pwd.getpwuid(os.getuid()).pw_gid
         self.assertIn(group, posix.getgrouplist(user, group))
 
 
diff --git a/Lib/test/test_pwd.py b/Lib/test/test_pwd.py
index bdf57776c82be13..82acce85f1db572 100644
--- a/Lib/test/test_pwd.py
+++ b/Lib/test/test_pwd.py
@@ -50,7 +50,7 @@ def test_values_extended(self):
         # check whether the entry returned by getpwuid()
         # for each uid is among those from getpwall() for this uid
         for e in entries:
-            if not e[0] or e[0] == '+':
+            if not e.pw_name or e.pw_name == '+':
                 continue # skip NIS entries etc.
             self.assertIn(pwd.getpwnam(e.pw_name), entriesbyname[e.pw_name])
             self.assertIn(pwd.getpwuid(e.pw_uid), entriesbyuid[e.pw_uid])
diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py
index ed5d15ecc7ddad6..d6b3b6a642bee1e 100644
--- a/Lib/test/test_shutil.py
+++ b/Lib/test/test_shutil.py
@@ -1999,8 +1999,8 @@ def test_make_archive_owner_group(self):
         # testing make_archive with owner and group, with various combinations
         # this works even if there's not gid/uid support
         if UID_GID_SUPPORT:
-            group = grp.getgrgid(0)[0]
-            owner = pwd.getpwuid(0)[0]
+            group = grp.getgrgid(0).gr_name
+            owner = pwd.getpwuid(0).pw_name
         else:
             group = owner = 'root'
 
@@ -2027,8 +2027,8 @@ def test_make_archive_owner_group(self):
     def test_tarfile_root_owner(self):
         root_dir, base_dir = self._create_files()
         base_name = os.path.join(self.mkdtemp(), 'archive')
-        group = grp.getgrgid(0)[0]
-        owner = pwd.getpwuid(0)[0]
+        group = grp.getgrgid(0).gr_name
+        owner = pwd.getpwuid(0).pw_name
         with os_helper.change_cwd(root_dir), no_chdir:
             archive_name = make_archive(base_name, 'gztar', root_dir, 'dist',
                                         owner=owner, group=group)
@@ -2433,8 +2433,8 @@ def check_chown(path, uid=None, gid=None):
         check_chown(dirname, gid=gid)
 
         try:
-            user = pwd.getpwuid(uid)[0]
-            group = grp.getgrgid(gid)[0]
+            user = pwd.getpwuid(uid).pw_name
+            group = grp.getgrgid(gid).gr_name
         except KeyError:
             # On some systems uid/gid cannot be resolved.
             pass
diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py
index c86bcb79eb85d89..5fa97e2ac226c43 100644
--- a/Lib/test/test_tarfile.py
+++ b/Lib/test/test_tarfile.py
@@ -3351,12 +3351,12 @@ def root_is_uid_gid_0():
     except ImportError:
         return False
     try:
-        if pwd.getpwuid(0)[0] != 'root':
+        if pwd.getpwuid(0).pw_name != 'root':
             return False
     except KeyError:
         # On Cygwin, there is no root user (uid 0)
         return False
-    if grp.getgrgid(0)[0] != 'root':
+    if grp.getgrgid(0).gr_name != 'root':
         return False
     return True
 
diff --git a/Tools/c-analyzer/c_common/fsutil.py b/Tools/c-analyzer/c_common/fsutil.py
index a8cf8d0537e40db..eb9b74d552ece00 100644
--- a/Tools/c-analyzer/c_common/fsutil.py
+++ b/Tools/c-analyzer/c_common/fsutil.py
@@ -411,7 +411,7 @@ def _get_user_info(user):
     if user is None:
         uid = os.geteuid()
         #username = os.getlogin()
-        username = pwd.getpwuid(uid)[0]
+        username = pwd.getpwuid(uid).pw_name
         gid = os.getgid()
         groups = os.getgroups()
     else:

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]