Is there any way to get class instance attributes without creating class instance?
Asked Answered
S

4

4

Here is the link to my first question: Creating class instance from dictionary?
So I am trying to create class instance from dictionary which holds keys that class has not. For example:

class MyClass(object):
    def __init__(self, value1, value2):
        self.attr1 = value1
        self.attr2 = value2

dict = {'attr1': 'value1', 'attr2': 'value2', 'redundant_key': 'value3'}

Before creating class instance I have to delete this redundant_key from dict

dict.pop('redundant_key', None)
new_instance = MyClass(**dict)

The problem is that I have several classes and several dicts with a lot of keys (I have several responses in json format which are represented as dicts and I want to create objects from this json responses). I already found an interim solution from previous question - how to delete redundant keys. I can create new dict from old dict only with keys that I need:

new_dict = {key: old_dict[key] for key in allowed_keys}

So here is the code:

class MyClass(object):
    def __init__(self, value1, value2):
        self.attr1 = value1
        self.attr2 = value2

dict = {'attr1': 'value1', 'attr2': 'value2', 'redundant_key': 'value3'}
new_instance = MyClass(**{key: dict[key] for key in allowed_keys})

All I need now is to get allowed_keys. So the question - Is there any way to get class instance attributes without creating class instance?

Sandalwood answered 8/5, 2015 at 16:47 Comment(8)
first, it is not possible, second, you want to get the arguments of a function, and not the attributes of a instance, which is possible by inspection.Petit
It's technically impossible, attributes can be added and removed dynamically to instances, so it doesn't make sense to have them with out an actual instance. I am not sure why would you want it, where is the allowed_keys defined?Tiler
@Petit All I need is to get attr1 and attr2 in allowed_keys when attr1, attr2 are class instance' attributesSandalwood
@Tiler I can define allowed_keys with myself but its about 10 dicts with 10-20 different keys so I thought about way to fill allowed_keys automatically. I don't really need class instance' attributes I need attributes that are creating during __init__ methodSandalwood
Looking at your use case, looks like what you need is not the attributes that __init__ will create, but its argument signature: The arguments it accepts. That's a different problem, and it has a solution :-)Diarrhea
@alexis: that's what I said 17min ago. But the OP won't listen :-(Petit
You mean "unnecessary", not "redundant".Effervescent
@Effervescent Yes, I am sorry for my poor englishSandalwood
E
3

If you insist on using an overly general dictionary to initialize your object, just define __init__ to accept, but ignore, the extra keys.

class MyClass(object):
    def __init__(self, attr1, attr2, **kwargs):
        self.attr1 = attr1
        self.attr2 = attr2

d = {'attr1': 'value1', 'attr2': 'value2', 'extra_key': 'value3'}
new_instance = MyClass(**d)

If you can't modify __init__ (as appears to be the case if it inherits from a SQLAlchemy declarative base), add an alternate constructor to accept all the keyword arguments but pick out the ones you need.

class MyClass(Base):
    @classmethod
    def from_dict(cls, **kwargs):
        # Let any KeyErrors propagate back to the caller.
        # They're the one who forgot to make sure **kwargs
        # contained the right keys.
        value1 = kwargs['attr1']
        value2 = kwargs['attr2']
        return cls(value1, value2)

new_instance = MyClass.from_dict(**d)
Effervescent answered 8/5, 2015 at 17:27 Comment(2)
You've broken one of the most basic rules of Python programming by naming a variable the same as a built-in...Autoionization
No, I copied the user's original code and didn't bother to change it.Effervescent
T
1

Disclaimer: This answers what OP was asking about (getting attributes of an instance), not what they needed. Which seems to be constructor's parameter list.

You cannot do what you want. In python attributes are added to an instance of a class dynamically. Two instances of the same class can have different attributes. Well... to be precise, there are things called instance attributes, and class attributes.

Instance attributes are the ones associated with the instance of a class. Class attributes are associated with its definition i.e. if you wrote (MyClass.foo)

If you look at the example, the attributes are added during the __init__ on self so they are instance attributes.

What you could do is to create a valid instance and inspect it (look at below example), provide a static list of allowed_keys (e.g. as a class attribute), or in some way require allowed_keys as constructor parameter. All of these are kind of workarounds as what you really need is impossible. They have their pros and cons.

example:

class MyClass(object):
    def __init__(self, value1, value2):
        self.attr1 = value1
        self.attr2 = value2

instance = MyClass(1,2)                        # create a valid object
instance_attributes = instance.__dict__.keys() # inspect it
print(instance_attributes)
Tiler answered 8/5, 2015 at 17:1 Comment(5)
I don't really need class instance' attributes I need attributes that are creating during __init__ method. Is there any way to get attributes that are added during in __init__ method ?Sandalwood
A case where there is a notion of class attributes is when a class has __slots__ instead of an attribute dictionary.Diarrhea
This solution can have side effects, won't work in each case and you already need to know the parameters of __init__ to instanciate the class.Petit
I am not saying this is a good idea. Also you do know the number of parameters, not the names of attributes which seems to be needed.Tiler
@Petit I think I got you. I wrote a little disclaimer. The question is kind of misleading. Plus, I think above ideas are the only solutions that will work with *args and **kwargs. I think the answer still has some value.Tiler
D
1

Here's how to do what you're trying to do: Instantiate your classes with appropriate arguments. Note that you're not examining the attributes that __init__ creates, but the arguments it accepts. It doesn't matter what it does with them-- that's not your concern when you're calling the constructor.

myparams = {'attr1': 'value1', 'attr2': 'value2', 'redundant_key': 'value3'}
usable = { k:v for k, v in myparams if k in MyClass.__init__.__code__.co_varnames }
new_instance = MyClass(**usable)

I used a filtered dictionary comprehension for clarity, but you could do it in one step of course.

PS. Note that you'll have a problem if one of your dictionaries happens to contain the key self. :-) You may want to filter it out if you can't trust your data source.

Diarrhea answered 8/5, 2015 at 17:18 Comment(3)
This is a really good solution, but the problem is that class which I have is creating by sqlalchemy. I am just write it according to the declarative class definition style. So MyClass.__init__.__code__.co_varnames returns something like that ('self', 'kwargs', 'new_state') and I just can't write __init__ method as I wantSandalwood
In that case your question should be specific to sqalchemy! You'd have to dig around its internals to find out what arguments are accepted by the classes you are interested in. There may be something you can use (perhaps its initializers have a standard way of managing the allowed keyword arguments), or there may not. In general you cannot expect to know what a function will do, without either running it or simulating it.Diarrhea
I also strongly suspect that the attributes created will depend on the keyword arguments you provide-- meaning that even if you can instantiate the constructor without arguments to instantiate a sample object, you won't see all possible attributes in the instantiated object.Diarrhea
P
1

You should have mentioned, that you want the columns of a SQLAlchemy-Mapper-Class. This is a much easier task:

dict = {'attr1': 'value1', 'attr2': 'value2', 'redundant_key': 'value3'}
columns = YourTable.__table__.columns.keys()
entry = YourTable(**{k: dict[k] for k in columns})
Petit answered 8/5, 2015 at 17:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.