How to get attributes of parent class object only - python

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'}

Related

Insert Nested Objects into mongodb in python3 (Nested object is a class Instance)

I have a nested structure like this:
class Student(object):
def __init__(self, name, age, address):
self.name = name
self.age = age
self.address = address
class Address(object):
def __init__(self, street, pcode, another_obj):
self.street = street
self.postal_code = pcode
self.another_obj = another_obj
class AnotherObject(object):
def __init__(self):
self.someattr = 'some_init_value'
# Now i amm going to save data like this
another_obj = AnotherObject()
address = Address('xyz', 'xyz', another_obj)
obj = Student('abc', 32, address)
The object obj is an instance of a class. I am doing collection.insert_one(obj). Usually, I would do obj.dict to get "dict" of class instance which is compatible with pymongo but it does not convert the nested objects into dict too.
The problem here is that the "address" and "some_other_object" are also some other class instances and that causes bson.errors.InvalidDocument Exception while insertion.
Is there a way to convert nested class instances/documents (address & some_other_object) into dict or any other type acceptable to mongodb.
The package I am using for mongodb communication is pymongo v3.9.0.
Error is TypeError:
document must be an instance of dict, bson.son.SON,
bson.raw_bson.RawBSONDocument, or a type that inherits from
collections.MutableMapping
Add in some to_dict() methods to your classes:
from pymongo import MongoClient
class Student(object):
def __init__(self, name, age, address):
self.name = name
self.age = age
self.address = address
def to_dict(self) -> dict:
record = {
"name": self.name,
"age": self.age,
"address": self.address.to_dict() if self.address is not None else None
}
return record
class Address(object):
def __init__(self, street, pcode, another_obj):
self.street = street
self.postal_code = pcode
self.another_obj = another_obj
def to_dict(self) -> dict:
record = {
"street": self.street,
"postal_code": self.postal_code,
"another_obj": self.another_obj.to_dict() if self.another_obj is not None else None
}
return record
class AnotherObject(object):
def __init__(self):
self.someattr = 'some_init_value'
def to_dict(self) -> dict:
record = {
"someattr": self.someattr
}
return record
# Now i amm going to save data like this
another_obj = AnotherObject()
address = Address('xyz', 'xyz', another_obj)
obj = Student('abc', 32, address)
db = MongoClient()['yourdatabase']
db.collection.insert_one(obj.to_dict())
print(db.collection.find_one({}))

List attributes of a sublass instance in Python

I'm trying to build some "ORM like" behavior in Python. To do this I have a Model class and would like to produce any kind of subclasses. My problem is when I try to list the attributes of my subclasses instances.
Here is my Model class:
class Model:
def __init__(self):
self.className = type(self).__name__
def listAttributes(self):
from modules.users.user import User
instance = User()
meta = vars(instance)
And a User subclass :
class User(models.Model):
name = models.FieldChar()
password = models.FieldChar()
age = models.FieldInteger()
When I invoke the listAttributes method from a User instance, I don't have what I expect, but probably what I should: the meta var contains only className. Here's how I invoke the method:
from modules.users import user
user = user.User()
user.listAttributes()
Same behavior when I override the listAttributes method in User():
def listAttributes(self):
meta = vars(self)
print(meta)
Is there a way to list the attribute of the object from which I call the listAttributes method?
Thanks for your lights!
You could make your listAttributes method a classmethod and try the following:
class Model(object):
#classmethod
def listAttributes(cls):
return [
a for a in dir(cls) if not
callable(getattr(cls, a)) and not
a.startswith("__")
]
If you just want to have the attributes that are of your "Fields" type (FieldChar, FieldInt...) then you could make a base class for the fields called e.g. ModelField and also check your attributes against this base type:
class Model(object):
#classmethod
def listAttributes(cls):
attributes = []
for a in dir(cls):
if ((not callable(getattr(cls, a)) and not
a.startswith("__") and
getattr(cls, a).__class__.__bases__[0] == ModelField)):
attributes.append(a)
You could even return the objects instead of just the names. Or append the objects to a dict in your Model class (to make usage in your code easier). I constructed my ORM like this:
class ModelField(object):
...
class IntField(ModelField):
...
class Model(object):
id = IntField(pk=True)
def __init__(self, *args, **kwargs):
try:
self.modelFields
except AttributeError:
self.__class__.introspect()
for key, modelfield in self.modelFields.items():
setattr(self, key, kwargs.get(key, None))
#classmethod
def introspect(cls):
cls.modelFields = {}
for a in dir(cls):
if ((not callable(getattr(cls, a)) and not
a.startswith("__") and
getattr(cls, a).__class__.__bases__[0] == ModelField)):
cls.modelFields[a] = getattr(cls, a)
Then i used cls.introspect to set up the modelFields dict and use this dict to e.g. autogenerate a db-table or construct querys by using attributes of the ModelField class.

