class Obj:
def __init__(self, **kw):
# code
obj = Obj(a=5, b=10)
print(obj.a, obj.b) # 5 10
Is there a proven solution to this task?
You can do the following to assign attributes passed in **kw:
class Obj:
def __init__(self, **kw):
# in case of python 2, the following line is: for k, v in kw.iteritems():
for k, v in kw.items():
setattr(self, k, v)
and then use the way you mentioned in your post:
obj = Obj(a=5, b=10)
print(obj.a, obj.b) # 5 10
Related
I need a Python decorator to add any number of named attributes to the decorated callable.
For example:
#attributes(foo='this', bar='exactly') # <-- Implement this decorator!
def fun1(i):
return i * 2
assert fun1.foo == 'this'
assert fun1.bar == 'exactly'
There exists a similar question, the answers to which deal with setting a single attribute with a fixed name. It doesn't apply here for setting an arbitrary number of attributes.
This doesn't work:
def attributes(**kwargs):
def _inner(func):
for k, v in kwargs.items():
func.k = v
return func
return _inner
Here is a function that implements the decorator:
def attributes(**kwargs):
def _inner(func):
for k, v in kwargs.items():
setattr(func, k, v)
return func
return _inner
Alternatively, here is a class that implements the decorator:
class attributes:
def __init__(self, **kwargs):
self.kwargs = kwargs
def __call__(self, func):
for k, v in self.kwargs.items():
setattr(func, k, v)
return func
func.k = v sets foo.k, not foo.foo or foo.bar. To set an attribute with a dynamic name, use setattr instead:
setattr(func, k, v)
I have the following setup:
class A:
def __init__(self, **kwargs):
# Some variables initialized
for k, v in kwargs.items():
setattr(self, k, v)
class B(A):
def __init__(self, **kwargs):
A.__init__(self, **kwargs)
self._b = {}
for k, v in kwargs.items():
setattr(self, k, v)
#property
def b(self):
return self._b
#b.setter
def b(self, value):
self._b.update(value)
class C(B):
def __init__(self, **kwargs):
B.__init__(self, **kwargs)
# Some variables initialized
for k, v in kwargs.items():
setattr(self, k, v)
When I now create a new instance of C I get the following error:
AttributeError: 'C' object has no attribute '_b'
Now this makes sense since B._b hasn't been initialized when A.__init__(self, **kwargs) is being called. I can resolve this issue simply by re-ordering the B's initialization like so,
class B(A):
def __init__(self, **kwargs):
self._b = {}
A.__init__(self, **kwargs)
for k, v in kwargs.items():
setattr(self, k, v)
I'd like to understand if there is a recommended/best practice approach when I need to pass kwargs from child to parent classes during initialization? It seems to me like the following things would work,
Re-order the initialization like I have above
Assign kwargs in each child class then pop them and pass the remaining kwargs along to the parent initialization
Something better
Hoping to get some approaches for 3.
The issue you have is with these loops:
for k, v in kwargs.items():
setattr(self, k, v)
You have one in each class, and that means that every one of the classes is setting all the keyword arguments as attributes on self.
When that loop runs in A, it fails because B has a property that needs initializing before it can work.
As you noted in the question, a quick fix would be to make sure that B sets up its dictionary before it runs A.__init__:
class B(A):
def __init__(self, **kwargs):
_b = {} # set this up first
A.__init__(self, **kwargs) # before calling the superclass
for k, v in kwargs.items():
setattr(self, k, v)
But there's probably a better approach that would let you avoid the redundant loops. I'd suggest explicitly naming the keyword arguments you expect in each class. That way b will only be seen by the B class, not by A, nor C (except as part of kwargs).
class A:
def __init__(self, *, a): # a is keyword-only arg, no kwargs accepted here
self.a = a
class B(A):
def __init__(self, *, b, **kwargs):
super().__init__(**kwargs) # doesn't mess with b!
self._b = {}
self.b = b
#property
def b(self):
...
class C(B):
def __init__(self, *, c, **kwargs):
super().__init__(**kwargs)
self.c = c
Now you can call C(a="foo", b={1: 2}, c="bar") and each class will only pay attention to the attribute it cares about.
I'm attempting to write a more pythonic interaction with win32com.client for my own use so I can do things like:
with Presentation(close=True) as P:
table = P[0].tables[0]
table.cells.Shape.TextFrame.TextRange.Text= 'hello'
I've managed to get the above working (very satisfying) by overloading __getattr__ and __setattr__.
I want to interact with a powerpoint table as an array not a linear object so I created the CellRange object
This is from tables.py, which handles the array views of win32com.client tables.
from itertools import product
import numpy as np
from win32com.client import pywintypes
ALIGN_LABELS = 'bottom bottom_base middle top top_base mixed'.split()
ALIGN_LABELS_N = {k: i for i, k in enumerate(ALIGN_LABELS)}
class Win32Interface(object):
def __init__(self, win32_object):
super(Win32Interface, self).__setattr__('win32_object', win32_object)
def __setattr__(self, k, v):
setattr(self.win32_object, k, v)
def __getattr__(self, v):
return getattr(self.win32_object, v)
class Cell(Win32Interface):
def __repr__(self):
return self.Shape.TextFrame.TextRange.Text
#property
def text(self):
return self.Shape.TextFrame.TextRange.Text
#text.setter
def text(self, v):
setattr(self.Shape.TextFrame.TextRange, 'Text', v)
class CellRange(object):
def __init__(self, cell_array):
super(CellRange, self).__init__()
super(CellRange, self).__setattr__('cell_array', cell_array)
def _apply_map(self, f):
func = np.vectorize(f)
return func(self.cell_array)
def __getattr__(self, k):
try:
arr = self._apply_map(lambda x: getattr(x, k))
return CellRange(arr)
except (AttributeError, pywintypes.com_error):
return getattr(self.cell_array, k)
def __setattr__(self, k, v):
if hasattr(v, 'shape'):
assert self.shape == v.shape, 'mismatched shape'
for cell, value in zip(self.cell_array.ravel(), v.ravel()):
cell.__setattr__(k, value)
else:
self._apply_map(lambda x: setattr(x, k, v))
def __repr__(self):
return self.cell_array.__repr__()
Ignoring the Table object for the moment, I want to know why
cell_range = CellRange(cell_array)
cell_range.text = 'hello'
throws up a cannot be set error. The above calls __setattr__ which then calls _apply_map to set each element of the array, this calls Cell.__setattr__. Why can I do print cell_range.text but not cell_range.text = 'hello'?
Stumbled into the solution about 10 minutes after I posted!
The answer is to use Object's __setattr__ instead of Win32Interface's
So obvious!
class Win32Interface(object):
def __init__(self, win32_object):
super(Win32Interface, self).__setattr__('win32_object', win32_object)
def __setattr__(self, k, v):
if k in self.properties:
super(Win32Interface, self).__setattr__(k, v)
else:
setattr(self.win32_object, k, v)
def __getattr__(self, v):
return getattr(self.win32_object, v)
#property
def properties(self):
class_items = self.__class__.__dict__.iteritems()
return {k:v for k, v in class_items if isinstance(v, property) and k != 'properties'}
I want to create a class inheriting from dict type but could use case-insensitive key to visit the data. I implement a simple one but I don't think using instance.__dict__ variable is a proper approach.
Is there a better way to do this?
Here is my code:
class MyDict(dict):
def __init__(self, *args, **kwargs):
if args:
for k, v in args[0].iteritems():
self.__dict__.update({k.lower(): v})
def __getitem__(self, k):
return self.__dict__.get(k.lower())
def __setitem__(self, k, v):
self.__dict__.update({k.lower(): v})
def __delitem__(self, k):
self.__dict__.pop(k, None)
if __name__ == '__main__':
test_0 = MyDict({'naME': 'python', 'Age': 24})
print(test_0['name']) # return 'python'
print(test_0['AGE']) # return 24
test_1 = MyDict()
test_1['StaCk'] = 23
print(test_1['stack']) # return 23
print(test_1['STACK']) # return 23
Edit: See Janne Karila's link, that contains a better solution.
Instead of using self.__dict__, which has a special meaning unrelated to this being a dict, you should use super() to call the corresponding function on the superclass.
E.g.,
def __setitem__(self, k, v):
if hasattr(k, 'lower'):
k = k.lower()
return super(MyDict, self).__setitem__(k, v)
Is there a way that I can define __init__ so keywords defined in **kwargs are assigned to the class?
For example, if I were to initialize a ValidationRule class with ValidationRule(other='email'), the value for self.other should be added to the class without having to explicitly name every possible kwarg.
class ValidationRule:
def __init__(self, **kwargs):
# code to assign **kwargs to .self
I think somewhere on the stackoverflow I've seen such solution. Anyway it can look like:
class ValidationRule:
__allowed = ("other", "same", "different")
def __init__(self, **kwargs):
for k, v in kwargs.iteritems():
assert( k in self.__class__.__allowed )
setattr(self, k, v)
This class will only accept arguments with a whitelisted attribute names listed in __allowed.
This may not be the cleanest way, but it works:
class ValidationRule:
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
I think I prefer ony's solution because it restricts available properties to keep you out of trouble when your input comes from external sources.
You could do something like this:
class ValidationRule:
def __init__(self, **kwargs):
for (k, v) in kwargs.items():
setattr(self, k, v)
class ValidationRule:
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
You can set your kwargs arguments by updating __dict__ attribute of the instance.
class ValidationRule:
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
This could be considered nicer than updating __dict__:
class C:
def __init__(self, **kwargs):
vars(self).update(kwargs)
>>> c = C(a='a', b='b')
>>> c.a # returns 'a'
>>> c.b # returns 'b'
I found the above answers helpful and then refined:
class MyObj(object):
def __init__(self, key1=1, key2=2, key3=3):
for (k, v) in locals().iteritems():
if k != 'self':
setattr(self, k, v)
Test:
>>> myobj = MyObj(key1=0)
>>> print myobj.key1
0
And validation is also there:
>>> myobj = MyObj(key4=4)
TypeError: __init__() got an unexpected keyword argument 'key4'