Unpacking a class [duplicate] - python

This question already has answers here:
Class that acts as mapping for **unpacking
(3 answers)
Implement packing/unpacking in an object
(4 answers)
Closed last year.
I'd like to make a class that unpacks it's objects like a dictionary.
For example, with a dictionary you can do this
foo = {
"a" : 1
"b" : 2
}
def bar(a,b):
return a + b
bar(**foo)
outputs 3
And I'd like to be able to do this
class FooClass:
def __init__(self):
self.a = a
self.b = b
f = FooClass()
bar(**f)
and have it output 3
This is the most related question I could find but it doesn't address this so I'm thinking it might not be possible.
Currently what my solution would be this:
class FooClass:
def __init__(self):
self.a = a
self.b = b
def to_dict(self):
return {
"a" : self.a,
"b" : self.b
}
f = FooClass()
bar(**f.to_dict())

As pointed out in the comments, writing a conformant subclass of the collections.abc.Mapping abstract class is the way to go. To (concretely) subclass this class, you need to implement __getitem__, __len__, and __iter__ to behave consistently like a dictionary would. So that means __getitem__ expects a string, __iter__ returns an iterable of strings, etc.
For a simple example, we'll simply delegate all of these to self.__dict__, but in real code you'd likely want to do something more refined.
from collections.abc import Mapping
class FooClass(Mapping):
def __init__(self, a, b):
self.a = a
self.b = b
def __getitem__(self, x):
return self.__dict__[x]
def __iter__(self):
return iter(self.__dict__)
def __len__(self):
return len(self.__dict__)
def bar(a, b):
return a + b
foo = FooClass(40, 2)
print(bar(**foo))

Aside from reyling on vars(f) or f.__dict__, you could use a dataclass.
from dataclasses import dataclass, asdict
#dataclass
class FooClass:
a: int
b: int
Demo:
>>> f = FooClass(1, 2)
>>> asdict(f)
{'a': 1, 'b': 2}

def bar(a, b):
return a + b
class FooClass:
def __init__(self, a, b):
self.a = a
self.b = b
f = FooClass(1, 2)
print(bar(*f.__dict__.values()))
# print(bar(**f.__dict__)) # Also works
Output:
3

Related

Instances of class A as class attributes of A in Python?

Are there Python versions that allow defining your class like this:
class Foo:
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
and then adding class attributes, such as BAR_1, BAR_2, etc.:
class Foo:
BAR_1 = ...
BAR_2 = ...
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
which are actually "special cases of Foo", such as:
class Foo:
BAR_1 = Foo(4, 9, 16)
BAR_2 = Foo(2, 3, 5)
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
so that, in my code, I can either make my own Foos or get common, predefined Foos by working directly with Foo.BAR_1 and Foo.BAR_2?
The code above obviously does not work, otherwise I would not post the question (Foo is an unresolved reference when defining BAR_1 and BAR_2). I found a trick on SO how to sort-of achieve this -> defining a custom ClassProperty class:
class ClassProperty(object):
def __init__(self, f):
self.f = f
def __get__(self, obj, owner):
return self.f(owner)
which then allows me to define Foo as
class Foo:
#ClassProperty
def BAR_1(cls):
return Foo(4, 9, 16)
#ClassProperty
def BAR_2(cls):
return Foo(2, 3, 5)
...
and that works, but the issue is that Foo.__init__ is called everytime whenever Foo.BAR_1 or Foo.BAR_2 is retrieved, which can be useful in certain situations (precisely those where you always want separate instances), but in the special case where Foo is simply a messenger class which is coincidentally hard to load (like a result of a computation for example), this solution is unfeasible. I'd like for the constructor of Foo to be called exactly once for BAR_1, exactly once for BAR_2 (ideally lazily, during the first retrieval, that would be fantastic), and after that it would only return the created instances. So, is there a way to do this?
I use Python 3.8.6.
During the time I composed the question body, I figured out I can just define Foo like this:
class Foo:
#ClassProperty
def BAR_1(cls):
if not hasattr(cls, '_Foo__BAR_1'):
cls.__BAR_1 = Foo(4, 9, 16)
return cls.__BAR_1
#ClassProperty
def BAR_2(cls):
if not hasattr(cls, '_Foo__BAR_2'):
cls.__BAR_2 = Foo(2, 3, 5)
return cls.__BAR_2
Now, I can call Foo.BAR_X for retrieval of a defined Foo.__BAR_X which is actually an instance of Foo, which is always created only once.

How to find an attribute in self.__init__?

