0

I wasn't able to figure out what to search for this, so if it's already out there, I apologize in advance, and would be happy to just take a search reference to somewhere else.

Some background: I'm working on some extra features for a python Excel library and one thing I'd like to be able to do is apply a style to a range of cells in a single call. Right now, our code permits us to do something like ws[1][1].font.bold = True to bold cell A1. We also have a range operator that permits us to do something like ws.range("A1:B2").value = 1 to set all the values in that range to 1.

What I'd like to do now is add the ability to do ws.range("A1:B2").font.bold = True, but I can't figure out how. range("A1:B2") is an object that doesn't explicitly reference the cells (just the two corners to save on space), but does not explicitly have the .font or .value attribute, rather it's intercepted via a @property decorator to apply appropriately.

The thing is, if I add an @property decorator to create a font property, I can return a new font object, but I have no way of getting this font object after the bold attribute has been modified to apply it to all the cells in the range.

I was thinking I could yield the font, then add the apply logic after the yield, but that doesn't work as yield returns a generator. The only other idea I can think of is to create an explicit .font object, then in the Range class, override .font.__setattr__ to listen for changes, but then I can't figure out how to get the original range from within __setattr__ that needs to be applied without having to create a third intermediary class. Not only does that seem rather awkward, it also seems incredibly unpythonic.

Is something like this possible without having to result to rather esoteric methods? I hope my explanation was clear and again, if it's already out there, feel free to point me to an existing reference -- I just didn't know what to search for.

4
  • You're almost there with your last idea. Create a .font object that holds a reference to the original Range. Then override __setattr__() in the .font to update styles of all cells in the range. Commented Oct 9, 2013 at 0:11
  • In fact you could probably do this generically if you wanted, by having Range return the same "fake" property for any attribute name, and have it delegate all __setattr__ calls to the original. Commented Oct 9, 2013 at 0:13
  • Ah, okay, that seems like it might work, but there's not a less esoteric way to do it? I feel like that would be incredibly confusing to maintain. Commented Oct 9, 2013 at 0:14
  • I'm not sure how this is an esoteric thing. You want a write to the font's attributes to be applied to all cells in a range. So, you intercept writes to font's attributes, and apply them to all cells in the correct range. It's really the most straightforward way to do it - you don't need to detect modifications on the .font or anything, you just write back things immediately. Commented Oct 9, 2013 at 0:16

1 Answer 1

1

To expand on my comment, you'd do something like:

class RangeFont(object):

    def __init__(self, range):
        self._range = range

    def __setattr__(self, name, value):
        if name.startswith('_'):
            super(RangeFont, self).__setattr__(name, value)
            return

        for cell in self._range: # assuming a range is iterable
            setattr(cell.font, name, value)


class Range(object):
    @property
    def font(self):
        return RangeFont(self)

A limitation of the above code is that it won't work for further levels of attributes, that is, you can't do ws.Range(...).font.foo.bar = 123. (That said you could extend this by having RangeFont.__getattr__ return a similar wrapper object.)

Sign up to request clarification or add additional context in comments.

2 Comments

okay, after giving up a couple times, I managed to implement this generically for deeper levels without having to make it too much more complicated: pastebin.com/raw.php?i=aAqmq554 :)
@kevmo314 Oh, derp, I didn't remember you need to work around the setattr override in __init__. Anyway, glad you got what you need.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.