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'}
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 a class somewhat like following:
from joblib import Memory
import time
def find_static_methods(cls):
# to be implemented
pass
class A:
def __init__(self, cache_path: str):
self._memory = Memory(cache_path, verbose=0)
self._methods = {}
for name, method in find_static_methods(A):
self._methods[name] = self._memory.cache(method)
def __getattribute__(self, item):
if item in self._methods:
return self._methods[item]
return super(A, self).__getattribute__(item)
#staticmethod
def method1(a: int, b: int):
time.sleep(3)
return a + b
I'm trying to memoize method1 using joblib.Memory. But I don't know the cache_path in advance. Please help me with the implementation of find_static_methods here.
Here is another way:
A_attrs = A.__dict__
for k, v in A_attrs.items():
if isinstance(v, staticmethod):
print(k)
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
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)
Considering this sample scenario:
#!/usr/bin/python
import binascii
import cProfile
import re
class custom_str(str):
__strip_non_hex = re.compile(r'^[^:]+:|[^0-9A-Fa-f]')
def __new__(cls, value):
return super(custom_str, cls).__new__(cls, value)
#classmethod
def new(cls, value):
# creates a pure-hexadecimal string
return cls(re.sub(cls.__strip_non_hex, '', value))
class custom_type(custom_str):
def __new__(cls, value):
# here I've to use custom_str.new()
return super(custom_type, cls).__new__(cls, custom_str.new(value))
#classmethod
def new(cls, value):
return cls('hex:%s' % (binascii.hexlify(value)))
if __name__ == '__main__':
# tests
v = custom_str('666f6f')
assert v == '666f6f'
assert type(v) == custom_str
v = custom_str.new('66,6f,6f')
assert v == '666f6f'
assert type(v) == custom_str
v = custom_type('hex:66,6f,6f')
assert v == '666f6f'
assert type(v) == custom_type
v = custom_type.new('foo')
assert v == '666f6f'
assert type(v) == custom_type
# profiling
cProfile.run("custom_type.new('foo')", sort='call')
Code works, tests passes. I'm just wondering if I can avoid calling custom_str.__new__() twice.
If I change custom_type.__new__() to return custom_str.new(value) it works, but them it'll be of type custom_str instead of custom_type.
On other hand, if I change it to return super(custom_type, cls).new(value) it gets into infinite recursion.
_strip_non_hex = re.compile(r'^[^:]+:|[^0-9A-Fa-f]')
def _strip(string):
return re.sub(_strip_non_hex, '', value)
class custom_str(str):
#classmethod
def new(cls, value):
# creates a pure-hexadecimal string
return custom_str.__new__(cls, _strip(value))
class custom_type(custom_str):
def __new__(cls, value):
return super(custom_type, cls).__new__(cls, _strip(value))
#classmethod
def new(cls, value):
return cls('hex:%s' % (binascii.hexlify(value)))
Pull the non-hex-stripping logic out of new and into its own function to untangle the dependency graph.