Enumerate an array of class objects

python
practice

#1

A newbie question, Python 3.5...

class Package:
    def __init__(self, name, address):
        self.customer = name
        self.pickuplocation = address

customers = []

customers.append(Package("John Doe","350 5th Ave"))
customers.append(Package("Jane Doe","100 7th Ave"))
customers.append(Package("Joe Daniels","11 1st Ave"))

for x, v in enumerate(customers):
    print (x, v.customer, v.pickuplocation)
    for k, v in enumerate(v):
        print (k, v)

Output

0 John Doe 350 5th Ave
Traceback (most recent call last):
  File "python", line 14, in <module>
TypeError: 'Package' object is not iterable

How do I go about enumerating the Package objects?

Thanks in advance,
Roy


#2

You're trying to iterate through a package object, instead of something containing package objects (note that you have nested loops)


#3

And definitely avoid names like x, k, and v


#4

class Package:
    def __init__(self, name, address):
        self.customer = name
        self.pickuplocation = address

customers = []

customers.append(Package("John Doe","350 5th Ave"))
customers.append(Package("Jane Doe","100 7th Ave"))
customers.append(Package("Joe Daniels","11 1st Ave"))

for index, item in enumerate(customers):
    print (index, item.customer, item.pickuplocation)

Output

0 John Doe 350 5th Ave
1 Jane Doe 100 7th Ave
2 Joe Daniels 11 1st Ave

I'm attempting to emulate an array of JavaScript class instances similar to the 'family' example in JS Objects. Obviously not the same in Python, so I'm having to wrap my head around this. Obviously the instances are not dictionaries. Am I correct?

For example, item above has attributes, but no keys() method.


#5

If you think of attributes as just a group, a container, then use dict, or let them refer to a dict of attributes as data.

If you want to list the attributes of an object then you're doing meta-programming, probably not what you want

If you want them to have string representations, implement __str__

If you want to print out attributes in a certain order that is different from their string representation then refer to each attribute by name


#6

'family' example?


#7

Things you'd create attributes for in an object would represent different things and not make much sense to loop through in most cases


#8

https://www.codecademy.com/en/courses/spencer-sandbox/4/1

Arrays of objects


#9

I'm beginning to find that, but still quite lost in the documentation.

Going to play around with this and see if I can figure it out. This is just for my own learning sake, nothing more. Probably getting in over my head.


#10

I'm guessing you meant something along these lines?

class Package:
    def __init__(self, customer, address):
        self.data = {}
        self.data['customer'] = customer
        self.data['address'] = address
        
customers = []

customers.append(Package("John Doe","350 5th Ave"))
customers.append(Package("Jane Doe","100 7th Ave"))
customers.append(Package("Joe Daniels","11 1st Ave"))

for index, item in enumerate(customers):
    print (item.data.keys())

Output

dict_keys(['customer', 'address'])
dict_keys(['customer', 'address'])
dict_keys(['customer', 'address'])

It is beginning to make sense, at least the implementing of a data group. Now it can be enumerated.

This is more what I was after...

class Package:
    def __init__(self, name, address):
        self.data = {}
        self.data['name'] = name
        self.data['address'] = address
        
customers = []

customers.append(Package("John Doe","350 5th Ave"))
customers.append(Package("Jane Doe","100 7th Ave"))
customers.append(Package("Joe Daniels","11 1st Ave"))

for index, item in enumerate(customers):
    print ("Customer # {0}".format(index))
    for key in item.data:
        print ("    {0}: {1}".format(key.capitalize(), item.data[key]))

Output

Customer # 0
    Name: John Doe
    Address: 350 5th Ave
Customer # 1
    Name: Jane Doe
    Address: 100 7th Ave
Customer # 2
    Name: Joe Daniels
    Address: 11 1st Ave

Thanks @ionatan, for steering me in the right direction. Feel free to criticize further. Always welcome.


#11

New question as regards a running static number...

class Package:
    order_number = 0
    def increment(self):
        Package.order_number += 1
        return Package.order_number
    def __init__(self, name, address):
        self.data = {}
        self.data['order_number'] = self.increment()
        self.data['name'] = name
        self.data['address'] = address

Please apprise me of any errors in my thinking, here. Thank you. The above code is giving a sequential number but I don't really know for certain if anyt rules are being broken. I struggled with it for a good long while to get it this far.


#12

#13

This is going to need some critical analysis...

class Package:
    order_number = 0
    def increment(self):
        Package.order_number += 1
        return Package.order_number
    def generate_label(self,data):
        for key in data:
            print ("    {0}: {1}".format(key.capitalize(), data[key]))
    def __init__(self, name, address):
        self.data = {}
        self.data['order_number'] = self.increment()
        self.data['name'] = name
        self.data['address'] = address

def find_customer(name):
    for index, item in enumerate(orders):
        i = index
        if item.data['name'] == name:
            return (i)
    return "Not found"

def search(cust_name):
    this = find_customer(cust_name)
    if this != 'Not found':
        orders[this].generate_label(orders[this].data)
    else: print (this)

orders = []

