This popular question addresses setting instance attributes with keyword arguments. However, I'd like to construct a class whose instances all have the same attributes based on some dictionary. How can this be achieved?
Here's my attempt. It seems I haven't quite understood something about class definitions.
d = {'x': 1, 'y': 2}
# Here's what I'd like to do
class A:
__dict__ = d
# Answer from the linked question that works
class B:
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
a = A()
b = B(**d)
# print(A.x) # Has no attribute x
# print(a.x) # Has no attribute x
print(b.x)
This is curious, because both a.__dict__ and b.__dict__ return the same thing.
The type function can take arguments that allow it to create a new class dynamically:
B = type('B', (), d)
b = B()
print(b.x, b.y)
Output:
1 2
The first argument is the name of the generated class. The second is a tuple containing its base classes. In particular, the following two snippets are (roughly) equivalent:
class D(A, B, C):
pass
and:
D = type('D', (A, B, C), {})
The last argument is a dict mapping names to attributes (both methods and values).
Related
I'm looking to establish a list, (list_=[a,b,c]), iterate over the list and apply it to a class (class_). So something like this:
for letter in list_:
x = class_.letter
Is this possible?
Attributes are typically accessed from a class using a dot notation, ex: my_class.attribute_1. This is useful when accessing attributes are hard coded.
But as you point out, this is not useful when needing to dynamically access attributes, as in the case of the list above. The solution in these cases is to access attributes using the getattr() method which takes input of your class and the attribute name. Ex: x = 'attribute_1, getattr(my_class, x) would return the same results as my_class.attribute_1.
💡 It is worth pointing out that attributes can be set in a similar way: my_class.foo = 1 is equivalent to setattr(my_class, 'foo', 1).
Here is a solution where attributes are accessed using a list (from an example class):
class MyClass:
def __init__(self):
self.a = 1
self.b = 2
self.c = 3
# Instantiate your class
my_class = MyClass()
# Create a list of letters
list_1 = ['a', 'b', 'c']
# Get attributes
for letter in list_1:
x = getattr(my_class, letter)
print(f'{letter=}, {x=}')
I'm working with data science and I have a pretty big script consisting of a lot of helper functions to run analysis on pandas dataframes and then one main function that utilizes my functions to get some results.
The issue is that most or the function inputs are just names of the columns in the dataframe (because they are taken as input from users) and this leads to a lot of functions have partially the same input.
What I wonder is if it possible to have a dict containing all my parameters and simply pass the dict to every function without modifying the dict? That is, each function would have to ignore the keywords in the dict that are not an input to that particular function.
To give an example say I have this function.
def func1(a, b, c):
return a + b * c
And I have this dict containing the inputs to all my functions.
input_dict = {'a': 1,
'b': 2,
'c': 3,
'd': 4}
If I call this function with this dict as input like this:
value = func1(**input_dict)
I will get an error because of the unexpected argument d.
Any workaround here that doesnt involve altering the dict? My code works but I would love to get away from having so many repeated input arguments everywhere.
Thanks in advance :)
What I wonder is if it possible to have a dict containing all my parameters and simply pass the dict to every function without modifying the dict?
That's a pretty good summary of the use case for a class.
from operator import attrgetter
class Foo:
def __init__(self, a, b, c, d):
self.a = a
self.b = b
self.c = c
self.d = d
def func1(self):
a, b, c = attrgetter('a', 'b', 'c')(self)
return a + b * c
f = Foo(1, 2, 3, 4)
value = f.func1()
The instance of your class replaces input_dict. Each helper
function becomes an instance method, with its definition using
only those instance attributes it needs.
My use of attrgetter is just a suggestion for helping migrate your existing functions into methods, as it doesn't require scattering a bunch
of references to self throughout. You can define func1 simply as
def func1(self):
return self.a + self.b * self.c
class Main(object):
def __init__(self, config):
selt.attributes = config
def return_new_copy(self, additional_attributes):
addtional_attributes.update(self.attributes)
return Main(additional_attributes)
I want to update the instance attributes and return a new instance of the same class. I guess I am trying to find out if the above code is Pythonic or if it's a dirty approach. I can't use classmethod for several reasons not mentioned here. Is there another recommended approach.
Your return_new_copy modifies the parameter passed in which is probably undesirable. It also overrides in the wrong direction (giving precedence to self.attributes)
I'd write it as follows:
def return_new_copy(self, additional_attributes):
# python<3.5 if there are only string keys:
# attributes = dict(self.attributes, **additional_attributes)
# python<3.5 if there are non-string keys:
# attributes = self.attributes.copy()
# attributes.update(additional_attributes)
# python3.5+
attributes = {**self.attributes, **additional_attributes}
return type(self)(attributes)
A few subtleties:
- I make sure to copy both the input attributes and the self attributes
- I merge the additional attributes on top of the self attributes
If you're looking for something to do this automatically, you might want to check out namedtuple
For example:
>>> C = collections.namedtuple('C', ('a', 'b'))
>>> x = C(1, 2)
>>> x
C(a=1, b=2)
>>> y = x._replace(b=3)
>>> y
C(a=1, b=3)
>>> x
C(a=1, b=2)
We say classes are mutable in Python which means you can using references we can change the values that will be reflected in object. For example,
>>> A = [1, 2, 3]
>>> B = A
>>> B[2] = 5
>>> A
[1, 2, 5]
Here I can change the values of A object using B because list is a mutable type. My question is why can't I change the attributes of a class below using same concept:
class C:
apple = 2
def __init__(self):
self.dangerous = 2
D = C # D is pointing to same class C
D().dangerous = 5 # changing the value of class attribute D
D().apple = 3 # changing the value of apple here
print D().apple
print D().dangerous
OUTPUT:
2
2
Could anyone explain why the output is 2 and 2 but not 3 and 5 since we are saying that the class is a mutable type.
UPDATE : Referring to the answer by #zxq9, if you see the below diagram when do D=C, D is actually pointing to the same class rather a new object as you have described. Could you explain this:
Each time you place parens after a class, you are constructing a new instance object of the class. So the things you printed were brand-spanking new and did not reflect the short-lived assignments you had made previously.
Here is an example (expanded to cover the underlying reference to class C):
>>> class C:
... red = 2
... def __init__(self):
... self.blue = 2
...
>>> C.red
2
>>> C.blue
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'C' has no attribute 'blue'
>>> C().red
2
>>> C().blue
2
>>> #OOOOH!
...
>>> z = C()
>>> z.red
2
>>> z.blue
2
>>> D = C
>>> D.red
2
>>> D().red
2
>>> D().red = "over 9000!"
>>> D.red
2
>>> D.red = "No, really over 9000!"
>>> D.red
'No, really over 9000!'
>>> C.red
'No, really over 9000!'
>>> #OOOOOOHHHH!
...
Note that we did change the class directly when I assigned D.red = "No, really over 9000!" -- because that was referencing the class definition itself, not an instantiated object created from it. Note also that assigning an attribute of D (a copy) changed the attribute of C (the original) because in many (but not all) cases Python makes such assignments by reference, meaning that D is really an alias of C, not copy of the underlying structure. Read up on Python's deepcopy() method for more about that particularly startling detail.
Walk through the example code carefully, note the difference between referencing ClassName and calling ClassName(). The first is a reference via a variable name to a class definition -- a blueprint for generating instance objects that carries a constructor function __init__() with it. The second is an invokation of __init__() whose return value is an instance object of the class within which it is defined.
This is also why you can do things like this:
def some_fun(another_fun, value):
another_fun(value)
def foo(v):
return v + v
def bar(v):
return v * v
some_fun(foo, 5)
some_fun(bar, 5)
This feature lends Python a high degree of flexibility in building functional abstractions. (Now if only it had tail-call elimination...)
It is an interesting example.
The line D().dangerous = 5 will change the attribute "dangerous" of the instance D(); But the line print D().dangerous print out the attribute "dangerous" of ANOTHER instance D().
The line D().apple = 3 will create an attribute "apple" in the instance D() since this instance does not have the attribute "apple".
The line print D().apple will print out the attribute "apple" of the class D since the instance D() does not have the attribute "apple".
One way to change the attribute "apple" of the class through its instance is by using D().__class__.apple=3
Subclassing frozenset and set doesn't seem to work the same when it comes to iterables. Try to run the following MWE:
class MonFrozenSet(frozenset):
def __new__(self, data):
super(MonFrozenSet,self).__init__(data)
return self
class MonSet(set):
def __init__(self, data):
super(MonSet,self).__init__(data)
x=(1,2,3,4)
A=MonSet(x)
B=MonFrozenSet(x)
for y in A: #Works
print y
for y in B: #Doesn't work
print y
The second for returns:
for y in B:
TypeError: 'type' object is not iterable
Any idea on how I can solve this?
If you are asking yourselves why I would like to use frozenset, the anwer is that I am trying to create a set of sets of tuples. The sets of tuples will be frozenset and the set of sets of tuples will be a set.
I use Python-2.7
When overriding __new__ you need to call the superclass's __new__, not its __init__. Also, you need to pass self (better named cls), since __new__ is a classmethod. Also, you need to return the result, since __new__ actually creates an object, it doesn't modify self. So:
class MonFrozenSet(frozenset):
def __new__(cls, data):
return super(MonFrozenSet,cls).__new__(cls, data)
Then:
>>> a = MonFrozenSet([1, 2, 3])
>>> for item in a:
... print item
1
2
3