-2

I'm trying to set up a score system where I need to input all the scores from a text file into an 'array of records'.

I'm fairly new to Python and hope for a simple solution.

In my program, the array of records would technically class as a list of namedtuples.

Currently I have:

Player = namedtuple("Player", ["name", "result", "difficulty", "score"])

Playerlist = []
while str(f.readline) != '':
    player = Player(
        f.readline(),
        f.readline(),
        f.readline(),
        f.readline())
    Playerlist.append(player)

I tried to print(Playerlist[0]), but nothing shows up.

I have also tried to print(Playerlist[0]) without any loop and got the expected result, though I won't have stored all the data from the text file into my program.

An example of what is in the text file (scores.txt):

George
lost
H
18
Holly
lost
H
28
Marcus
won
H
30

EDIT: I tried:

with open("scores.txt", "r") as f:
    for line in f:
        player = Player(
            f.readline(),
            f.readline(),
            f.readline(),
            f.readline())
        Playerlist.append(player)

However all of the contents came out mixed up:

Player(name='H\n', result='28\n', difficulty='Marcus\n', score='won\n')
martineau
  • 119,623
  • 25
  • 170
  • 301

2 Answers2

0

There are several problems with both this code and this approach. There are many file formats that are useful for this kind of thing; one very popular one that has built-in Python support is JSON.

import json
from pprint import pprint


old_players = [
    {
        'name': 'Bob',
        'result': 'success?',
        'difficulty': 'hard',
        'score': 55,
    },
    {
        'name': 'Tatsuki',
        'result': 'embarrassment',
        'difficulty': 'easy',
        'score': -2,
    },
]

with open('player-file.json', 'w') as outfile:
    outfile.write(json.dumps(old_players))

with open('player-file.json', 'r') as infile:
    new_players = json.loads(infile.read())

pprint(new_players)
# [{'difficulty': 'hard', 'name': 'Bob', 'result': 'success?', 'score': 55},
#  {'difficulty': 'easy', 'name': 'Tatsuki', 'result': 'embarrassment', 'score': -2}]

namedtuple isn't something I see used often. Using it with JSON can be a bit wonky, and while there are workarounds, it might be a better idea to either use a Player class with a simple custom serializer, subclass a class generated by namedtuple that defines a method to return either JSON or a JSON-formattable dict (pretty convoluted), or write a separate function that explicitly translates your namedtuple objects into JSON.

Regarding reading your existing format:

from pprint import pprint
from collections import namedtuple


Player = namedtuple("Player", ["name", "result", "difficulty", "score"])


def chunks(l, n):
    """Yield successive n-sized chunks from l."""
    for i in range(0, len(l), n):
        yield l[i:i + n]


with open('/tmp/players.old', 'r') as infile:
    lines = [l.strip() for l in infile.readlines()]

for player_values in chunks(lines, 4):
    pprint(Player(*player_values))

# Player(name='George', result='lost', difficulty='H', score='18')
# Player(name='Holly', result='lost', difficulty='H', score='28')
# Player(name='Marcus', result='won', difficulty='H', score='30')

The chunks function comes from this answer. It depends on you knowing the number of values you're unpacking per player, which can't change for this format.

When lines is read here, a list comprehension is used to strip the newline from the end of each value.

Finally, the Player tuple is instantiated with player_values, a list generated by chunks and expanded using *. This means, instead of passing the list player_values to the function Player.__init__(...), the individual values will be sent as *args. So effectively, instead of Player([name, result, difficulty, score]), the method call becomes Player(name, result, difficulty, score).

While this technically retrieves the values, note that the score that's assigned here is a string, not a numeric value. If you want that to be cast to an int, for example, you'd need to write out the full instantiation:

# ...

for player_values in chunks(lines, 4):
    pprint(Player(
        player_values[0],
        player_values[1],
        player_values[2],
        int(player_values[3]),
    ))

