Condorcet voting method in OOP Python For learning purposes, I made this little class which determines the Condorcet winner of a rank voting election (basically, a Condorcet winner is a candidate that would beat every other candidate in a head-to-head election).
This code does work, but I'd like to learn two-three things by posting it here:

Is this class written in a correct Pythonic way?
Do you see any ways to improve this code?
(bonus) If there are voting specialists among you, could you tell if that way of going through a Condorcet algorithm seems correct?


Condorcet.py:
import itertools


class Condorcet:
    &quot;&quot;&quot;
    Gives Concorcet winner from a list of votes
    &quot;&quot;&quot;
    def __init__(self, filename):
        self.candidates = set()
        self.scores = dict() # score for each permutation of two candidates
        self.results = dict() # winner of each pair of candidates
        self.winner = None # Condorcet winner of the election
        self.voters = list() # ranking of each voter
        self.filename = filename
    
    def process(self):
        self._get_data_from_file()
        self._build_dict()
        self._matches_candidates()
        self._elect_winner()
        return self.winner
    
    def _get_data_from_file(self):
        with open(self.filename, encoding='utf-8') as file:
            for lines in file:
                # currently hard-coding the number of candidates at 4
                # needs fixing but it's not my point here
                # (more a question for StackOverflow actually)
                (one, two, three, four) = lines.split(None, 4)
                self.voters.append((one, two, three, four))
    
    def _build_dict(self):
        &quot;&quot;&quot;
        Builds a dictionary of scores
        for each permutation of two candidates
        &quot;&quot;&quot;
        for voting in self.voters:
            for candidate in voting:
                self.candidates.add(candidate)
            for pair in list(itertools.permutations(voting, 2)):
                if pair not in self.scores:
                    self.scores[pair] = 0
                if voting.index(pair[0]) &lt; voting.index(pair[1]):
                    self.scores[pair] += 1
    
    def _matches_candidates(self):
        &quot;&quot;&quot;
        Analyses the dictionary of scores and
        gives the winner of every pair of candidates
        &quot;&quot;&quot;
        for match in list(itertools.combinations(self.candidates, 2)):
            reverse = tuple(reversed(match))
            if self.scores[match] &gt; self.scores[reverse]:
                self.results[match] = match[0]
            else:
                self.results[match] = match[1]
    
    def _elect_winner(self):
        &quot;&quot;&quot;
        If a candidates is the winner against
        every other candidate, declares him the winner
        (Note: does not detect Condorcet cycles yet)
        &quot;&quot;&quot;
        for candidate in self.candidates:
            candidate_score = 0
            for result in self.results:
                if candidate in result and self.results[result] == candidate:
                    candidate_score += 1
            if candidate_score == len(self.candidates) - 1:
                self.winner = candidate
                


execute_condorcet.py:
import Condorcet

condorcet = Condorcet.Condorcet('condorcet.txt')
winner = condorcet.process()
print(winner)


condorcet.txt:
# each line represents the ranking
# of candidates A, B, C and D
# by a unique voter

A B C D # this voter ranked A first, B second, C third, D fourth
C B D A
A B D C
C A D B
C B A D
C A B D

# and it goes on

and the winner could be C, beating :

A 4 votes to 2
B 4 votes to 2
D 5 votes to 1
The purpose of your class is to perform a single computation: given filename, determine winner. That's a task for a function; there is no need for a class. You can turn each method to an independent function that takes one or two arguments and returns one or two values instead of accessing instance attributes, and provide this function to orchestrate the whole computation:

def concordet_winner(filename):
    voters = _get_data_from_file(filename)
    candidates, scores = _build_dict(voters)
    results = _matches_candidates(candidates, scores)
    return _elect_winner(candidates, results)


Now, reading this code gives a clear picture how the data flows through the computation.



For the purpose of learning OOP, you could consider a slightly different exercise: instead of having all votes in a file, create a class with a vote method that takes in an individual voter's vote, and a winner property that updates automatically after every vote.