I am running into the following problem:
class Student():
def __init__(self,firstname, lastname, **kwargs):
self.firstname = firstname
self.lastname = lastname
student1 = Student("John" ,"Johnson" , {"Nationality": "Middle-Earth" , "Sports": ["volleyball","skiing"]})
I want to be able to create students that all have a first and a last name, but only some have information about nationality or sports. (Actually I am experimenting with reconstructing objects from json files.)
So I want to turn kwarg key into instance variable names, like this pseudo-code
for key,value in kwargs:
self.key = value
the above is pseude-code, but is there a way to do this in correct python? in other words: derive instance variable names from kwargs rather than all predefined.
I know this won't result in errorproof code etc..., but I want to know if it can be done.
You can use this in the __init__ method:
for k, v in kwargs.items():
setattr(self, k, v)
However, this is not recommended for code than needs to be reusable. If there are typos in the keyword parameters, you won't catch them. Code-checking tools such as pylint will not recognize class attributes initialized like that. You can use the following pattern (especially if there are many kewyord arguments):
class Student:
def __init__(self, firstname, lastname, **kwargs):
self.firstname = str(firstname)
self.lastname = str(lastname)
self.nationality = None
self.sports = None
for k, v in kwargs.items():
if k in self.__dict__:
setattr(self, k, v)
else:
raise KeyError(k)
# this works
s=Student('John', 'Doe', sports=['socker'])
# this will give an error
s=Student('John', 'Doe', sprots=['socker'])
You need to pass unpacked kwargs to Student and use setattr(object, name, value) to set the values:
class Student():
def __init__(self,firstname, lastname, **kwargs):
self.firstname = firstname
self.lastname = lastname
for (k,v) in kwargs.items():
setattr(self, k, v)
student1 = Student("John" ,"Johnson" , **{"Nationality": "Middle-Earth" , "Sports": ["volleyball","skiing"]})
print(student1.__dict__)
Output:
{'lastname': 'Johnson', 'Sports': ['volleyball', 'skiing'], 'firstname': 'John', 'Nationality': 'Middle-Earth'}
I recommend stating up front what attributes an instance can have, and using sentinel values like None to indicate the lack of a "real" value over eliminating the attribute altogether. For example:
class Student():
def __init__(self,firstname, lastname, nationality=None, sports=None):
self.firstname = firstname
self.lastname = lastname
self.nationality = nationality
self.sports = [] if sports is None else sports
Then
student1 = Student("John" ,"Johnson" , "Middle-Earth" , ["volleyball","skiing"])
student2 = Student("Bob", "Smith", nationality="Australia") # No sports
student3 = Student("Alice", "Doe", sports=["hockey"]) # No nationality
If you want to parse JSON which may have key names that don't match your parameter names, define a class method to do the processing to get appropriate arguments. For example,
#classmethod
def from_dict(cls, data):
firstname = data["FirstName"]
lastname = data["some last name"]
nationality = data.get("Nationality")
sports = data.get("Sports", [])
return cls(firstname, lastname, nationality, sports)
Then
student = Student.from_dict({'FirstName': 'John', 'some_last_name': 'Doe',})
Related
I am sorry if this was answered before, but I could not find any answer for this problem at all.
Let's say I have this class and list of objects:
def Person:
def __init__(self, name, country, age):
self.name = name
self.country = country
self.age = age
persons = [Person('Tom', 'USA', 20), Person('Matt', 'UK', 19), Person('Matt', 'USA', 20)]
Now I would like the user to search for a person by entering any combination of attribute values and I want to return the objects that have all these values exclusively. For example, if the user enters: 'Matt', 'USA' and no age, I want the program to return the third person only who's Matt and is from the USA and not return all three objects because all of them have some of the entered combination of attribute values.
My implementation currently uses an if statement with the or operator which would return all the objects since the usage of or would return all the objects if one statement is True, which is what I am trying to solve.
Thanks in advance.
You can use a list comprehension for the task. And the if condition should check if the value is None else check in the list.
class Person:
def __init__(self, name, country, age):
self.name = name
self.country = country
self.age = age
def __repr__(self):
return "[{},{},{}]".format(name, country, str(age))
persons = [Person('Tom', 'USA', 20), Person('Matt', 'UK', 19), Person('Matt', 'USA', 20)]
name = "Matt"
country = "USA"
age = None
result = [
p for p in persons
if (name == None or p.name == name) and
(country == None or p.country == country) and
(age == None or p.age == age)
]
print(result) #[[Matt,USA,None]]
I have a class with a constructor that receives several arguments. These arguments I get from an external file, which is a list of dictionaries.
My problem is that some dictionaries have missing fields. If a field is missing, I want the constructor to simply receive None instead. But as the code is written now, it exits on an exception.
This is a simple illustration of the situation
class Person()
def __init__(self, id, age, name, gender):
self.id = id
self.age = age
self.name = name
self.gender = gender
people_list = pickle.load(open("some_file.p", "rb"))
first_person = people_list[0] # this is a dictionary {'id': '1234', 'age': 27, 'name': 'robert'}
Person(first_person['id'], first_person['age'], first_person['name'], first_person['gender'])
As the code is written right now, I get an exception that 'gender' does not exist as a key in the dictionary, which is true. I want any missing field to gets passed a None value, but without doing a billion ifs or try .. excepts. Is there a good way to do this? Obviously without still incurring an exception.
In general, you can get a value from a dict with a default if it doesn't exist with:
d = {'a': 1}
print(d.get('b')) # prints None
But in your case, this would be more appropriate:
class Person():
# the defaults are defined in the constructor
def __init__(self, id, age=None, name=None, gender=None):
self.id = id
self.age = age
self.name = name
self.gender = gender
people_list = pickle.load(open("some_file.p", "rb"))
for person in people_list:
Person(**person) # assuming they at least all have an 'id'
Of course, this only works if the keys of the dictionaries match the names of the parameters exactly.
The ** operator 'unpacks' the dictionary, so this:
d = {'a': 1, 'b': 2}
f(**d)
Is the same as:
f(a=1, b=2)
I have 2 classes:
class Parent(object):
def __init__(self, id, name):
self.id = id
self.name = name
self.__parent_vars = ['id', 'name'] # make a copy
def print_values(self):
res = {}
for el in self.__parent_vars:
res[el] = vars(self)[el]
return res
class Child(Parent):
def __init__(self, id, name, last_name, age):
Parent.__init__(self, id, name)
self.last_name = last_name
self.age = age
What I want to do - is to get from Child parameters of Parent class. I made it using additional variable and it works, but I need more elegant solution without additional variable. I need it for pickle class. If I create additional variable it breaks my Schemas in a big project.
I try to find something like this:
c = Child(12,"Foo","whatever",34)
vars(c.super())
with expected output:
{'id': 12, 'name': 'Foo'}
I found this question: Get attributibutes in only base class (Python) but it has significant difference of mine, so I can't use that solution.
I am afraid you cannot easily. In Python, classes only carry methods and static attributes. Non
static attributes are commonly stored in the __dict__ attribute of the objects. That means that except in special cases, you cannot easily know which attributes where assigned in a parent class method, in a child class method or even outside any method.
I can only imagine a meta_class that would instrument the __init__ method to store which attributes were changed during its call:
import collections
import functools
import inspect
class Meta_attr(type):
init_func = {}
attrs = collections.defaultdict(set)
def __new__(cls, name, bases, namespace, **kwds):
c = type.__new__(cls, name, bases, namespace, **kwds)
cls.init_func[c] = c.__init__
#functools.wraps(c.__init__)
def init(self, *args, **kwargs):
before = set(self.__dict__.keys())
cls.init_func[c](self, *args, **kwargs)
after = set(self.__dict__.keys())
cls.attrs[c].update(after.difference(before))
init.__signature__ = inspect.signature(c.__init__)
c.__init__ = init
return c
class Parent(object, metaclass=Meta_attr):
def __init__(self, id, name):
self.id = id
self.name = name
def print_values(self):
res = {}
for el in Meta_attr.attrs[Parent]:
res[el] = vars(self)[el]
return res
class Child(Parent):
def __init__(self, id, name, last_name, age):
Parent.__init__(self, id, name)
self.last_name = last_name
self.age = age
It gives:
>>> c = Child(1,"a","b", 20)
>>> c.print_values()
{'id': 1, 'name': 'a'}
BEWARE: if an attribute is set outside of the __init__ method, it will not be registered by this meta class...
I found one more solution. It looks simplier, so I used it. It based on using Marshmallow Schemas.
Also It is more usiful in my project because I already used marshmallow Schemas.
The idea: I create 2 classes and 2 different Schemas. And can serialize bigger class (Child) using just Parent schema:
Code
Define 2 classes:
class Parent(object):
def __init__(self):
self.id = 'ID'
self.name = 'some_name'
class Child(Parent):
def __init__(self):
Parent.__init__(self)
self.last_name = 'last_name'
self.age = 15
from marshmallow import Schema, fields, EXCLUDE
Define 2 Schemas:
class ParentSchema(Schema):
ba = Parent()
id = fields.Str(missing=ba.id)
name = fields.Str(missing=ba.name)
class ChildSchema(Schema):
ba = Child()
id = fields.Str(missing=ba.id)
name = fields.Str(missing=ba.name)
last_name = fields.Str(missing=ba.last_name)
age = fields.Int(missing=ba.age)
use it to get only Parent attributes for Child object:
user_data = {'id':'IDIDID', 'name':'add_name', 'last_name':'FAMILIYA'}
res = ParentSchema().load(user_data, unknown=EXCLUDE)
# output:
res
{'name': 'add_name', 'id': 'IDIDID'}
I'm learning and experimenting with Python. How can I pass the contact_list in my second function print_contacts so that it can print the name from the contact_list? I'm sure I am doing something wrong, can anyone please explain why is it so?
class Contact(object):
contact_list = []
def __init__(self, name, email):
self.name = name
self.email = email
return Contact.contact_list.append(self)
# How to pass contact_list to below function?
def print_contacts(contact_list):
for contact in contact_list:
print(contact.name)
To me it doesn't make any sense to have a Contact object also own a contact_list attribute, and even less if it's class-wide instead of instanced. I would do this instead:
class Contact(object):
def __init__(self, name, email):
self.name = name
self.email = email
def __str__(self):
return f"{self.name} <{self.email}>"
# or "{} <{}>".format(self.name, self.email) in older versions of
# python that don't include formatted strings
contacts = []
def print_contacts(contacts: "list of contacts") -> None:
for c in contacts:
print(c)
adam = Contact("Adam Smith", "adam#email.com")
contacts.append(adam)
bob = Contact("Bob Jones", "bob#email.com")
contacts.append(bob)
charlie = Contact("Charlie Doe", "charlie#email.com")
contacts.append(charlie)
print_contacts(contacts)
# Adam Smith <adam#email.com>
# Bob Jones <bob#email.com>
# Charlie Doe <charlie#email.com>
Or alternatively, model an AddressBook that knows how to create Contact objects and display them all.
class AddressBook(list):
def add_contact(self, *args, **kwargs):
new_contact = Contact(*args, **kwargs)
self.append(new_contact)
def display_contacts(self):
for contact in self:
print(contact)
contacts = AddressBook()
contacts.add_contact("Adam Smith", "adam#email.com")
contacts.add_contact("Bob Jones", "bob#email.com")
contacts.add_contact("Charlie Doe", "charlie#email.com")
contacts.display_contacts()
class Contact(object):
contact_list = []
def __init__(self, name, email):
self.name = name
self.email = email
Contact.contact_list.append(self)
#classmethod
def print_contacts(cls):
for contact in cls.contact_list:
print(contact.name)
cont1 = Contact("John", "john#john.com")
cont2 = Contact("Mary", "mary#mary.com")
Contact.print_contacts()
will print
>>John
Mary
To answer your question as to why your code currently doesn't work: first, your init method doesn't need a return call, init is called upon object creation to establish object variables and typically doesn't need to return anything (especially in this case since .append() doesn't provide anything to return). Second, a classmethod seems better suited for what you are trying to do with the second method, and you can read more about that here: What are Class methods in Python for?
I am a newbie to Python. I need to create a simple student class which includes first name, last name, id, and a dictionary which maps course name to its grade.
class Student:
def __init__(self, firstName, lastName, id, _____ (dictionary values)):
self._firstName = firstName;
self._lastName = lastName;
self._id = id;
self.
My question is how can I initizalize the dictionary values inside the constructor?
For example, let`s say I would like to add 3 course to grade mappings:
"math: 100"
"bio: 90"
"history: 80"
For example:
student1 = Student("Edward", "Gates", "0456789", math: 100, bio: 90, history: 80)
The last 3 values should go into the dictionary.
Since the number of key-value which can be part of the dictionary can vary, what should I write in the constructor parameter signature?
I want to send all the student values when I call the constructor...
If you are looking to add a dictionary Mathias' answer suffices with the key word arguments in python.
However, if you wish to add object variables from the key word arguments, you require setattr
For example, if you want something like this:
student1 = Student("Edward", "Gates", "0456789", {'math': 100, 'bio': 90, 'history': 80})
print student1.math #prints 100
print student1.bio #prints 90
Then this will do the trick:
class Student(object):
def __init__(self, first_name, last_name, id, **kwargs):
self.first_name = first_name
self.last_name = last_name
self.id = id
for key, value in kwargs.iteritems():
setattr(self, key, value)
student1 = Student("Edward", "Gates", "0456789", {'math': 100, 'bio': 90, 'history': 80})
Note that **kwargs will unpack only something like dictionary or tuple of tuples. If you wish to send a list of values without keys, you should use *args. Check here to know more.
Python collects all keyword arguments for you.
class Student:
def __init__(self, firstName, lastName, id, **kwargs):
self._firstName = firstName;
self._lastName = lastName;
self._id = id;
self. _grades = kwargs
Here is an excellent explanation about kwargs in python
Why not send the complete grades dictionary to the your class and store it in a variable.
(Also please note that in Python there is no semicolon at the end of the line)
class Student:
def __init__(self, firstName, lastName, id, grade_dict):
self._firstName = firstName
self._lastName = lastName
self._id = id
self._grades = grade_dict
def get_grades(self):
return self._grades
and then when you want to initialize and use the grades:
student1 = Student("Edward", "Gates", "0456789", {'math': 100, 'bio': 90, 'history': 80})
grades = student1.get_grades()
for key, value in grades.items():
print 'Marks in {}: {}'.format(key, str(value))
Which prints:
Marks in bio: 90
Marks in math: 100
Marks in history: 80
You can try something like:
student = Student("Edward", "Gates", "0456789", {"math": 100, "bio": 90, "history": 80})
And inside your constructor you can copy these values to a new dictionary:
class Student:
def __init__(self, firstName, lastName, id, grades):
self._firstName = firstName;
self._lastName = lastName;
self._id = id;
self._grades = grades.copy()
Notice that we're copying the dictionary to a new attribute because we want to avoid keeping a reference.
First, make sure to remove the semicolon ; from your code - it won't compile!
Second, I believe you're looking to do something like:
class Student:
def __init__(self, first_name, last_name, _id, **courses):
self._first_name = first_name
self._last_name = last_name
self._id = _id
self.courses = courses
def print_student(self):
print self._first_name
print self._last_name
print self._id
for key in self.courses:
print key, self.courses[key]
courses = {'math': 100, 'bio': 90, 'history': 80}
s = Student("John", "Smith", 5, **courses)
s.print_student()
OUTPUT
John
Smith
5
bio 90
math 100
history 80