How to list the frame numbers which contain keyframes in Python?
Graph editor Operator
(for version 2.79.4 and above)

Set up the selections on your graph editor to match those of the dope sheet.

 

Since version 2.79.4 the graph editor has the very handy context.selected_editable_fcurves property. The operator below uses each of these selected fcurves.  Render frames are added to a list sequentially from frame start to end if there is any change (over a tolerance) in the evaluated fcurve between frames current, f, and previous  f-1.

The render command line is written to a text block 

To use, run the script (to register) Use the space bar and search for  "Report Changed Frames" in the graph editor space.  Remember it uses the selected fcurves, press A before running to select all visible in current graph editor selection.

import bpy


def main(context):
    scene = context.scene
    selected = context.selected_editable_fcurves[:]
    # all selected fcurves
    f = scene.frame_start
    render_frames = [f]
    f += 1
    while f &lt;= scene.frame_end:
        def changed(fc, tol=0.0001):
            return abs(fc.evaluate(f) - fc.evaluate(f - 1)) &gt; tol

        if any(changed(fc) for fc in selected):
            render_frames.append(f)
        f += 1

    report = bpy.data.texts.new("Changed Frames")

    report.write('"%s" -b "%s" ' % (bpy.app.binary_path, bpy.data.filepath)) 
    for f in render_frames:
        report.write(" -f %d" % f)

class GRAPH_OT_report_frames(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "graph.report_frames"
    bl_label = "Report changed frames"

    @classmethod
    def poll(cls, context):
        return context.area.type == 'GRAPH_EDITOR'

    def execute(self, context):
        main(context)
        return {'FINISHED'}

def register():
    bpy.utils.register_class(GRAPH_OT_report_frames)

def unregister():
    bpy.utils.unregister_class(GRAPH_OT_report_frames)

if __name__ == "__main__":
    register()


As stated this doesn't list all frames with a keyframe, rather it lists all frames that have some animated change on that frame, (for any of  all selected fcurves).  

If you are just after keyframe frames, add all kfp.co.x (keyframe point frame) points to a list, for all selected fcurves.  Converting to a set s = set() will remove doubles.

all_frames = set(kfp.co.x 
        for fc in context.selected_editable_fcurves
        for kfp in fc.keyframe_points)




Oldschool (all versions)

You can also iterate through all actions and check the state of Keyframe.select_control_point property to get the corresponding frame of all keys in the selection.



import bpy
from math import ceil


class ACTION_OT_report_selection(bpy.types.Operator):
    """Report Selected Keyframes"""
    bl_idname = "action.report_selected_keyframes"
    bl_label = "Report Selected Keyframes"

    def selected_keys(self):
        """Returns the frames of all selected keys""" 
        ctrl_points = set()
        for action in bpy.data.actions:
            for channel in action.fcurves: 
                for key in channel.keyframe_points:       
                    if key.select_control_point:
                        ctrl_points.add(ceil(key.co.x))
        return sorted(ctrl_points)

    def cli_block(self, frame_list, block_name):
        """Creates a new textblock with all necessary command line arguments"""
        report = bpy.data.texts.new(block_name)
        report.write('"%s" -b "%s" ' % (bpy.app.binary_path, bpy.data.filepath)) 
        for f in frame_list:
            report.write(" -f %d" % f)

    @classmethod
    def poll(cls, context):
        area_types = ('DOPESHEET_EDITOR', 'GRAPH_EDITOR', 'TIMELINE')
        return context.area.type in area_types

    def execute(self, context):
        render_frames = self.selected_keys()

        if not render_frames:
            self.report({'ERROR'}, "Nothing selected")
            return {'CANCELLED'}
        else:
            textblock_name = "Render Arguments"
            self.cli_block(render_frames, textblock_name)
            self.report({'INFO'}, "'%s' text block created" % textblock_name)

        return {'FINISHED'}


def register():
    bpy.utils.register_class(ACTION_OT_report_selection)

def unregister():
    bpy.utils.unregister_class(ACTION_OT_report_selection)

if __name__ == "__main__":
    register()