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()
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)
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.
Related
Say I have a class of people
class person()
def __init__(self, fname:str, lname:str):
self.fname = fname
self.lname = lname
How do I say that I'm expecting a list of person?
class group()
def __init__(self, groupName:str, people:person|person[]):
self.groupName = groupName
self.people = people
Could I use ... people:person|list[person]): ?
A list of person objects is list[person] (or typing.List[person], prior to Python 3.9).
Yes, you can use people: list[person] to indicate that the people attribute is expected to be a list of person objects. The | symbol is not used in this case, because you only want to specify a single type, which is a list of person objects.
class group():
def __init__(self, groupName:str, people:list[person]):
self.groupName = groupName
self.people = people
When you use the | symbol, you are indicating that the attribute can store either one type or another. For example, person | list[person] would indicate that the attribute can store either a single person object or a list of person objects.
You can do
class Person:
def __init__(self, fname: str, lname: str):
self._fname = fname
self._lname = lname
class Group:
def __init__(self, name: str, people: Person|list[Person]):
self._name = name
self._people = people
and mypy will be happy. That said, it's probably a better design to simply have people: list[Person] (i.e. only a list is accepted), and have a 1-item list if only one person is in the group. Otherwise, you may have to implement different member functions for different types (or a bunch of if branches) if those functions use self._people.
Topic Closed
So I'm learning OOP in python and wanted to test my knowledge. That's what i did
class Student:
def cantBeStudent():
print('You don\' classify as a stududent')
def __init__(self, age, education):
self.age = age
self.education = education
if (self.age < 16) or (self.education < 3):
cantBeStudent()
student1 = Student(age=18, education=2)
I get name_error when i try to call cantBeStudent(). It says that cantBeStudent is not defined. I can't find my answer on google so I came here.
Edit: Also when i comment out whole cantBeStudent i get SyntaxError on def init
You need to add self to the method invocation and declaration:
class Student:
def cantBeStudent(self): # need self
print('You don\' classify as a stududent')
def __init__(self, age, education):
self.age = age
self.education = education
if (self.age < 16) or (self.education < 3):
self.cantBeStudent() # need self
student1 = Student(age=18, education=2)
OR
You need to invoke cantBeStudent as a static method like so:
class Student:
def cantBeStudent(): # no self as first argument, therefore static method
print('You don\' classify as a stududent')
def __init__(self, age, education):
self.age = age
self.education = education
if (self.age < 16) or (self.education < 3):
Student.cantBeStudent() # because declaration has no self,
# cantBeStudent belongs to entire Student class
student1 = Student(age=18, education=2)
When you construct a class, methods that you define must take the instance as the first argument. The class instance is referred to as self (though you could call it anything you wanted):
class X:
def __init__(self, number):
self.number = number
def add_number(self, arg):
self.number += arg
You see this when you define __init__. All other functions* work this way as well. When you call them like
instance = X(1)
instance.add_number(3)
It's analogous to doing:
instance = X(1)
X.add_number(instance, 3)
It's just calling the method against the instance will automatically pass self for you. When you call that method inside the instance, you need to specify the instance you are calling against, it's just this is called self instead of instance:
class X:
~snip~
def add_number(self, arg):
self.number += arg
def increment_number(self):
# note the self.<method>
self.add_number(1)
Again, this would be identical to the call:
instance = X(1)
X.increment_number(instance)
Because the instance gets passed in so that it can be called with the appropriate method
* All other functions that are not decorated with #staticmethod or #classmethod
You should provide self to any function that you want it to be counted as an object's method. If you do not want to provide self that function could be a static function (which means it does not rely on the type of the object itself). Then, you need to clarify that function, by #staticmethod decorator.
You missed self parameter on cantBeStudent method and when call it from contructor, it should be self.canBeStudent. Like this:
class Student:
def cantBeStudent(self):
print('You don\' classify as a stududent')
def __init__(self, age, education):
self.age = age
self.education = education
if (self.age < 16) or (self.education < 3):
self.cantBeStudent()
The purpose of self in Python: What is the purpose of the word 'self', in Python?
Add self on the function cantBeStudent
class Student:
def cantBeStudent(self):
print("You don't classify as a stududent")
def __init__(self, age, education):
self.age = age
self.education = education
if (self.age < 16) or (self.education < 3):
self.cantBeStudent()
That happens because you need to specify that the function is contained in the same class of 'self'
If you would done in this way it would had worked:
def cantBeStudent():
print("You don't classify as a stududent")
class Student:
def __init__(self, age, education):
self.age = age
self.education = education
if (self.age < 16) or (self.education < 3):
cantBeStudent()
The main difference is that in the first case, the function is inside the class Student, in the other case the function is out of the class, so don't need the 'self'
I'm completely new to Python so I may not be asking this in the right way and I have tried to find an answer (unsuccessfully, obviously).
I am trying to set up a class and then to find all instances of the class that meet a certain criterion. I don't know if a class is the right way to go about this so open to all advice (even if it takes me away from classes).
For a simple example (see code below) if I wanted to find all Employee's who are over 30 would the right approach be to make a class method? In reality you'd have DOB rather than age but for simplicity I've gone with age. I'd also want some sort of variable so that I could change 30 to let's say 50.
class Employee:
def __init__(self, name, age):
self.name = name
self.age = age
e1 = Employee("John", 36)
e2 = Employee("Sally", 21)
e3 = Employee("Jamie", 53)
What is the "right" way or efficient way to achieve this? For this example above for ages above 30 I'd want the e1 and e3 instances. Any help would be appreciated!
You would typically have your instances in some sort of collection like a list rather than individual variables. You could then filter your list or use a list comprehension to get the elements in the list you want. Here's an example with a list comprehension:
class Employee:
def __init__(self, name, age):
self.name = name
self.age = age
employees = [
Employee("John", 36),
Employee("Sally", 21),
Employee("Jamie", 53)
]
over_thirty = [e for e in employees if e.age > 30]
for e in over_thirty:
print(e.name)
prints:
John
Jamie
You can avoid the extra list over_thirty and iterate directly over the filter results for the same result:
for e in filter(lambda e: e.age > 30, employees):
print(e.name)
Or also modify the class:
class Employee:
def __init__(self, name, age):
self.name = name
self.age = age
self.age_over_30 = self.age > 30
l = [Employee("John", 36), Employee("Sally", 21), Employee("Jamie", 53)]
print([i.name for i in l if i.age_over_30])
Or if you would modify the age attribute, make the class:
class Employee:
def __init__(self, name, age):
self.name = name
self.age = age
#property
def age_over_30(self):
return self.age > 30
I wondered if its better to:
Pass Object1 into Object2 for use
or
Instantiate Object1 from Object2
I could do either, but I am wondering what is better practice, or where i could get caught up.
These are rather rudimentary examples, and of course in the second example, i could just use the list. The actual code I am using, i need to use a lot of Object1 values to do things in Object2.
Example 1:
class Object1:
def __init__(self, person):
self.id = person[0]
self.name = person[1]
self.age = person[2]
class Object2:
def __init__(self, object1):
self.object1 = object1
retirement_age = self.object1.age + 25
print(self.object1.id)
print(self.object1.name)
print(retirement_age)
person = [1, 'Ryan', 30]
o = Object1(person)
r = Object2(o)
Example 2:
class Object1:
def __init__(self, person):
self.id = person[0]
self.name = person[1]
self.age = person[2]
class Object2:
def __init__(self, person):
self.o = Object1(person)
retirement_age = self.o.age + 25
print(self.o.id)
print(self.o.name)
print(retirement_age)
person = [1, 'Ryan', 30]
r = Object2(person)
Well, it depends, and only you can decide.
In the first example, you can use o in many places throughout your program without creating the same object over and over again (just make sure you are not modifying it anywhere, see below why).
The second option encapsulates the fact that Object1 even exists.
You need to ask yourself a few questions:
Will you use Object1 instances anywhere else in the code base? Does it even make sense to have a "standalone" Object1 instance outside of Object2 instance?
Does Object2 need to know about Object1? Does it need to know how to construct it? should it even care?
Can Object2 instances simply contain the attributes of Object1 directly? If the answer is 'yes' then Object1 is redundant.
You have to be very careful with option 1.
If the object you pass through to Object2 is modified anywhere, the changes will affect all of the corresponding Object2 instances.
class Object1:
def __init__(self, person):
self.id = person[0]
self.name = person[1]
self.age = person[2]
class Object2:
def __init__(self, object1):
self.object1 = object1
retirement_age = self.object1.age + 25
print(self.object1.id)
print(self.object1.name)
print(retirement_age)
person = [1, 'Ryan', 30]
o = Object1(person)
r = Object2(o)
# modifying o.name will affect r.object1.name as well
o.name = 'John'
print(r.object1.name)
# 1
# Ryan
# 55
# John
It depends on the context.
You could use the first method as Object2 doesn't need any information about how to construct an Object1.
On the other hand, if you needed to use person in Object2, you should use the second method as it would be easier to use person instead of self.object1.id, especially if you had to use person many times.
Many of my classes look like the following class to represent accounts
class Account(object):
def __init__(self, first, last, age, id, balance):
self.first = first
self.last = last
self.age = age
self.id = id
self.balance = balance
def _info(self):
return self.first, self.last, self.age, self.id, self.balance
def __eq__(self, other):
return self._info == other._info()
def __hash__(self):
return hash((type(self), self.info()))
def ... # other methods follow
But really the only relevant information is the list of attributes I care about first, last, age, id, balance. Is there a standard method to define Python classes that follow this structure?
At first glance I thought of namedtuple but I'm not sure that that allows me to add additional methods after the fact. Really, I want something like the following
class Account(object):
attributes = "first last age id balance"
def ... # other methods
What is the best way of obtaining this?
Not sure how idiomatic it is, but the following satisfies your requirements:
class Slottable:
def __init__(self, *args):
for slot, arg in zip(self.slots.split(' '), args):
setattr(self, slot, arg)
def _info(self):
return tuple(getattr(self, attr) for attr in self.slots.split())
def __eq__(self, other):
return self._info() == other._info()
def __hash__(self):
return hash((type(self), self._info()))
class Account(Slottable):
slots = "first last age id balance"
def fullname(self):
return self.first + " " + self.last
matt = Account("Matthew", "Smith", 28, 666, 1E6)
john = Account("John", "Jones", 46, 667, 1E7)
d = {matt: 5, john: 6} # Hashable
print matt.fullname()
#=> "Matthew Smith"
print john.fullname()
#=> "John Jones"
print matt == matt, matt == john
#=> True False
matt.age = 29 # Happy birthday!
print matt.age
#=> 29
Here are some recipes you can try: override __setattr__, __dict__, __slots__ and/or init. Let us know what works for you.
Many libraries out there exist to cover this need: attrs, dataclasses, pydantic, ... and my new addition to this landscape, pyfields.
Choice will mainly depend on the features you need or do not need. pyfields focuses on fields definition and optional validation and conversion, without any constraint on your class. Fields that can be native become as fast as python native attributes can be, while fields requiring callbacks (validators/converters) are implemented using descriptors.
You can blend your own constructor with the
from pyfields import field, init_fields
class Account(object):
first = field(doc="first name")
last = field(doc="last name")
age = field(doc="the age in years")
id = field(doc="an identifier")
balance = field(doc="current balance in euros")
#init_fields
def __init__(self, msg):
print(msg)
a = Account("hello, world!", first="s", last="marie", age=135, id=0, balance=-200000)
print(vars(a))
yields
hello, world!
{'balance': -200000, 'id': 0, 'age': 135, 'last': 'marie', 'first': 's'}
As opposed to other, more "all in one" libraries, pyfields concentrates on the fields and the constructor only, with a "minimum viable product" spirit. So if you would also like dict representation and conversion, hash, equality and comparison, you should add them on top of your class using another library. I am currently developing a mixture lib providing mix-in classes for this, with the same philosophy of a la carte features - that you will be able to use with or without pyfields.
See pyfields documentation for details. Do not hesitate to provide feedback !