This script is meant to do the tedious work involved in a monthly event I run on a subreddit. It does a search for all posts relevant to the event since the last posting, and creates the bulk of the next month's post.I would most like criticism at an organizational level. My functions ramble together, and it's hard to keep track of what I have, so I'd like suggestions for a better to do that. In the problem domain, the name Piece is not as horribly vague as it seems. Of course, if you're aware of this and still think it's an awful name, I welcome your thoughts.import configparserimport datetimeimport loggingimport reimport picklefrom typing import Optionalimport prawimport praw.modelsDELIMITER = '---'  # type: strREDDIT = NoneJAM_MAINTAINER = 'G01denW01f11'def init_reddit(config_pathname: str) -> praw.Reddit:    Create global Reddit object from config file    config = configparser.ConfigParser()    config.read(config_pathname)    return praw.Reddit(client_id=config['RedditParams']['client_id'],                       client_secret=config['RedditParams']['client_secret'],                       user_agent=config['RedditParams']['user_agent'])def get_reddit() -> praw.Reddit:    Get the global Reddit object. Create it if it hasn't been created    global REDDIT    if not REDDIT:        REDDIT = init_reddit('config.ini')    return REDDITclass Piece(object):    A piece to be listed in the piano jam    def __init__(self, composer: str = None, title: str = None, video_url: str = None, score_url: str = None,                 category: str = None):        self.composer = composer  # type: str        self.title = title  # type: str        self.video_url = video_url  # type: str        self.score_url = score_url  # type: str        self.category = category  # type: str    def __eq__(self, other: 'Piece') -> bool:        return self.composer == other.composer and self.title == other.title    def __ne__(self, other: 'Piece') -> bool:        return not self == other    def __str__(self) -> str:        return '{}: [{}]({}) | [Sheet Music]({})'.format(self.composer, self.title, self.video_url.replace(')', '\)'),                                                         self.score_url.replace(')', '\)'))class Submission(object):    A submission to the month's Jam    def __init__(self, username: str = None, url: str = None, title: str = None, piece: Piece = None):        self.username = username  # type: str        self.url = url  # type: str        self.title = title  # type: str        self.piece = piece  # type: Piece    def __eq__(self, other: 'Submission') -> bool:        return self.username == other.username and self.piece == other.piece    def __ne__(self, other: 'Submission') -> bool:        return not self == other    def __str__(self) -> str:        return '{}\'s {} by [/u/{}]({})'.format(self.piece.composer, self.piece.title, self.username, self.url)    def set_piece(self, pieces: [Piece]) -> None:                From a list of valid pieces, set the one that matches        :param pieces: A list of pieces to choose from                self.piece = find_piece_matching_title(pieces, self.title)        if not self.piece:            logging.warning('Could not find piece for {} | {}'.format(self.title, self.url))def find_piece_matching_title(pieces: [Piece], title: str) -> Optional[Piece]:        Use a simple heuristic to tell which piece a submission is from the title    :param pieces: Pieces to choose from    :param title: Submission title    :return: Appropriate piece, if any        for piece in pieces:        if biggest_word_in_line(piece.title).lower() in title.lower():            return piece    return Nonedef format_title(section_title: str) -> str:        Apply proper formatting to the title of a section    :param section_title: The title of a section to be formatted    :return: Formatted title        return '**{}**'.format(section_title)class Jam(object):    A Piano Jam posting    CATEGORIES = ['Jazz', 'Classical', 'Ragtime', 'Video Game / Anime / Film']  # type: [str]    def __init__(self, outline_pathname: str = 'jam_outline.txt'):                Create a Piano Jam instance from a given outline file        :param outline_pathname: pathname to file with default jam contents                self.filename = ''  # type: str        self.submissions = []  # type: [Submission]        self.pieces = []  # type: [Piece]        with open(outline_pathname, 'r') as f:            self.text = f.read()    def __str__(self):        submissions_str = ''        for submission in self.submissions:            submissions_str += str(submission) + '\n\n'        pieces_str = ''        for piece in self.pieces:            pieces_str += str(piece) + '\n\n'        return self.text.format(submissions_str, pieces_str)    def add_submission(self, submission: Submission):                Add a submission to the Jam. Multiple submissions do not get added        :param submission: Submission to the Piano Jam        :return: None                for prior_submission in self.submissions:            if submission.username == prior_submission.username and submission.piece == submission.piece:                if submission.url != prior_submission.url:                    logging.warning('User {0} attempted to submit a piece multiple times'.format(submission.username))                return        self.submissions.append(submission)    def add_piece(self, piece: Piece):        if piece not in self.pieces:            self.pieces.append(piece)    def save(self, filename: str='') -> None:        if filename:            self.filename = filename        if not self.filename:            raise ValueError('No filename to save to!')        with open(self.filename, 'wb') as f:            pickle.dump(self, f)    @classmethod    def load(cls, filename: str) -> 'Jam':        with open(filename, 'rb') as f:            jam = pickle.load(f)  # type: Jam        if type(jam) != Jam:            raise TypeError('Tried to load a Jam. Got {}'.format(type(jam)))        assert jam.filename == filename        return jamdef parse_piece(piece_text: str) -> Piece:        Construct a Piece from its string representation.    Expected format: Composer: [Title](url) | [Sheet Music](sheetUrl)    :param piece_text: Line from Piano Jam specifying a Piece to learn        piece = Piece()    piece.composer = piece_text[:piece_text.index(':')]    piece.title = re.findall(re.compile('\[(.*?)\]'), piece_text)[0]  # type: str    urls = re.findall(re.compile('\((.*?)\)'), piece_text)    piece.video_url = urls[0]  # type: str    piece.score_url = urls[1]  # type: str    return piecedef parse_pieces(section_text: str) -> [Piece]:    Parse all the pieces in a given section    pieces = section_text.split('\n')[1:]  # First line is the category; discard    return (parse_piece(piece_text) for piece_text in pieces if piece_text.strip() != '')def get_pieces_from_jam(jam_text: str) -> [Piece]:        Parse all the pieces from a Jam, given the contents of a post    :param jam_text: The contents of a Piano Jam posting    :return: List of pieces to be used for the Jam        sections = jam_text.split(DELIMITER)    sections = (section.strip() for section in sections)    filtered_sections = []    for section in sections:        section = section.strip()        for category in Jam.CATEGORIES:            category = format_title(category)            if section.startswith(category):                filtered_sections.append(section)                break    pieces = []    for section in filtered_sections:        pieces.extend(parse_pieces(section))    return piecesdef get_selections_from_url(url: str) -> [Piece]:        Parse all the pieces from a jam, given its url    :param url: URL to a Piano Jam post    :return: List of pieces to be used for the Jam        try:        post = praw.models.Submission(get_reddit(), url=url)    except KeyError:        raise KeyError('Could not recognize url {0}'.format(url))    return get_pieces_from_jam(post.selftext)def search_for_submissions():        Search Reddit for posts with [Piano Jam] in title within past month    :return: List of urls to posts        subreddit = get_reddit().subreddit('piano')    results = subreddit.search('[Piano Jam]', sort='new', time_filter='month')    return (result for result in results)def filter_submissions(submissions: [praw.models.Submission], jam: praw.models.Submission):    return [submission for submission in submissions            if '[piano jam]' in submission.title.lower() and            datetime.datetime.fromtimestamp(submission.created) >            datetime.datetime.fromtimestamp(jam.created)]def find_last_jam() -> praw.models.Submission:    candidates = search_for_submissions()    for candidate in candidates:        if candidate.author == JAM_MAINTAINER and '[' not in candidate.title:            return candidate    raise ValueError('Could not find last Piano Jam')def biggest_word_in_line(line: str) -> str:    words = line.split()    length = 0    biggest_word = None    for word in words:        if len(word) > length:            length = len(word)            biggest_word = word    assert biggest_word    return biggest_worddef create_jam() -> [Submission]:        Find all Piano Jam submissions since the last posting    Log a warning if there are submissions not in the previous Jam.    Create Jam from submissions and pickle it for later use.        previous_jam = find_last_jam()    entries = filter_submissions(search_for_submissions(), previous_jam)    submissions = [Submission(entry.author, entry.shortlink, entry.title) for entry in entries]    pieces = get_pieces_from_jam(previous_jam.selftext)    new_jam = Jam()    for submission in submissions:        submission.set_piece(pieces)        if submission.piece:            new_jam.add_submission(submission)    new_jam.save('current_jam.txt')
Script to summarize Reddit posts over the past month
python;reddit
It doesn't make a whole lot of sense to have a function with no arguments that modifies a global object. Your init_reddit function is better than your get_reddit function because of this.IMHO you should rethink why you have a function that has more comments than code in it. There may be a more idiomatic way to express that. (see find_piece_matching_title, format_title)Classes are good; consider making a Reddit class that either inherits from praw.Reddit or has your reddit instance as a member variable. You could put search_for_submissions and filter_submissions in there.Your parse_piece, parse_pieces, get_pieces_from_jam, etc. functions should be a part of your Piece or Jam objects. If you're using objects to contain your data, it makes sense to have functions manipulating that data as methods.Overall, I see in your code a whole bunch of top-level functions and objects without a clear indication of how they're supposed to work together. The difficult part in coding is not necessarily writing the individual pieces, but in figuring out the simplest (least complected) way for them to interact.