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)
Related
Is there a way to convert json in to an object with only required fields, such that extra fields are ignored, and if the required fields do not exits throw an error?
If an object's field matches exactly with json fields, we could use something like this:
import json
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
test_json = '{"name": "user", "age":"50"}'
test_dict = json.loads(test_json)
test_obj = Person(**test_dict)
However, I would want the code to silently ignore extra fields in json for example:
test_json = '{"name": "user", "age":"50", "hobby":"swimming"}'
And if required fields are missing, throw an error
test_json = '{"name": "user", "hobby":"swimming"}'
I know you can add checks in when initializing the obj from the dictionary. But there are many fields and the json can come from different places thus formatting could change, so I wonder if there is a library could help achieve the above?
In order to get the extra fields in the object, you can use keyworded arguments (kwargs). For instance, this code can take any number of arguments (larger than 2 since the name and age must be there).
class Person:
def __init__(self, name, age, **kwargs):
self.name = name
self.age = age
print(kwargs)
You can tinker around with this and see if you can get it to fully work as desired.
this code allows you to get only dictionary keys that match the names of your class attributes using the inspect module:
import inspect
import json
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
getting the required field in your class initialization, this would recognize that you will need a name and age variables in your class __init__ method:
argspec = inspect.getfullargspec(Person.__init__)
required = argspec.args
if argspec.defaults:
required = required[:-len(argspec.defaults)]
required.remove('self')
keeping only the names that match object attributes:
test_json = '{"name": "user", "age":"50", "foo": "bar", "bar": "baz"}'
test_dict = json.loads(test_json)
test_dict = {k:v for k, v in test_dict.items() if k in required}
initializing the object:
test_obj = Person(**test_dict)
You can you Pydantic and define your class like in the example bellow:
import json
from pydantic import BaseModel
class Person(BaseModel):
name: str
age: int
# Ignore the extra field
test_json_extra_field = '{"name": "user", "age":"50", "hobby":"swimming"}'
person_01 = Person(**json.loads(test_json_extra_field))
print(person_01)
# throw error because age is not in the json
test_json_no_required_field = '{"name": "user", "hobby":"swimming"}'
person_02 = Person(**json.loads(test_json_no_required_field))
print(person_02)
Pydantic BaseModel will ignore the extra field in test_json_extra_field and throw an error in test_json_no_required_field because age is not in the json info.
Suppose I have a python class like:
class User:
name = None
id = None
dob = None
def __init__(self, id):
self.id = id
Now I am doing something like this:
userObj = User(id=12) # suppose I don't have values for name and dob yet
## some code here and this code gives me name and dob data in dictionary, suppose a function call
user = get_user_data() # this returns the dictionary like {'name': 'John', 'dob': '1992-07-12'}
Now, the way to assign data to user object is userObj.name = user['name'] and userObj.dob = user['dob']. Suppose, User has 100 attributes. I will have to explicitly assign these attributes. Is there an efficient way in Python which I can use to assign the values from a dictionary to the corresponding attributes in the object? Like, name key in the dictionary is assigned to the name attribute in the object.
1. Modify the Class definition
class User():
def __init__(self, id):
self.data = {"id":id}
userObj = User(id=12)
2. Update the dict()
user = {"name":"Frank", "dob":"Whatever"} # Get the remaining data from elsewhere
userObj.data.update(user) # Update the dict in your userObj
print(userObj.data)
Here you go !
Instead of mapping a dict to the variable keys. You can use setattr to set variables in an object.
class User:
name = None
id = None
dob = None
def __init__(self, id):
self.id = id
def map_dict(self, user_info):
for k, v in user_info.items():
setattr(self, k, v)
Then for boiler code to use it.
userObj = User(id=12)
user_dict = {
'name': 'Bob',
'dob': '11-20-1993',
'something': 'blah'
}
userObj.map_dict(user_dict)
First, there is no need to predeclare properties in python.
class Foo:
bar: int # This actually creates a class member, not an instance member
...
If you want to add values to a class instance just use setattr()
d = {
'prop1': 'value1',
'prop2': 'value2',
'prop2': 'value2'
}
x = Foo()
for prop in d.keys():
setattr(x, prop, d[prop])
class User(dict):
def __init__(self, *args, **kwargs):
super(User, self).__init__(*args, **kwargs)
self.__dict__ = self
and then just get your dictionary and do:
userObj = User(dictionary)
EDIT:
user the function setattr() then
[setattr(userObj, key, item) for key,item in dict.items()]
In Case you REALLY need to
This solution is for the case, other solutions dont work for you and you cannot change your class.
Issue
In case you cannot modify your class in any way and you have a dictionary, that contains the information you want to put in your object, you can first get the custom members of your class by using the inspect module:
import inspect
import numpy as np
members = inspect.getmembers(User)
Extract your custom attributes from all members by:
allowed = ["__" not in a[0] for a in members]
and use numpy list comprehention for the extraction itself:
members = np.array(members)["__" not in a[0] for a in members]
Modify the user
So lets say you have the following user and dict and you want to change the users attributes to the values in the dictionary (behaviour for creating a new user is the same)
user = User(1)
dic = {"name":"test", "id": 2, "dob" : "any"}
then you simply use setattr():
for m in members:
setattr(user, m[0], dic[m[0]])
For sure there are better solutins, but this might come in handy in case other things dont work for you
Update
This solution uses the attribute definitions based on your class you use. So in case the dictionary has missing values, this solution might be helpful. Else Rashids solution will work well for you too
Let's say I have a data item person with two properties, name and age, such that;
person
name
age
I want to return this to a caller, but am unsure of what method to use. My ideas so far are however:
A dictionary for each person placed in a list -- Have tried, the syntax to perform were a little tedious, also I got AttributeError
A class with two properties -- I don't even know how to go about this, nor if it even works
My code is looking something like this at the moment:
persons = []
for person in people: # "people" fetched from an API
persons = {
"name": "Foo"
"age": "Bar"
}
return persons
# And then to access returned result
for person in persons:
print(person["name"]) # Gives AttributeError
# DoMoreStuff
First of all - the error you are not returning a list of dicts. Just a single dict. Instead of appending your persons to the list you created, you replace the list with your persons. So if you try to iterate over it, you in fact iterate over the keys. What you wanted is probably:
persons.append({
"name": "Foo"
"age": "Bar"
})
Second of all: to get a "class with two properties" I would recommend looking on namedtuple. https://docs.python.org/3/library/collections.html#collections.namedtuple
zefciu is correct and I would like to expand on his idea. First of all, before dealing with a list of persons, you need to know how to work with a single person. There are three ways to represent a person: a dictionary, a class, and a namedtuple.
Dictionary
Given a person name (John) and age (32), you can represent a person as:
person = {'name': 'John', 'age': 32 } # or
person = dict(name='John', age=32)
You can then access this person's name as person['name'] and age as person['age'].
Class
You can define a person class, along with an initializer as:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
Now, you can create and access a person object:
person = Person('John', 32) # or
person = Person(name='John', age=32)
print('Name:', person.name)
print('Age:', person.age)
namedtuple
namedtuple is part of the collections library, so you need to import it. Here is how to define it:
from collections import namedtuple
Person = namedtuple('Person', ['name', 'age'])
To use it:
person = Person('John', 32) # or
person = Person(name='John', age=32)
print('Name:', person.name) # like a class
print('Name:', person[0]) # like a tuple
Populate a List
persons = []
for person in people:
name = ... # extract name from person
age = ... # extract age
persons.append(dict(name=name, age=age)) # For dictionary
persons.append(Person(name=name, age=age)) # For class or namedtuple
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
I got this problem writing a little GUI lib that maps classes to simple table views. Every class member of a certain type = column, and order of columns is important. But...
class Person(object):
name = None
date_of_birth = None
nationality = None
gender = None
address = None
comment = None
for member in Person.__dict__.iteritems():
if not member[1]:
print member[0]
output:
comment
date_of_birth
name
address
gender
nationality
...
ugh, the oder got all mixed up...desired output:
name
date_of_birth
nationality
gender
address
comment
Is there a way to do it without maintaining additional OrderedDict() of columns?
It's possible in Python3, through the use of PEP3115 which allows you to override the dict type in the metaclass while the class is being constructed (eg. to use an OrderedDict which tracks the insertion order). Here's an implementation of this approach:
class OrderedMeta(type):
#classmethod
def __prepare__(metacls, name, bases):
return OrderedDict()
def __new__(cls, name, bases, clsdict):
c = type.__new__(cls, name, bases, clsdict)
c._orderedKeys = clsdict.keys()
return c
class Person(metaclass=OrderedMeta):
name = None
date_of_birth = None
nationality = None
gender = None
address = None
comment = None
for member in Person._orderedKeys:
if not getattr(Person, member):
print(member)
In Python2, it's a lot more tricky. It would be achievable with something fairly hacky like introspecting the source, and working out the definition order from the AST, but that's probably a lot more trouble than it's worth.
If all you need is an aggregate of variables, perhaps you should use a namedtuple instead. It also maintains the order of the fields (as it's a tuple).
from collections import namedtuple
Person = namedtuple('Person', ('name',
'data_of_birth',
'nationality',
'gender',
'address',
'comment'))
print Person._fields
Ok, it's not an answer per se but a workaround that only works in the context of original question ("Every class member [which is an instance] of a certain type = column, and order of columns is important"). The solution is to introduce a class variable _count into the CertainType class, and increment it at every instantiation. After that all class members of the CertainType are packed into a list which is sorted using key=attrgetter('_count')
P.S. Omitting that "which is an instance" part was a mistake on my part and it has limited range of solutions considerably. Sorry for that.