1

I have a text file with a series of clients. Each line has a different client. Each client has an ID, a username, and a password.

I want to create a "Client" class, and generate objects in that class in a loop. Each object would have a username and a password, and would be stored in a variable that contains the client's ID. Client 1 would be stored in "client_1", Client 2 would be stored in "client_2", etc.

I created the method "read()" that opens the text file, breaks if there are empty lines, and retrieves the ID, username and password for each client (each line).

What I can't figure out, is how to make it so that when the client's ID is "1", I create an object for that client and store it in the variable "client_1". When the client's ID is "2", I store client's 2 object in the variable "client_2", and so on.

But I want to do this automatically, instead of having 9000 clients and having to create 9000 variables myself.

Thanks

class Client:

    def __init__(self, username, password):
        self.username = username
        self.password = password

    def read(self):
        clients = []
        with open("Clients.txt", "r") as file:
            lines = file.readlines()
            for line in lines:
                if not line:
                    break
                else:
                    client = line.split(" | ")
                    client_id = client[0]
                    #How do I create the variable "client_client[0]"?
                    username = client[1]
                    pre_password = client[2]
                    password = pre_password.strip("\n")
                    #client_client[0] = Client(username, password)
                    clients.append(#client_client[0])
            return clients

My text file (ID, username, password - from left to right):

1 | admin | Z9?zzz
2 | John | J1!jjj
3 | Steve | S1!sss

Also, is there a problem if I'm using the "username" and "password" variables in read(), when I have already used them in the def init?

Thanks

3 Answers3

2

Advices

  • In your loop, you are using break. Don't do that, what you want to use is continue that will skip this iteration instead of get you out of it.

  • You are only using strip('\n') on your password. You should do it on all items (to make sure they are all uniform). But you were right to use strip('\n') only on the password case because it's the only one that has \n. Don't put argument into strip() and it will take care of all the spaces, tabs, and other \n, \r and such.

  • You should see the self parameter of a class as a box that lives inside it. It's the "environement" you can basically access everywhere inside your class. And if you create something inside self, such as self.client, it will not be the same as a single variable named client. What you probably wanted to do here is assign your client list you just read to the self, such as self.client_list = self.read().

About your program

What you need to do is not create as many variables as there are users. But you are right in the philosophy, you want to have them stored in one place. And that's what you did. Now, the point of your program is still obscure to us. But what you probably want to do is :

  1. Have a database in which you know how items are ordered. You know that in each element of your users_list, you have the first item that is an id, the second its name and the third its password.

  2. Make operations based on this database.

    • You want to "load" a client, check if he exists in your database, and match the password he entered with the one you have linked to it !
    • You want to delete one ?
    • Order an ice cream for someone already logged ?
IMCoins
  • 3,149
  • 1
  • 10
  • 25
1

What is wrong with the list of clients that you already have? You can access the clients as clients[0], clients[1] and so forth. The list is the abstraction for arbitrary many variables. The lists are 0-indexed, so the first element has index 0. This can be confusing, especially since some languages like R, FORTRAN or Wolfram Language are 1-indexed. I do not consider that a fundamental problem, you just have to be clear about it. If it really bothers you, you could use a dict with numeric indices and just map whatever index you want to a customer.

Also I would make read_clients a free function. It does not use anything except the public API of the Client class to function. Therefore it should not be a member function. But if you want to have it in that class, make it a @staticmethod at least because it is not tied to one particular Client.

What I mean with public API: Every class that you write has public methods and private methods. In Python there are no access specifiers, but the convention is that methods starting with an underscore (_) are not be used externally. The ones with two underscores (like __init__) are also not to be called directly but are called by syntactic sugar. You want to have focused methods (SRP), so have the least amount of public functions possible. Also consider this: Say somebody wants to use your Client class to read a different file format with usernames and passwords. That person would have to modify your code in order to add another read method. But if the read_clients function was external and just used the __init__ of your Client class, that somebody could just add a new free function somewhere. This is the OCP.

And Client could be just a collections.NamedTuple. So like this:

import collections

Client = collections.namedtuple('Client', ['username', 'password'])

def read_clients(filename):
    clients = []

    # open file
        # loop over all lines in the list
            # Parse the username and password.
            username = # …
            password = # …
            client = Client(username, password)
            clients.append(client)

    return clients

You do not have to define the class Client yourself, and all Client objects (say client) will have the attributes client.username and client.password.

Using password and username is not a problem because the parameter of __init__ are in a different scope. The members of your class can only be accessed via self., so that is not a problem either.

If you really wanted to dynamically create variables, there are ways to do so, see here. But again you want to abstract away the actual number of clients and that is what the list is for.

Martin Ueding
  • 8,245
  • 6
  • 46
  • 92
  • If I used the list, could I use class methods? I don't know. Also, client 1 would be in clients[0] and client 2 would be in clients[1]. Wouldn't this bring problems? I'm also googling "Python public API" and "staticmethods", because honestly I don't know what that is. I also don't understand what you mean by collections.namedtuple. I still have a lot to learn, but thanks! – António Gonçalves Feb 09 '19 at 14:00
  • @AntónioGonçalves: I have expanded my answer and tried to address all the points in your comment. – Martin Ueding Feb 10 '19 at 12:47
1

Don't create variables dynamically! Instead use Python's built-in dictionary object, which allows you to look values up by key.

class Client:

    def __init__(self, username, password):
        self.username = username
        self.password = password

def read(file):
    clients = {}
    for line in file:
        if not line:
            continue  # allows blank lines anywhere
        else:
            id, name, password = line.split(" | ")
            password = password.strip("\n")
            clients[id] = Client(name, password)
    return clients

if __name__ == '__main__':
    data = """\
1 | admin | Z9?zzz
2 | John | J1!jjj
3 | Steve | S1!sss
"""
    from io import StringIO
    with StringIO(data) as file:
        clients = read(file)
    for id, client in clients.items():
        print(id, client.username, client.password)

It was somewhat confusing to have the read function as a method of the class, since calling it then required you to create a Client instance in order to call it. An alternative was to recast it as a classmethod, but that over-complicates things, so it made more sense as a simple function. Instead of returning a list it returns a dictionary of Clients, each keyed on its respective id.

I've simplified the processing somewhat (though there is still no error handling), and made the loop more robust to blank lines.

I also added a little bit of test code to allow you to verify that the clients have been correctly created from your test data, and to make your class and function importable should you choose. Replace with StringIO(data) as file: with with open("Clients.txt") as file: to use a real data file instead.

holdenweb
  • 33,305
  • 7
  • 57
  • 77