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.
Related
Consider the following python code:
from dataclasses import dataclass
#dataclass
class Registration:
category: str = 'new'
#dataclass
class Car:
make: str = None
category: str = None
reg: Registration = None
def __post_init__(self):
''' fill in any missing fields from the registration of car '''
if self.reg:
for var in vars(self.reg):
if not self.var:
self.var = self.reg.var
r = Registration()
a = Car(make='ford', category='used', reg=r)
# its unknown if b is used/new, so we explicitly pass it None
b = Car(make='ford', category=None, reg=r)
In above example, the __post_init__ is supposed to fill in fields in Car class if it was not passed in during creation of Car object. However if None was explicitly passed in as the field value (in this case for category) it's not supposed to overwrite it from the Registration object. But the above code does. How do I detect what values were explicitly passed in during the object creation vs what are defaults?
I'd be surprised if there were a way to distinguish between
a None passed explicitly vs one that the object acquired via
its defaults. In situations like yours, one technique is to use
a kind sentinel value as the default.
#dataclass
class Car:
NO_ARG = object()
make: str = None
category: str = NO_ARG
reg: Registration = None
def __post_init__(self):
if self.reg:
for var in vars(self.reg):
if getattr(self, var) is self.NO_ARG:
setattr(self, var, getattr(self.reg, var))
However, you might also take the awkward situation you find yourself
in as a signal that perhaps there's a better way to model your
objects. Without knowing more about the
broader context it's difficult to offer definitive advice, but
I would say that your current strategy strikes me as fishy, so I
would encourage you to thinks some more about your OO plan.
To give one example of an alternative model, rather than using the Registration to
overwrite the attributes of a Car, you could instead build a property
to expose the Registration attribute when the Car attribute
is missing. A user of the class can decide whether they want
the category strictly from the Car or they are happy to take
the fallback value from the Registration, if available. This approach
comes with tradeoffs as well.
#dataclass
class Car:
make: str = None
category: str = None
reg: Registration = None
#property
def category_reg(self):
if self.category is None and self.reg:
return self.reg.category
else:
return self.category
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
I have two models: City, and its alias CityAlias. The CityAlias model contains all the names in the City, plus the aliases. What I want is that whenever City is searched by name, the CityAlias model should be queried. This is what I've come up with:
class CityQuerySet(models.QuerySet):
""" If City is searched by name, search it in CityAlias """
def _search_name_in_alias(self, args, kwargs):
for q in args:
if not isinstance(q, models.Q): continue
for i, child in enumerate(q.children):
# q.children is a list of tuples of queries:
# [('name__iexact', 'calcutta'), ('state__icontains', 'bengal')]
if child[0].startswith('name'):
q.children[i] = ('aliases__%s' % child[0], child[1])
for filter_name in kwargs:
if filter_name.startswith('name'):
kwargs['aliases__%s' % filter_name] = kwargs.pop(filter_name)
def _filter_or_exclude(self, negate, *args, **kwargs):
# handles 'get', 'filter' and 'exclude' methods
self._search_name_in_alias(args=args, kwargs=kwargs)
return super(CityQuerySet, self)._filter_or_exclude(negate, *args, **kwargs)
class City(models.Model):
name = models.CharField(max_length=255, db_index=True)
state = models.ForeignKey(State, related_name='cities')
objects = CityQuerySet.as_manager()
class CityAlias(models.Model):
name = models.CharField(max_length=255, db_index=True)
city = models.ForeignKey(City, related_name='aliases')
Example: Kolkata will have an entry in City model, and it will have two entries in the CityAlias model: Kolkata and Calcutta. The above QuerySet allows to use lookups on the name field.
So the following two queries will return the same entry:
City.objects.get(name='Kolkata') # <City: Kolkata>
City.objects.get(name__iexact='calcutta') # <City: Kolkata>
So far so good. But the problem arises when City is a ForeignKey in some other model:
class Trip(models.Model):
destination = models.ForeignKey(City)
# some other fields....
Trip.objects.filter(destination__name='Kolkata').count() # some non-zero number
Trip.objects.filter(destination__name='Calcutta').count() # always returns zero
Django internally handles these joins differently, and doesn't call the get_queryset method of City's manager. The alternative is to call the above query as following:
Trip.objects.filter(destination=City.objects.get(name='Calcutta'))
My question is that can I do something, so that however the City model is searched by name, it always searches in the CityAlias table instead?
Or is there another better way to implement the functionality I require?
I think it is better (and more pythonic) to be explicit in what you ask for throughout instead of trying to do magic in the Manager and thus:
City.objects.get(aliases__name__iexact='calcutta') # side note: this can return many (same in original) so you need to catch that
And:
Trip.objects.filter(destination__aliases__name='Calcutta').count()
I was trying to use Custom Lookups but apparently you cannot add a table to the join list. (Well, you could add an extra({"table": ...}) in the model's manager but it's not an elegant solution).
So I'd propose you:
1) Keep always your 'main/preferred' name city also as a CityAlias. So the metadata of the city will be in City... but all the naming information will be in CityAlias. (and maybe change the names)
In this way all look-ups will happen in that table. You could have a boolean to mark which instance is the original/preferred.
class City(models.Model):
state = models.ForeignKey(State, related_name='cities')
[...]
class CityAlias(models.Model):
city = models.ForeignKey(City, related_name='aliases')
name = models.CharField(max_length=255, db_index=True)
2) If you are thinking about translations... Have you thought about django-modeltranslation app?
In this case, it would create a field for each language and it would be always better than having a join.
3) Or, if you are using PostgreSQL, and you are thinking about "different translations for the same city-name" (and I'm thinking with transliterations from Greek or Russian language), maybe you could use PostgreSQL dictionaries, trigrams with similarities, etc. Or even in this case, the 1st approach.
Speaking of keeping it simple. Why not just give the City model a char field 'CityAlias' that contains the string? If I understand your question correctly, this is the most simple solution if you only need one alias per city. It just looks to me as though you are complicating a simple problem.
class City(models.Model):
name = models.CharField(max_length=255, db_index=True)
state = models.ForeignKey(State, related_name='cities')
alias = models.CharField(max_length=255)
c = City.objects.get(alias='Kolkata')
>>>c.name
Calcutta
>>>c.alias
Kolkata
I am creating a phone book with python and was stumped on how to search through the class for a specific contact or how to search for a specific entry in the class.
This is what I have so far:
class person:
def __init__(self, first_name, last_name, phone_number):
person.first = first_name
person_last = last_name
person_number = phone_number
class friend:
def __init__(self, email, birth_date):
email = johnny.seagraves8219
birth_date = 8/13/1993
super(friend, self)._init_
ans = True
while ans:
print("""
1. Add a contact
2. Look up contact by name
Press enter to quit
""")
ans = input("What would you like to do?")
if ans == "1":
elif ans == "2":
look_up = input("Who would you like to look up?")
The class will not have entries that you can search through as far as I know. The class is basically just a constructor which is used to create an instance of a, in this case, person in the phone book. You could use an array to hold the instances and then search the array.
for i in arrayName:
if(arrayName[i].first == look_up):
# do something
To create an instance simply call the constructor:
firstPerson = person("Mike", "Ryans", "1800838699")
arrayName.append(firstPerson)
My knowledge of python is fairly limited but this is what I think should work.
Good luck!
To begin with, Flexicon is correct: your person/friend class is just a single entity that holds only a single persons information. To be able to search for people, you'll either need to make an array (list) of person objects, a map (dictionary) mapping a name or nickname to the object (so {'Timmie': <my_timidger_object>}, or you can wrap one of these approaches in a AddressBook class that contains additional methods that one of those basic data structures cannot do for you.
Some other important problems: your friend class does not extend the person, the constructor for a class has two underscores, like __init__; as well, you should add default values for email and birth_date in the person class, or trying to access these later will cause an error; your attribute need self before them or they will not be treated as attribute for the object
Here is Object Orientated (using a list to hold the people) way to do it, though it might be overkill for something this simple:
class person:
def __init__(self, first_name, last_name, phone_number):
self.person.first = first_name
self.person_last = last_name
self.person_number = phone_number
self.email = None #Notice the placeholders?
self.birth_date #Not having this information should not be exceptional
class friend(person): #Here, friend extends person
def __init__(self, email, birth_date):
self.email = email
self.birth_date = birth_date
super(friend, self).__init__()
class AddressBook:
def __init__(self, people = None):
if people:
self.entries = list(people)
else:
self.entries = []
#This is merely an example method, a better way would be to use some relational method like SQL to put in a query to find specific information about the person, but that is beyond the scope of this answer
def find_num(self, first_name, last_name):
for person in self.entries:
if (person.last_name, person.first_name) == (last_name, first_name):
return person
return None
class Employee(db.Model):
firstname = db.StringProperty()
lastname = db.StringProperty()
address1 = db.StringProperty()
timezone = db.FloatProperty() #might be -3.5 (can contain fractions)
class TestClassAttributes(webapp.RequestHandler):
"""
Enumerate attributes of a db.Model class
"""
def get(self):
for item in Employee.properties():
self.response.out.write("<br/>" + item)
#for subitem in item.__dict__:
# self.response.out.write("<br/> --" + subitem)
The above will give me a list of the property names for the variable "item".
My idea of item.__dict__ didn't work because item was a str.
How can I then display the data field type for each property, such as db.FloatProperty() for the property called timezone?
GAE = Google App Engine - but I'm sure the same answer would work for any class.
Thanks,
Neal Walters
Iterate using "for name, property in Employee.properties().items()". The property argument is the Property instance, which you can compare using instanceof.
For problems like these, the interactive Python shell is really handy. If you had used it to poke around at your Employee object, you might have discovered the answer to your question through trial and error.
Something like:
>>> from groups.models import Group
>>> Group.properties()
{'avatar': <google.appengine.ext.db.StringProperty object at 0x19f73b0>,
'created_at': <google.appengine.ext.db.DateTimeProperty object at 0x19f7330>,
'description': <google.appengine.ext.db.TextProperty object at 0x19f7210>,
'group_type': <google.appengine.ext.db.StringProperty object at 0x19f73d0>}
From that you know that the properties() method of a db.Model object returns a dict mapping the model's property names to the actual property objects they represent.
I add the same problem, and the first 2 answers did not help me 100%.
I was not able to get the type information, from the meta data of the class or the
instance property, which is bizarre. So I had to use a dictionary.
The method GetType() will return the type of the property as a string.
Here is my answer:
class RFolder(db.Model):
def GetPropertyTypeInstance(self, pname):
for name, property in self.properties().items():
if name==pname:
return property
return None
def GetType(self, pname):
t = self.GetPropertyTypeInstance(pname)
return RFolder.__DB_PROPERTY_INFO[type(t)]
__DB_PROPERTY_INFO = {
db.StringProperty :"String",
db.ByteStringProperty :"ByteString",
db.BooleanProperty :"Boolean",
db.IntegerProperty :"Integer",
db.FloatProperty :"Float",
db.DateTimeProperty :"DateTime",
db.DateProperty :"Date",
db.TimeProperty :"Time",
db.ListProperty :"List",
db.StringListProperty :"StringList",
db.ReferenceProperty :"Reference",
db.SelfReferenceProperty :"SelfReference",
db.UserProperty :"User",
db.BlobProperty :"Blob",
db.TextProperty :"Text",
db.CategoryProperty :"Category",
db.LinkProperty :"Link",
db.EmailProperty :"Email",
db.GeoPtProperty :"GeoPt",
db.IMProperty :"IM",
db.PhoneNumberProperty :"PhoneNumber",
db.PostalAddressProperty :"PostalAddress",
db.RatingProperty :"Rating"
}