# Player(name='George', result='lost', difficulty='H', score=18)
# Player(name='Holly', result='lost', difficulty='H', score=28)
# Player(name='Marcus', result='won', difficulty='H', score=30)
kungphu
  • 4,592
  • 3
  • 28
  • 37
  • Wow, This is alot to take in at once, and your response was really quick too! The only problem I have with this is I wouldn't know how to read files using this, the old players in my program, they're all read from a file that the scores are saved to rather than inputting them myself. – Marcus Yip Apr 17 '19 at 00:33
  • If you haven't worked with JSON before, it's something to get familiar with; it's a common and very useful storage format, and often used in API responses (particularly where XML might be an alternative or a legacy format). It has the significant advantage of being well-defined and letting Python-native code handle both the encoding and decoding, so you don't have to worry about manually implementing both of those things for a custom storage format. – kungphu Apr 17 '19 at 00:36
  • It should not be difficult to check for the legacy file (store them with different names) and write a conversion function to pull from that file if there's no JSON version (writing to a JSON file afterwards so that conversion only happens once). Or are you stuck with that format? – kungphu Apr 17 '19 at 00:39
  • I'll see what I can do with JSON though, I'm very new to programming and especially OOP. – Marcus Yip Apr 17 '19 at 00:43
  • Understood. I've updated the answer with one option for your existing format, though I'd suggest switching to JSON (or even [CSV](https://docs.python.org/3/library/csv.html), though JSON is a better option) if possible. – kungphu Apr 17 '19 at 00:54
  • agh, I wish I could make both of these answers, is there any way to do that? I'd like to credit you both – Marcus Yip Apr 17 '19 at 01:00
  • Haha, don't worry about it. I appreciate the thought. ;) – kungphu Apr 17 '19 at 01:01
-1

Since you are using while str(f.readline) != '': it reads first line. Therefore, first line(and by extension all lines between records) must have blank line. Also readline in your while is missing paranthesis(). Other than that the code is working in python 3. You can use with to open file:

with open('test.txt', 'r') as f:
    while True:
        player = Player(f.readline(), f.readline(), f.readline(), f.readline())
        if "" in player:
            break;
        Playerlist.append(player)

for i in range(len(Playerlist)):
    print (Playerlist[i])

It automatically closes files and handles details for you. However, using json or other formats in a better option.

Nevus
  • 1,307
  • 1
  • 9
  • 21
  • This looks very promising, and if it works, was exactly what I was looking for! – Marcus Yip Apr 17 '19 at 00:54
  • @MarcusYip Note that with your original code you will need text file to have first line and all line between records to be empty(well not empty but it isn't read). However with code above ^ there should be no empty lines. Using formats like this are a real pain (a single newline can mess up entire program) so if possible please look into formats like json. – Nevus Apr 17 '19 at 00:58
  • I understand what you mean (I think..?) though this is just for a part of a project I have to hand in, in 4 days. I am desperate for the immediate answer and you gave it to me! – Marcus Yip Apr 17 '19 at 01:05
  • My program actually also writes scores to the text file, I have made sure that the values entered follow eachother, line by line. Would I still need to worry about blank lines? As the data entered in the text file is done by the program and not a human. – Marcus Yip Apr 17 '19 at 01:06
  • If you are absolutely sure file follows the format there's no problem. – Nevus Apr 17 '19 at 01:10
  • For this solution, there should be _no_ blank lines in your file (except possibly at the end of the file, which will effectively be ignored by the `if "" in player` check). You will probably also need to use `f.readline().strip()` in your `Player(...)` instantiations or you'll get newlines at the end of all of your values. – kungphu Apr 17 '19 at 01:11
  • Ah, I was just looking for how to get rid of those newlines, this is very helpful! Yes, I am pretty sure there are no blank lines, the file will be accessed by the program and the program only, I've made it so that it saves this way. – Marcus Yip Apr 17 '19 at 01:18