1

I am trying to create multiple instances of a Soda object using information from a file. The file is formatted like this Name,price,number

Mtn. Dew,1.00,10

Coke,1.50,8

Sprite,2.00,3

My code I have is this (this is a function within main()):

from Sodas import Soda

def fillMachine(filename) :

    # Create an empty list that will store pop machine data
    popMachine = []

    # Open the file specified by filename for reading
    infile = open(filename, "r")

    # Loop to read each line from the file and append a new Soda object 
    # based upon information from the line into the pop machine list.
    for line in infile :
        popMachine.append(Soda(str(line.strip())))

    # Close the file
    infile.close()

    # Return the pop machine list
    return popMachine

If I got this right, popMachine should be a list of 3 different Soda objects, each with one line of the input file.

Within my class, I then need to be able to get just the name or price or quantity for use in calculations later. My class Sodas code looks like this:

#Constructor
def __init__(self, _name = "", _price = 0.0, _quantity = 0) :
        self._name = self.getName()
        self._price = _price
        self._quantity = _quantity

def getName(self) :
    tempList = self.split(",")
    self._name = tempList[0]
    return self._name

This is where I run into problems. IIRC self stands in place of line in the main code, so self should be a string such as "Mtn. Dew,1.00,10" and the expected outcome of the split(",") method should form a list like ["Mtn. Dew", "1.00", "10"] where I can then use the index of that list to return just the name.

However, I get this error "AttributeError: Soda instance has no attribute 'split'" And I'm not sure why. Also, all the comments in this code came from my instructor as part of the assignment, so even if there are quicker/better methods for doing this whole thing, this is the way I have to do it :/

smci
  • 32,567
  • 20
  • 113
  • 146
