{"action":"opened","issue":{"url":"https://api.github.com/repos/pandas-dev/pandas/issues/16959","repository_url":"https://api.github.com/repos/pandas-dev/pandas","labels_url":"https://api.github.com/repos/pandas-dev/pandas/issues/16959/labels{/name}","comments_url":"https://api.github.com/repos/pandas-dev/pandas/issues/16959/comments","events_url":"https://api.github.com/repos/pandas-dev/pandas/issues/16959/events","html_url":"https://github.com/pandas-dev/pandas/issues/16959","id":243199118,"number":16959,"title":"Get rid of exec() usage?","user":{"login":"jbrockmendel","id":8078968,"avatar_url":"https://avatars5.githubusercontent.com/u/8078968?v=4","gravatar_id":"","url":"https://api.github.com/users/jbrockmendel","html_url":"https://github.com/jbrockmendel","followers_url":"https://api.github.com/users/jbrockmendel/followers","following_url":"https://api.github.com/users/jbrockmendel/following{/other_user}","gists_url":"https://api.github.com/users/jbrockmendel/gists{/gist_id}","starred_url":"https://api.github.com/users/jbrockmendel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jbrockmendel/subscriptions","organizations_url":"https://api.github.com/users/jbrockmendel/orgs","repos_url":"https://api.github.com/users/jbrockmendel/repos","events_url":"https://api.github.com/users/jbrockmendel/events{/privacy}","received_events_url":"https://api.github.com/users/jbrockmendel/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2017-07-15T20:42:19Z","updated_at":"2017-07-15T20:42:19Z","closed_at":null,"body":"```\r\n$ grep -r \"[^_]exec(\" pandas\r\npandas/compat/__init__.py:    exec(\"\"\"\r\npandas/core/groupby.py:        exec(_def_str)\r\npandas/core/groupby.py:        exec(_def_str)\r\n```\r\n\r\nThe usage in `compat` is for `raise_with_traceback` where the py2 version is a syntax error in py3.  This is pretty straightforward to de-exec by putting the py2-specific code in a module that only gets imported in py2.  \r\n\r\nThe usage in `core.groupby` is tougher.  The string being exec'ed defines a method in some cases and property in others.  The property version I think I've got a working fix, but the method version has me stumped.\r\n\r\nThese `_def_str`s are constructed in `core.groupby._whitelist_method_generator`:\r\n\r\n```\r\n    method_wrapper_template = \\\r\n        \"\"\"def %(name)s(%(sig)s) :\r\n    \\\"\"\"\r\n    %(doc)s\r\n    \\\"\"\"\r\n    f = %(self)s.__getattr__('%(name)s')\r\n    return f(%(args)s)\"\"\"\r\n    property_wrapper_template = \\\r\n        \"\"\"@property\r\ndef %(name)s(self) :\r\n    \\\"\"\"\r\n    %(doc)s\r\n    \\\"\"\"\r\n    return self.__getattr__('%(name)s')\"\"\"\r\n    for name in whitelist:\r\n        # don't override anything that was explicitly defined\r\n        # in the base class\r\n        if hasattr(GroupBy, name):\r\n            continue\r\n        # ugly, but we need the name string itself in the method.\r\n        f = getattr(klass, name)\r\n        doc = f.__doc__\r\n        doc = doc if type(doc) == str else ''\r\n        if isinstance(f, types.MethodType):\r\n            wrapper_template = method_wrapper_template\r\n            decl, args = make_signature(f)\r\n            # pass args by name to f because otherwise\r\n            # GroupBy._make_wrapper won't know whether\r\n            # we passed in an axis parameter.\r\n            args_by_name = ['{0}={0}'.format(arg) for arg in args[1:]]\r\n            params = {'name': name,\r\n                      'doc': doc,\r\n                      'sig': ','.join(decl),\r\n                      'self': args[0],\r\n                      'args': ','.join(args_by_name)}\r\n        else:\r\n            wrapper_template = property_wrapper_template\r\n            params = {'name': name, 'doc': doc}\r\n        yield wrapper_template % params\r\n```\r\n"}}
{"actor":{"display_login":"jbrockmendel"}}