0

I want to get all objects which age is less than 18. How can easily do that? Should I had to pass all objects to get_nonage to get it?

class People:
    def __init__(self, uid, age):
        self.uid = uid
        self.age = age

    def get_nonage(self):
        # print nonage here which age<18
        # expected output: {1: 11, 3: 15}
        pass

p1 = People(1, 11)
p2 = People(2, 20)
p3 = People(3, 15)

...

People.get_nonage()

ggorlen
  • 44,755
  • 7
  • 76
  • 106
  • 1
    First, `get_nonage` should probably not be an instance method. Second, yes, you will have to pass it all of the People object if you want it to search all of the People objects. But that implies that you should be storing them in a list, instead of in a bunch of separate variables. Then you just pass the list. – abarnert Jul 27 '18 at 06:19
  • Can I just know the class is People, then use some method of People to directly get all data of its objects without know how many objects I have, possible? –  Jul 27 '18 at 06:21
  • 1
    Your design seems flawed here. Firstly, `People` is really `Person`. This is an important distinction. Once you create a person, you'll put them into an array (list) called `people`. Then, reducing/filtration/mapping operations are simple, in your case you're filtering on age: `[p for p in people if p.age < 18]` – ggorlen Jul 27 '18 at 06:24
  • Once you create a person, you'll put them into an array called people, what this mean? –  Jul 27 '18 at 06:25
  • Where I add the array people? –  Jul 27 '18 at 06:25
  • 1
    I'll answer the question – ggorlen Jul 27 '18 at 06:26
  • Possible duplicate of [Return the sum of values returned by another method of all the instances of that class?](https://stackoverflow.com/questions/51494587/return-the-sum-of-values-returned-by-another-method-of-all-the-instances-of-that) –  Jul 27 '18 at 07:18

2 Answers2

2

Your design seems flawed here. Firstly, People is really Person. This is an important distinction, because the class name should match the data and functions it's built from. In your case, groups of people don't have a single age and id but a person does. Once you create a person, you'll put them into a list called people. Then, reduction/filtration/mapping operations such as the one you request are simple.

class Person:
    def __init__(self, uid, age):
        self.uid = uid
        self.age = age

    def __repr__(self):
        return f"Person (id: {self.uid}, age: {self.age})"

if __name__ == "__main__":
    people = [
        Person(1, 11),
        Person(2, 20),
        Person(3, 15)
    ]

    underage_people = [p for p in people if p.age < 18]
    print(underage_people)

Output:

[Person (id: 1, age: 11), Person (id: 3, age: 15)]

If you want, you can put this list comprehension in a static class function as in atline's answer, but it doesn't seem appropriate to me. Such a method should belong to some other class that manages people, such as, say, a VoterRegistration application class that needs to determine who isn't eligible to vote. Or it could be in a People class that collects Persons in some way; either way, I think it's important to clearly establish what data and relationships you're modelling with classes and objects here. In both cases, the People class would have a member list of Person objects and a variety of functions to manipulate and report on that data, such as get_underage() (as written above in the list comp) or get_median_age(), for example. Here's a concrete sketch of what I'm talking about:

class Person:
    def __init__(self, uid, age):
        self.uid = uid
        self.age = age

    def __lt__(self, other):
        return self.age < other.age

    def __repr__(self):
        return f"Person (id: {self.uid}, age: {self.age})"

class People:
    def __init__(self, people):
        self.people = people

    def get_underage(self):
        return [p for p in self.people if p.age < 18]

    def get_average_age(self):
        return sum([p.age for p in self.people]) / len(self.people)

    def get_oldest(self):
        return max(self.people)
        
    def get_youngest(self):
        return min(self.people)
        
if __name__ == "__main__":
    people = People([
        Person(1, 11),
        Person(2, 20),
        Person(3, 15)
    ])

    print(f"Underage people: {people.get_underage()}")
    print(f"Average age: {people.get_average_age()}")
    print(f"Oldest: {people.get_oldest()}")
    print(f"Youngest: {people.get_youngest()}")

Output:

Underage people: [Person (id: 1, age: 11), Person (id: 3, age: 15)]
Average age: 15.333333333333334
Oldest: Person (id: 2, age: 20)
Youngest: Person (id: 1, age: 11)
ggorlen
  • 44,755
  • 7
  • 76
  • 106
1

In order to search all of the People objects, you have to have all of the People objects.

But that implies you shouldn't have them in a bunch of separate variables in the first place, but in some collection, like a list.

Then, get_nonage can take that list.

While we're at it, there's no reason for get_nonage to be a method. It's not something that a People instance does, it's something you do to a bunch of People instances.

So:

class People:
    # ...

def get_nonage(people):
    nonage = {person.uid: person.age for person in people if person.age<18}
    print(nonage)

people = [
    People(1, 11),
    People(2, 20),
    People(3, 35)
]

get_nonage(people)

In a comment, you ask:

Can I just know the class is People, then use some method of People to directly get all data of its objects

What method would that be? You haven't written one. A class normally doesn't know all of its instances.

Of course if you really want it to, you can make a class record its instances in a class attribute. (A normal instance, like age, has a separate value for each instance. A class attribute has a single value shared by all instances.)

For example:

class People:
    all_people = []

    def __init__(self, uid, age):
        self.uid = uid
        self.age = age
        self.all_people.append(self)

If you do that, then you can make get_nonage into a class method—that is, a method meant to be called on the class itself, rather than on an instance. A class method gets the class, cls, as its first parameter, instead of an instance, self, and it can only access class attributes, like the all_people we just created above.

    @classmethod 
    def get_nonage(cls):
        nonage = {person.uid: person.age for person in cls.all_people if person.age<18}
        print(nonage)

And now, you can call it, without having to pass it anything, because it already knows everything:

people = [
    People(1, 11),
    People(2, 20),
    People(3, 35)
]

People.get_nonage()

However, this is usually not a great design. For example, what if you wanted to delete a person? Previously, you'd just remove them from the list (or, in your original version, reassign p3 to something else). But then People.all_people won't know you did that; that person will still be there, and still be counted, even though you no longer have any way to access them.

abarnert
  • 354,177
  • 51
  • 601
  • 671