How does an ORM model definition bind column attributes to actual columns?

I'm trying to understand how ORMs are able to resolve table columns via a class attribute without explicitly providing table and column names.
from orm import Column
class Car(Model):
id = Column()
Queries then are able to do this:
Car.select().where(Car.id == 7)
I understand that Column defines a method for the __eq__ operator, but how would a column instance know it is referring to class Car and attribute id (assuming those are used for table and column names by default)?
I'm learning a lot about python by digging into how ORM libs work!
In SqlAlchemy and DJango ORM, the base class (Model here) is tie to a metaclass or a factory method (like __new__) which performs introspection.
Here is how you can reproduce with a function (simpler solution):
class Model():
pass
class Column():
def __init__(self, name=None):
self.name = name
def setup_class(cls):
for name, value in cls.__dict__.items():
if isinstance(value, Column):
attr = getattr(cls, name)
attr.name = attr.name or name
This setup_class function introspect the cls class and setup the name attribute if it is empty or None, for instance:
class Car(Model):
id = Column()
from pprint import pprint
pprint(Car.id.name)
# -> None
setup_class(Car)
pprint(Car.id.name)
# -> 'id'
Edit
implementation example using metaclass:
class Column():
def __init__(self, name=None):
self.name = name
class MyMeta(type):
def __new__(cls, name, bases, attrs):
for name, col in attrs.items():
if isinstance(col, Column):
col.name = col.name or name
return super(MyMeta, cls).__new__(cls, name, bases, attrs)
class Model(metaclass=MyMeta):
pass
class Car(Model):
id = Column()
import pprint
pprint.pprint(Car.id.name)
# -> 'id'
Of course, that's not enough. You have to understand that Column is a descriptor. This descriptor is used to build the SQL queries.

How to print the contents of contact list when designing a new class

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?

Django create new instance of model with '__init__' function from table, instance data is error?

there is my model code which have problem:
class Person(models.Model):
name = models.CharField(max_length=20)
url = models.URLField()
card_id = models.CharField(max_length=20)
def __init__(self, name, card_id, url, *args, **kwargs):
super().__init__(*args, **kwargs)
self.name = name
self.card_id = card_id
self.url = url
def __str__(self):
return str({
'name': self.name,
'card_id': self.card_id,
'url': self.url
})
there is test code:
class PersonTestCase(TestCase):
def setUp(self):
Person.objects.create(name="test",
card_id="123",
url="http://www.baidu.com")
def test_get(self):
p = Person.objects.get(name="test")
print(p)
the print of test function 'test_get' is:
{'name': 1, 'card_id': 'test', 'url': '123'}
which is error.
the value of name is data of id column in table ,
the value of card_id is data of name column in table,
...
but if I change function __init__ to follow:
def __init__(self, id=None, name=None, card_id=None, url=None, *args, **kwargs):
or delete function __init__, get the print of function test_get is:
{'name': 'test', 'card_id': '123', 'url': 'http://www.baidu.com'}
which is right.
In somewhere, I use Person(....) to create new instance, if I delete __init__, it means I cannot use Person(...) to create new instance,
Shall I change my code?
So what is best way to create new instance from table data?
You should not define __init__ on a model at all. The base class - which you call via super - already sets attributes from kwargs. Remove that method.
So what is best way to create new instance from table data?
You had it, the best way is to use MyModel.objects.create which will handle all the arguments, automatically set the primary key and return the instance.
The second way is:
my_model = MyModel(...)
my_model.save()
If you want to know the problem with your test
It's the instance attribute you are defining, such as self.name = name. When you assign the first argument to it you are actually assigning the id (which is passed by Django ORM behind the scene to the original __init__)
Moral of the story
Don't reinvent the wheel, those attributes are already set by Django.

Categories

Resources