0

I have two different objects. One of them wraps N number of the other type objects within a list or tuple attribute. Let's say students in classrooms:

class Student:
    def __init__(self, name):
        self.name = name

class ClassRoom:
    def __init__(self, students):
        self.students = students

Of course, we have heaps of Student as well as ClassRoom instances:

john, sam = Student('John'), Student('Sam')
patrick, michael, bill = Student('Patrick'), Student('Michael'), Student('Bill')
klass1 = ClassRoom([john, sam])
klass2 = ClassRoom([patrick, michael, bill])

Just consider that each student is unique by their name and you cannot reach to a student's classroom by reference as such:

sam.get_classroom() # Student class doesn't support this :(

And we have a helper function that does the job:

def get_classroom_by_student(klasses, student_name):
    for klass in klasses:
        for student in klass.students:
            if student.name==student_name:
                return klass
                # Or yield if a student belongs to more than one class

sams_class = get_classroom_by_student([klass1, klass2], 'Sam')
bills_class = get_classroom_by_student([klass1, klass2], 'Bill')

Since "Flat is better than nested", how do I create an efficient generator or is there some pythonic way to implement this helper function?

4
  • IIRC, you can't both return and yield in the same function - it has to be a generator or not. Commented Sep 28, 2012 at 19:28
  • Well, I said "or", not "and". Thanks anyway. Commented Sep 28, 2012 at 19:31
  • Sounds like a good candidate for a database if this functionality and/or its performance is important to you. Commented Sep 28, 2012 at 19:41
  • yeah, it's not a database orm or something like that for me. but i do have -heaps- of klass instances that i need to find (return) the first found, which should be the only one in my case. I've added the yielding option for the people who may need.. Commented Sep 28, 2012 at 19:46

3 Answers 3

1

Assuming you do not want to change your data model, you could rewrite your function like this:

def get_classroom_by_student(klasses, student_name):
    for klass in klasses:
        for student in klass.students:
            if student.name==student_name:
                yield klass

This is more of less equivalent to

def get_classroom_by_student(klasses, student_name):
    combinations = []
    for klass in klasses:
        for student in klass.students:
            if student.name==student_name:
                combinations.append(klass)
    return combinations

However, cleaner is the following:

def get_classroom_by_student(klasses, student):
    for klass in klasses:
        for s in klass.students:
            if s is student:
                yield klass

This you can rewrite using a nested list comprehension:

def get_classroom_by_student(klasses, student):
    return [klass for klass in klasses for s in klass.students if s is student]

Or even shorter

def get_classroom_by_student(klasses, student):
    return [klass for klass in klasses if student in klass.students]
Sign up to request clarification or add additional context in comments.

2 Comments

just to note that you've edited and added comprehensions after @thg435's answer, even though your simplification looks closer to the need.
And, get_classroom_by_student([klass1, klass2], john) does not return a klass, but a generator.
0

How about this:

class Student:
    def __init__(self, name):
        self.name = name

class ClassRoom:
    def __init__(self, students):
        self.students = students

john, sam = Student('John'), Student('Sam')
patrick, michael, bill = Student('Patrick'), Student('Michael'), Student('Bill')

klass1 = ClassRoom([john, sam])
klass2 = ClassRoom([patrick, michael, bill])


def where_is(student, klasses):
    return next((x for x in klasses if student in x.students), None)

assert klass1 is where_is(john, [klass1, klass2])
assert klass2 is where_is(patrick, [klass1, klass2])

nobody = Student('foo')
assert None is where_is(nobody, [klass1, klass2])

For the yielding version just omit next and return the generator:

def where_is(student, klasses):
    return (x for x in klasses if student in x.students)

for klass in where_is(john, [klass1, klass2]):
    print klass

1 Comment

this is bloody pythonic and exactly what i needed. do you know how to do yielding version of that as well? just for the records and the people may need.. thanks a lot!
0

So this doesn't have as much nesting. Essentially we build a dictionary, m, that maps each student to its classroom. It isn't that readable or clean in my opinion.

You might want to build the dictionary once and then just use that to look up student classes. I highly recommend checking out models in Django. They do what you are looking for in a much cleaner way. What's great about Django is in your case if you put a foreign key relationship on classroom to student, it will automatically add methods to the student object to let you look up their class rooms. So it does all the work for you.

from itertools import chain

def flatten(items):
    return list(chain.from_iterable(items))

def get_classroom_by_student(klasses, student_name):
    m = flatten([dict.fromkeys(k.students, k).items() for k in klasses])
    m = dict([(s.name, c) for s,c in m])
    return m[student_name]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.