class C:
def __init__(self):
self.a = 1
self.b = 2
def __setattr__(self,name,value):
if a in self.__init__: #Determine if a is an instance of __init__ function
do something
The above code will return an error and says
if name in self.__init__:
TypeError: argument of type 'method' is not iterable
If I don't iterate through self.__init__ function, how else am I supposed to know what attributes are defined in self.__init__ function?
If an attribute is set in init, I want to set the name prefixed by "somestring_" and append it to self__dict__: e.g., if I print self.__dict__ after self.__setattr__, it will print {'somestring_a': 1, 'somestring_b': 2}
Add an attribute that lists the attributes that are set in __init__, and use that.
class C:
predefined = ['a', 'b']
def __init__(self):
self.a = 1
self.b = 2
def __setattr__(self,name,value):
if name in self.predefined:
do something
else:
do something else
Another option would be to copy the keys of self.__dict__ at the end of the __init__ method:
class C:
def __init__(self):
self.a = 1
self.b = 2
self.predefined = set(self.__dict__)
def __setattr__(self,name,value):
if name in self.predefined:
do something
else:
do something else
class C():
def __init__(self, a, b):
self.a = a
self.b = b
a = C(1, 2)
print(a.__dict__)
>>> {'a': 1, 'b': 2}
So __dict__.keys() will give you the list of attributes ...
BUT :::
if you will check the list of your attributes in __setattr__ , you have to keep in mind that this function is also called when you do a = C(1, 2) so you shouldn't check your attributes in this level of code.

Accessing higher-level class from nested class in Python

Suppose I have two Python classes, A and B, and that B is an attribute of A. Can a method of B modify a property of A? for example, I would like to be able to call
A.B.setXinA(1)
A.x
>>> 1
One way around it would be embed a reference to A in B:
A.B.reftoA = A
But that's rather ugly... Is there a way to access the higher-level class directly? Below is a working example using the second method:
class A:
def __init__(self, b):
b.parent = self
setattr(self, b.name, b)
class B:
def __init__(self, name):
self.name = name
b = B('abc')
a = A(b) # b is now a.abc
abc.parent.x = 1
a.x
>>> 1
What about a method in B like this:
class B:
def __init__(self, name):
self.name = name
def setXinA(self, x):
self.parent.x = x
Then:
>>> b = B('abc')
>>> a = A(b)
>>> b.setXinA(19)
>>> print(A.x)
19
This way requires that setXinA is called by an instance of B rather than just B.setXinA(42) for example. Also, it sets x as an attribue of the class A, rather than any particular instance of A.

Pseudo-dicts as properties

I have a Python class C which should have two pseudo-dicts a and b. The term pseudo-dicts means that the dictionaries don't actually exist and that they are “recomputed” each time a key is accessed.
In pseudocode this would look like this:
class C:
def a.__getitem__(self, key):
return 'a'
def b.__getitem__(self, key):
return 'b'
>>> c = C()
>>> c.a['foo']
'a'
>>> c.b['bar']
'b'
I could implement a class for a and b, but since both have just a few short methods, I wonder whether there is a more elegant and compact way to do this.
Why not just define your own class?
class PseudoDict(object):
def __init__(self, c):
self.c = c
def __getitem__(self, key):
return self.c.somethingmagical()
class C(object):
def __init__(self):
self.a = PseudoDict(self)
self.b = PseudoDict(self)
c = C()
print c.a['foo']
print c.b['bar']
I'm not sure where the values for these 'pseudo-dicts' are coming from, so you'll have to update the __getitem__ method.
Like this?
from collections import defaultdict
class C:
a = defaultdict(lambda:'a')
b = defaultdict(lambda:'b')
c=C()
print c.a['foo']
print c.b['bar']
Or maybe like this for real calculation functions?
from collections import defaultdict
class C:
def __init__(self):
self.a = defaultdict(self.geta)
self.b = defaultdict(self.getb)
def geta(self):
return 'a'
def getb(self):
return 'b'
c=C()
print c.a['foo']
print c.b['bar']

Python object conversion

Assume that we have an object k of type class A. We defined a second class B(A). What is the best practice to "convert" object k to class B and preserve all data in k?
This does the "class conversion" but it is subject to collateral damage. Creating another object and replacing its __dict__ as BrainCore posted would be safer - but this code does what you asked, with no new object being created.
class A(object):
pass
class B(A):
def __add__(self, other):
return self.value + other
a = A()
a.value = 5
a.__class__ = B
print a + 10
a = A() # parent class
b = B() # subclass
b.value = 3 # random setting of values
a.__dict__ = b.__dict__ # give object a b's values
# now proceed to use object a
Would this satisfy your use case? Note: Only the instance variables of b will be accessible from object a, not class B's class variables. Also, modifying variables in a will modify the variable in b, unless you do a deepcopy:
import copy
a.__dict__ = copy.deepcopy(b.__dict__)
class A:
def __init__(self, a, b):
self.a = a
self.b = b
class B(A):
def __init__(self, parent_instance, c):
# initiate the parent class with all the arguments coming from
# parent class __dict__
super().__init__(*tuple(parent_instance.__dict__.values()))
self.c = c
a_instance = A(1, 2)
b_instance = B(a_instance, 7)
print(b_instance.a + b_instance.b + b_instance.c)
>> 10
Or you could have a sperate function for this:
def class_converter(convert_to, parent_instance):
return convert_to(*tuple(parent_instance.__dict__.values()))
class B(A):
def __init__(self, *args):
super().__init__(*args)
self.c = 5
But using the 2nd method, I wasn't able to figure out how to pass additional values

Categories