orders.append(Package("John Doe","350 5th Ave"))
orders.append(Package("Jane Doe","100 7th Ave"))
orders.append(Package("Joe Daniels","11 1st Ave"))

search("Jane Doe")

Output

Order_number: 2
Address: 100 7th Ave
Name: Jane Doe

#14

I think attributes like name and address should be attributes of the object, they aren't just data, they are part of the logic. But sure, it's equivalent for the most part

Linear search can be a problem at a large scale, it would be better to use dictionaries and register each package with a name and a destination

For id numbers you could use uuid4 which is very unlikely to generate the same id twice, eliminating the need for a counter

>>> from uuid import uuid4
>>> print(uuid4())
30b85606-9316-4df7-be20-8c85e2b5df4a

Plus, it looks fancy.. :smiley:

I'd use None, not a string


#15

Still need to implement the uuid() function, but I think I'm getting closer to what I was after in the beginning.

class Package:
    order_number = 0
    def increment(self):
        Package.order_number += 1
        return Package.order_number
    def generate_label(self):
        for key in self.__dict__:
            print ("    {0}: {1}".format(key.capitalize(), self.__dict__[key]))
    def __init__(self, name, address):
        self.name = name
        self.address = address
        self.data = {}
        self.data['order_number'] = self.increment()

def find_customer(name):
    for index, item in enumerate(orders):
        i = index
        if item.__dict__['name'] == name:
            return (i)
    return None

def search(cust_name):
    this = find_customer(cust_name)
    if this:
        orders[this].generate_label()
    else: print (this)

orders = []

orders.append(Package("John Doe","350 5th Ave"))
orders.append(Package("Jane Doe","100 7th Ave"))
orders.append(Package("Joe Daniels","11 1st Ave"))

search("Jane Doh")
search("Jane Doe")

Output

None
    Name: Jane Doe
    Address: 100 7th Ave
    Data: {'order_number': 2}

Still not sure about the search option though. Unclear how to set up the instances in an organized way that permits some duplication. That's why I selected the list object to contain the instance objects.


#16

Avoid using __dict__ directly, you're not meant to use any of the "magic names" directly.

You'll really have to rethink this line:

-

find_customer and search
do they really need to search through every package?
You could let customer names be keys to which packages are getting delivered to them instead.

-

Do you really need two names referring to the index?


#17

Here's my take, in two files. One for the module and another for using it to produce similar output to yours.
Main differences:
- Not treating class attributes like unordered pairs
- "Searches" are look-ups
- Broke out customers into its own class
- Can create instances of Database instead of one global

# File: demo.py
from mtf import Database


db = Database()
db.add_package('John Doe', '350 5th Ave')
db.add_package("Jane Doe", "100 7th Ave")
db.add_package("Joe Daniels", "11 1st Ave")


names = ['Jane Doh', 'Jane Doe']
for name in names:
    try:
        packages = db.get_packages_by_name(name)
        for package in packages:
            print(package)
    except KeyError:
        print('{} not in database.'.format(name))

Jane Doh not in database.
Name: Jane Doe
Address: 100 7th Ave
Order_id: 339f10b3-25d2-4f1f-afbe-f25304d89466

# File: mtf.py
from uuid import uuid4


class DatabaseKeyError(KeyError):
    pass


class Customer:
    def __init__(self, name):
        self.name = name
        self.packages = []

    def __str__(self):
        return self.name


class Package:
    def __init__(self, customer, address):
        self.customer = customer
        self.address = address
        self.order_id = uuid4()

    def __str__(self):
        return "Name: {customer}\nAddress: {address}\nOrder_id: {order_id}" \
               .format_map(vars(self))


class Database:
    def __init__(self):
        self.customers = {}
        self.packages = {}

    def add_package(self, name, address):
        # Add customer to database if it's new
        if name not in self.customers:
            self.customers[name] = Customer(name)
        customer = self.customers[name]
        # Associate package with customer object
        package = Package(customer, address)
        customer.packages.append(package)
        # And also make it available for look-up by order-id
        self.packages[package.order_id] = package

    def get_package_by_id(self, order_id):
        return self.packages[order_id]

    def get_packages_by_name(self, name):
        if name not in self.customers:
            raise DatabaseKeyError('{} is not a known customer'.format(name))
        return self.customers[name].packages

#18

Expertly written and presented. Thank you! There is much to learn from this. I'll be studying and modeling until I can cement the many concepts represented in this implementation.


#19

Don't read too much into it, I'm just rebelling against the looping-through-attributes and linear search. Other than that there's no plan what-so-ever there

To take it further one would need to consider what operations need to be supported and how well they need to perform.
And, it really should leverage some actual database backend, unless it's just meant to support operations for some algorithm.


#20

Yes, I had my doubts. Will dig some more.

For some reason, Python wouldn't let me return index so I fudged it. Might have been my implementation...

Wondering if I should keep ironing out wrinkles in the above code, or abandon it. Seems a waste to get this far with many in's and out's yet to uncover. Hope you won't mind if I pose more questions in this regard at some later juncture.