Lauren Mabe
  • 11
  • 1
  • 2
  • 2
    `self` refers to your Soda instance, so unless you define a method called `split`, it does not have one. – user3483203 May 14 '18 at 02:41
  • There's no place you actually pass in a string to either `getName` or `__init__`. Caling `self.getName()` is not passing in any string. – smci May 14 '18 at 09:08
  • This is a case where `__init__` will expect proper arguments `(name,price,number)`, not a string. So the Pythonic thing is to add a **staticmethod** `from_string`/`make`/`make_from_string`, which then **calls the `Soda()` constructor**. Instead of your `getName` being called *from the constructor*, which is a much more painful decomposition. – smci May 14 '18 at 10:01
  • Take a skim of [Meaning of @classmethod and staticmethod for beginner?](https://stackoverflow.com/questions/12179271/meaning-of-classmethod-and-staticmethod-for-beginner/14605349#14605349) – smci May 14 '18 at 10:09

5 Answers5

1

The self variable refers to the object instance. What you can do is to either split the line in the constructor like so

def __init__(self, line):
    name, price, quantity = line.split(',')
    self.name = name
    self.price = price
    self.quantity = quantity

...or you can split it lazily when asked for it

def __init__(self, line):
    self._name = None
    self.line = line

@property
def name(self):
    if self._name is None:
        self._name = self.line.split()[0]
    return self._name

Note that in python variables such as function names are usually snake_case by convention.

AGN Gazer
  • 8,025
  • 2
  • 27
  • 45
vidstige
  • 12,492
  • 9
  • 66
  • 110
  • I do not think there is anything lazy about splitting a line in your second example: you already split the line during initialization. So, instead, just return `self._name`. – AGN Gazer May 14 '18 at 02:54
  • right, that should not be there. Just the first line was supposed to be there, thanks. Edited now. – vidstige May 14 '18 at 02:55
  • 1
    I don't see the benefit of the lazy option. You are still storing the exact same amount of information, all you do is invoke an additional call to `split` each time you want to know the name of a soda. Plus, it also pushes errors associated with creating the class (not enough values, etc) to a later point, instead of when the class is instantiated. – user3483203 May 14 '18 at 02:58
  • I did not mention or imply any benefit, but it _is_ an option. – vidstige May 14 '18 at 03:01
  • @chrisz I edited the "lazy" code to actually be lazy. But I think this is an overkill here - the first version is the best. – AGN Gazer May 14 '18 at 03:18
  • 1
    That is a pretty drastic change to make, you should check with users before making modifications that alter how their code works. – user3483203 May 14 '18 at 03:19
  • @vidstige Please fill free to roll back my last edit if you disagree with it. – AGN Gazer May 14 '18 at 03:21
1

When you use self, you refer to the instance of Soda, and since you have not defined a split method, it will not have one.

You can simply use unpacking to pass the result of split to the class.

You may want to add in some checks to make sure that unpacking returns three values, although since you use default arguments it will only error out if you provide more than three values.

class Soda:
  def __init__(self, name = "", price = 0.0, quantity = 0) :
        self.name = name
        self.price = price
        self.quantity = quantity


sodas = []
with open('test.txt') as f:
  for line in f:
    sodas.append(Soda(*line.split(',')))

for soda in sodas:
  print(soda.name)

Output:

Mtn. Dew
Coke
Sprite

You could even define a helper method that returns a Soda instance from a line in your file:

@staticmethod
def make_soda(line):
  try:
    name, price, quantity = line.split(',')
    return Soda(name, price, quantity)
  except:
    raise ValueError('Bad Soda')

Which you can call using:

Soda.make_soda(line)
user3483203
  • 50,081
  • 9
  • 65
  • 94
1

IIRC self stands in place of line in the main code, so self should be a string such as "Mtn. Dew,1.00,10" and the expected outcome of the split(",") method should form a list like ["Mtn. Dew", "1.00", "10"] where I can then use the index of that list to return just the name.

Hold up just a minute. This is a common misconception when people are learning to code. Fundamentally, you are conflating your source code with the data-type str. This is an easy mistake to make, for indeed, one creates a text-file when one is writing source code. One writes text into the file, and one can even load that in Python (open('my_script.py').read()) and we get a string! And yes, strings are consumed by the programs you run when your source code takes the journey from your programming language to machine code. But I advocate that you keep these two things separate in your mind. Your source code is not a string. It should sit "above" it conceptually (and anyway, we are dealing in useful abstractions here). So, while your idea is essentially correct,

self stands in place of line in the main code

But here "the line" means what the line refers to, the piece of logic executed by your code, not literally the line of code you write. So, self refers to the object instance when the method is executed by an instance of the class.

So, self is not a string. It is an object of the type your class is defining. Your class doesn't have that method. str objects do.

Here's a little look at the python data model. "self" is quite straight-forward. The instance itself is passed as an argument. This is done under the covers for you by "magic" (magic that you can learn to play with later) but essentially, my_instance.some_method() is mostly equivalent to MyClass.some_method(my_instance)

So consider,

In [1]: class SomeClass:
   ...:     def __init__(self, name):
   ...:         self.name = name
   ...:     def foo(self):
   ...:         print(self.name, 'says foo!')
   ...:

In [2]: some_instance = SomeClass('Juan')

In [3]: some_instance.foo()
Juan says foo!

In [4]: SomeClass.foo(some_instance)
Juan says foo!

A method is merely a function that is part of a class, and if it get's called by an instance of that class, it will auto-magically get the instance itself passed as the first argument. Note, it is the position of the argument that is privileged here, not the name self which is merely convention. It can be whatever you want. The names don't even have to be consistent within the class (but of course, consistent within the function itself). Let's call it zucchini for fun:

In [8]: class WhyNot:
   ...:
   ...:     def __init__(self):
   ...:         self.foo_flag = False
   ...:         self.bar_flag = False
   ...:
   ...:     def foo(zucchini):
   ...:         print("I speak American English")
   ...:         zucchini.foo_flag = True
   ...:
   ...:     def bar(courgette):
   ...:         print("And British English")
   ...:         courgette.bar_flag = True
   ...:
In [9]: x = WhyNot()

In [10]: x.foo()
I speak American English

In [11]: x.bar()
And British English

In [12]: x.foo_flag
Out[12]: True

In [13]: x.bar_flag
Out[13]: True

But please, stick to convention.

juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172
0

In the class Soda, the key word 'self' refers to an instance of Soda. Simply 'split' a Soda object does not make much sense here. What you want to do is actually to split the string arguments at the creation of each Soda object.

Therefore, a better way is to split arguments into name, price and quantity in the init() function.

def __init__(self, args = "", _price = 0.0, _quantity = 0):
        args = args.split(",")
        self._name = args[0]
        self._price = args[1]
        self._quantity = args[2]

While in the getName function, as the name suggests, it is better to only fetch the name.

def getName(self) :
    return self._name

I hope this can help you in your way learning Python~

0

There's no place you actually pass in a string to either getName or __init__. Calling self.getName() is not passing in any string.

This is a case where init will expect (name,price,number), not a string. So the Pythonic thing is to add a staticmethod from_string/make/make_from_string:

class Soda:
    def __init__(self, name = '', price = 0.0, quantity = 0) :
            self._name = name
            self._price = price
            self._quantity = quantity

    @staticmethod
    def from_string(s):
        return Soda(s.split(','))


 >>> Soda.from_string('Mtn. Dew,1.00,10')
 <__main__.Soda object at 0x107428f60>
 # Success! Mind you, your class could do with a __str__() method now...
smci
  • 32,567
  • 20
  • 113
  • 146