For the benefit of people who don't want to rely on a third party module like Pillow, here is an entirely python 2 and 3 native solution:
import sys
is_py2 = sys.version_info[0] == 2
def is_animated_gif(image_path):
    """Return true if image is an animated gif
    primarily used this great deep dive into the structure of an animated gif
    to figure out how to parse it:
        http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
    Other links that also helped:
        https://en.wikipedia.org/wiki/GIF#Animated_GIF
        https://www.w3.org/Graphics/GIF/spec-gif89a.txt
        https://stackoverflow.com/a/1412644/5006
    :param image_path: string, assumed to be a path to a gif file
    :returns: boolean, True if the image is an animated gif
    """
    ret = False
    image_count = 0
    def skip_color_table(fp, packed_byte):
        """this will fp.seek() completely passed the color table"""
        if is_py2:
            packed_byte = int(packed_byte.encode("hex"), 16)
        has_gct = (packed_byte & 0b10000000) >> 7
        gct_size = packed_byte & 0b00000111
        if has_gct:
            global_color_table = fp.read(3 * pow(2, gct_size + 1))
    def skip_image_data(fp):
        """skips the image data, which is basically just a series of sub blocks
        with the addition of the lzw minimum code to decompress the file data"""
        lzw_minimum_code_size = fp.read(1)
        skip_sub_blocks(fp)
    def skip_sub_blocks(fp):
        """skips over the sub blocks
        the first byte of the sub block tells you how big that sub block is, then
        you read those, then read the next byte, which will tell you how big
        the next sub block is, you keep doing this until you get a sub block
        size of zero"""
        num_sub_blocks = ord(fp.read(1))
        while num_sub_blocks != 0x00:
            fp.read(num_sub_blocks)
            num_sub_blocks = ord(fp.read(1))
    with open(image_path, "rb") as fp:
        header = fp.read(6)
        if header == b"GIF89a": # GIF87a doesn't support animation
            logical_screen_descriptor = fp.read(7)
            skip_color_table(fp, logical_screen_descriptor[4])
            b = ord(fp.read(1))
            while b != 0x3B: # 3B is always the last byte in the gif
                if b == 0x21: # 21 is the extension block byte
                    b = ord(fp.read(1))
                    if b == 0xF9: # graphic control extension
                        block_size = ord(fp.read(1))
                        fp.read(block_size)
                        b = ord(fp.read(1))
                        if b != 0x00:
                            raise ValueError("GCT should end with 0x00")
                    elif b == 0xFF: # application extension
                        block_size = ord(fp.read(1))
                        fp.read(block_size)
                        skip_sub_blocks(fp)
                    elif b == 0x01: # plain text extension
                        block_size = ord(fp.read(1))
                        fp.read(block_size)
                        skip_sub_blocks(fp)
                    elif b == 0xFE: # comment extension
                        skip_sub_blocks(fp)
                elif b == 0x2C: # Image descriptor
                    # if we've seen more than one image it's animated
                    image_count += 1
                    if image_count > 1:
                        ret = True
                        break
                    # total size is 10 bytes, we already have the first byte so
                    # let's grab the other 9 bytes
                    image_descriptor = fp.read(9)
                    skip_color_table(fp, image_descriptor[-1])
                    skip_image_data(fp)
                b = ord(fp.read(1))
    return ret
The is_animated_gif() function works by skipping over all the extensions and color information and counting the actual images in the file, when it finds the second image it can safely assume the gif is animated and its work is done. 
It doesn't rely on any shortcuts like checking for the existence of an application extension block because it didn't seem like those were required for the gif to be animated, and I didn't want to assume anything.