Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.python > #109986 > unrolled thread
| Started by | Rob Gaddi <rgaddi@highlandtechnology.invalid> |
|---|---|
| First post | 2016-06-15 17:37 +0000 |
| Last post | 2016-06-16 12:58 -0700 |
| Articles | 11 — 6 participants |
Back to article view | Back to comp.lang.python
Bulk Adding Methods Pythonically Rob Gaddi <rgaddi@highlandtechnology.invalid> - 2016-06-15 17:37 +0000
Re: Bulk Adding Methods Pythonically Random832 <random832@fastmail.com> - 2016-06-15 14:21 -0400
Re: Bulk Adding Methods Pythonically Rob Gaddi <rgaddi@highlandtechnology.invalid> - 2016-06-15 18:39 +0000
Re: Bulk Adding Methods Pythonically Steven D'Aprano <steve@pearwood.info> - 2016-06-16 11:38 +1000
Re: Bulk Adding Methods Pythonically Rob Gaddi <rgaddi@highlandtechnology.invalid> - 2016-06-21 17:04 +0000
Re: Bulk Adding Methods Pythonically Ethan Furman <ethan@stoneleaf.us> - 2016-06-15 12:03 -0700
Re: Bulk Adding Methods Pythonically lists@juliensalort.org (Julien Salort) - 2016-06-16 17:53 +0200
Re: Bulk Adding Methods Pythonically Steven D'Aprano <steve@pearwood.info> - 2016-06-17 03:36 +1000
Re: Bulk Adding Methods Pythonically Lawrence D’Oliveiro <lawrencedo99@gmail.com> - 2016-06-15 18:59 -0700
Re: Bulk Adding Methods Pythonically Random832 <random832@fastmail.com> - 2016-06-16 12:15 -0400
Re: Bulk Adding Methods Pythonically Ethan Furman <ethan@stoneleaf.us> - 2016-06-16 12:58 -0700
| From | Rob Gaddi <rgaddi@highlandtechnology.invalid> |
|---|---|
| Date | 2016-06-15 17:37 +0000 |
| Subject | Bulk Adding Methods Pythonically |
| Message-ID | <njs3nv$b19$1@dont-email.me> |
I've got a whole lot of methods I want to add to my Channel class, all
of which following nearly the same form. The below code works, but
having to do the for loop outside of the main class definition feels
kludgey. Am I missing some cleaner answer? I thought about just
checking for all of it in __getattr__, but that's hacky too and doesn't
docstring nicely. Python 3.4
class Channel:
# Lots of stuff in here.
...
# Add functions for a zillion different single channel measurements to
# the Channel object. Relative measurements are more complex, and need to be
# handled separately.
for fnname, measparam in (
('frequency', 'FREQ'), ('falltime', 'FTIM'), ('negduty', 'NDUT'),
('negwidth', 'NWID'), ('overshoot', 'OVER'), ('duty', 'PDUT'),
('period', 'PER'), ('preshoot', 'PRE'), ('width', 'PWID'),
('amplitude', 'VAMP'), ('mean', 'VAVG'), ('base', 'VBAS'),
('max', 'VMAX'), ('min', 'VMIN'), ('ptp', 'VPP'),
('rms', 'VRMS'), ('top', 'VTOP')):
def measmaker(p):
def inner(self, cursorarea=False):
region = 'CREG' if cursorarea else 'SCR'
return float(self.scope.query(':MEAS:AREA {reg};{meas}? {chan}'.format(
reg = region, meas = p, chan = self.name
)))
inner.__name__ = fnname
inner.__doc__ = "Channel {} measurement".format(fnname)
return inner
setattr(Channel, fnname, measmaker(measparam))
--
Rob Gaddi, Highland Technology -- www.highlandtechnology.com
Email address domain is currently out of order. See above to fix.
[toc] | [next] | [standalone]
| From | Random832 <random832@fastmail.com> |
|---|---|
| Date | 2016-06-15 14:21 -0400 |
| Message-ID | <mailman.80.1466014920.2288.python-list@python.org> |
| In reply to | #109986 |
On Wed, Jun 15, 2016, at 13:37, Rob Gaddi wrote:
> I've got a whole lot of methods I want to add to my Channel class, all
> of which following nearly the same form. The below code works, but
> having to do the for loop outside of the main class definition feels
> kludgey. Am I missing some cleaner answer?
Inside the class definition you can add things to locals() [it returns
the actual dictionary being used in class preparation], but I don't know
if this can be relied on or is an implementation detail of CPython.
Anyone know?
But anyway, instead of using a loop, why not just define each one on its
own line:
def mkmeasure(fnname, measparam):
...
class Channel:
frequency = mkmeasure('frequency', 'FREQ')
falltime = mkmeasure('falltime', 'FTIM')
[toc] | [prev] | [next] | [standalone]
| From | Rob Gaddi <rgaddi@highlandtechnology.invalid> |
|---|---|
| Date | 2016-06-15 18:39 +0000 |
| Message-ID | <njs7cc$pd4$1@dont-email.me> |
| In reply to | #109988 |
Random832 wrote:
> On Wed, Jun 15, 2016, at 13:37, Rob Gaddi wrote:
>> I've got a whole lot of methods I want to add to my Channel class, all
>> of which following nearly the same form. The below code works, but
>> having to do the for loop outside of the main class definition feels
>> kludgey. Am I missing some cleaner answer?
>
> Inside the class definition you can add things to locals() [it returns
> the actual dictionary being used in class preparation], but I don't know
> if this can be relied on or is an implementation detail of CPython.
> Anyone know?
>
> But anyway, instead of using a loop, why not just define each one on its
> own line:
>
> def mkmeasure(fnname, measparam):
> ...
>
> class Channel:
> frequency = mkmeasure('frequency', 'FREQ')
> falltime = mkmeasure('falltime', 'FTIM')
Thought about it, but whenever I'm dropping 20-someodd of those there's
inevitably some place where I screw up the replication of the quoted
name and the bound name.
--
Rob Gaddi, Highland Technology -- www.highlandtechnology.com
Email address domain is currently out of order. See above to fix.
[toc] | [prev] | [next] | [standalone]
| From | Steven D'Aprano <steve@pearwood.info> |
|---|---|
| Date | 2016-06-16 11:38 +1000 |
| Message-ID | <5762030b$0$1617$c3e8da3$5496439d@news.astraweb.com> |
| In reply to | #109989 |
On Thu, 16 Jun 2016 04:39 am, Rob Gaddi wrote:
>> class Channel:
>> frequency = mkmeasure('frequency', 'FREQ')
>> falltime = mkmeasure('falltime', 'FTIM')
>
> Thought about it, but whenever I'm dropping 20-someodd of those there's
> inevitably some place where I screw up the replication of the quoted
> name and the bound name.
So? You have unit tests, right? Add one more test that checks the names. You
can even add that to your class definition code:
NAMES = ('frequency', 'falltime')
THINGIES = ('FREQ', 'FTIM')
class Channel:
for name, thingy in zip(NAMES, THINGIES):
locals()[name] = mkmeasure(name, thingy)
assert all(getattr(Channel, name).__name__ == name for name in NAMES)
But at the point that you have 20+ almost identical methods, you should take
that as a cold-smell. Why do you have so many almost identical methods?
(That doesn't mean it is *necessarily* wrong, only that you have to think
carefully about whether it is or not.)
--
Steven
[toc] | [prev] | [next] | [standalone]
| From | Rob Gaddi <rgaddi@highlandtechnology.invalid> |
|---|---|
| Date | 2016-06-21 17:04 +0000 |
| Message-ID | <nkbs3a$mo5$1@dont-email.me> |
| In reply to | #110003 |
Steven D'Aprano wrote:
> On Thu, 16 Jun 2016 04:39 am, Rob Gaddi wrote:
>
>>> class Channel:
>>> frequency = mkmeasure('frequency', 'FREQ')
>>> falltime = mkmeasure('falltime', 'FTIM')
>>
>> Thought about it, but whenever I'm dropping 20-someodd of those there's
>> inevitably some place where I screw up the replication of the quoted
>> name and the bound name.
>
> So? You have unit tests, right? Add one more test that checks the names. You
> can even add that to your class definition code:
>
> NAMES = ('frequency', 'falltime')
> THINGIES = ('FREQ', 'FTIM')
>
> class Channel:
> for name, thingy in zip(NAMES, THINGIES):
> locals()[name] = mkmeasure(name, thingy)
>
> assert all(getattr(Channel, name).__name__ == name for name in NAMES)
>
>
> But at the point that you have 20+ almost identical methods, you should take
> that as a cold-smell. Why do you have so many almost identical methods?
>
> (That doesn't mean it is *necessarily* wrong, only that you have to think
> carefully about whether it is or not.)
>
The context is that I've got an oscilloscope here on my desk with a USB
cable around to my PC. There are a slew of different measurements that
I can request it make of the waveform on a given channel (i.e. the cable
connected to the front-panel connector marked "2"). I send it ASCII
commands like ':MEAS:VAMP? CHAN1' and it sends me ASCII replies like
'1.033e-01'. I'm trying to wrap an API around all this that allows me
to treat channels as Channels and ask questions like:
0.1 < chan.amplitude() < 0.2
I could just implement all that as a lookup in __getattr__, I suppose.
The efficiency hit would be invisible next to the time spent doing all
that I/O. But it feels hacky compared to having inspectable methods
with docstrings, like the sort of thing that future me will want to take
past me behind the woodshed for.
--
Rob Gaddi, Highland Technology -- www.highlandtechnology.com
Email address domain is currently out of order. See above to fix.
[toc] | [prev] | [next] | [standalone]
| From | Ethan Furman <ethan@stoneleaf.us> |
|---|---|
| Date | 2016-06-15 12:03 -0700 |
| Message-ID | <mailman.81.1466017340.2288.python-list@python.org> |
| In reply to | #109986 |
On 06/15/2016 10:37 AM, Rob Gaddi wrote:
> I've got a whole lot of methods I want to add to my Channel class, all
> of which following nearly the same form. The below code works, but
> having to do the for loop outside of the main class definition feels
> kludgey. Am I missing some cleaner answer? I thought about just
> checking for all of it in __getattr__, but that's hacky too and doesn't
> docstring nicely. Python 3.4
If I understand correctly:
- you are creating one class (not several similar classes)
- this class has several very similar functions
- you (understandably!) don't want to write out these very
similar functions one at a time
If that is all correct, then, as Random suggested, move that loop into
the class body and use locals() [1] to update the class dictionary.
Just make sure and delete any temporary variables.
Your other option is to put the code in a class decorator.
--
~Ethan~
[1] https://docs.python.org/3/library/functions.html#locals
Yes, returning the class namespace is a language gaurantee.
[toc] | [prev] | [next] | [standalone]
| From | lists@juliensalort.org (Julien Salort) |
|---|---|
| Date | 2016-06-16 17:53 +0200 |
| Message-ID | <1moy6yz.d13evh1ofkjsiN%lists@juliensalort.org> |
| In reply to | #109991 |
Ethan Furman <ethan@stoneleaf.us> wrote: > If that is all correct, then, as Random suggested, move that loop into > the class body and use locals() [1] to update the class dictionary. > Just make sure and delete any temporary variables.[ [...] > [1] https://docs.python.org/3/library/functions.html#locals > Yes, returning the class namespace is a language gaurantee. But that says: "Note The contents of this dictionary should not be modified; changes may not affect the values of local and free variables used by the interpreter." -- Julien Salort Entia non sunt multiplicanda praeter necessitatem http://www.juliensalort.org
[toc] | [prev] | [next] | [standalone]
| From | Steven D'Aprano <steve@pearwood.info> |
|---|---|
| Date | 2016-06-17 03:36 +1000 |
| Message-ID | <5762e384$0$1618$c3e8da3$5496439d@news.astraweb.com> |
| In reply to | #110033 |
On Fri, 17 Jun 2016 01:53 am, Julien Salort wrote: > Ethan Furman <ethan@stoneleaf.us> wrote: > >> If that is all correct, then, as Random suggested, move that loop into >> the class body and use locals() [1] to update the class dictionary. >> Just make sure and delete any temporary variables.[ > [...] >> [1] https://docs.python.org/3/library/functions.html#locals >> Yes, returning the class namespace is a language gaurantee. > > But that says: > "Note The contents of this dictionary should not be modified; changes > may not affect the values of local and free variables used by the > interpreter." That only applies to locals() inside a function. The intent of locals() inside a class is to be writable, and if the docs don't explicitly make that guarantee, they should. http://bugs.python.org/issue27335 -- Steven
[toc] | [prev] | [next] | [standalone]
| From | Lawrence D’Oliveiro <lawrencedo99@gmail.com> |
|---|---|
| Date | 2016-06-15 18:59 -0700 |
| Message-ID | <c852685e-4371-43e1-ad31-64e6000abfb8@googlegroups.com> |
| In reply to | #109986 |
On Thursday, June 16, 2016 at 5:37:14 AM UTC+12, Rob Gaddi wrote: > I've got a whole lot of methods I want to add to my Channel class, all > of which following nearly the same form. The below code works, but > having to do the for loop outside of the main class definition feels > kludgey. No, that’s fine. Table-driven programming is a good technique, and I’ve done this sort of thing myself. > for fnname, measparam in ( > ... > def measmaker(p): Since this function makes no direct reference to “fnname” or “measparam”, why not move the definition to before the loop so it gets executed just once? > def inner(self, cursorarea=False): > ... > return inner And there, ladies and gentlemen, you see the benefits of functions as first-class objects. :) > setattr(Channel, fnname, measmaker(measparam)) I would follow the loop with a “del fnname, measparam, measmaker", just to avoid polluting your module namespace with leftover debris.
[toc] | [prev] | [next] | [standalone]
| From | Random832 <random832@fastmail.com> |
|---|---|
| Date | 2016-06-16 12:15 -0400 |
| Message-ID | <mailman.94.1466093711.2288.python-list@python.org> |
| In reply to | #109986 |
On Wed, Jun 15, 2016, at 15:03, Ethan Furman wrote: > [1] https://docs.python.org/3/library/functions.html#locals > Yes, returning the class namespace is a language gaurantee. How do you get a guarantee from that text? I don't see any definition asserting that the "current local symbol table" is the class namespace (more specifically, the object returned by (metaclass).__prepare__, which will be copied into [not used as, for normal types] (class).__dict__). We *know* that "representing the current local symbol table" can and often does mean "copied from the local symbol table which is not a in fact a dictionary" rather than "being the local symbol table which is a real dictionary object" (that's *why*, after all, updating locals() doesn't work in the general case), nor does it mention any exemption to the warning about updating it. If there's a guarantee of this, it's somewhere else.
[toc] | [prev] | [next] | [standalone]
| From | Ethan Furman <ethan@stoneleaf.us> |
|---|---|
| Date | 2016-06-16 12:58 -0700 |
| Message-ID | <mailman.95.1466107159.2288.python-list@python.org> |
| In reply to | #109986 |
On 06/16, Random832 wrote: > On Wed, Jun 15, 2016, at 15:03, Ethan Furman wrote: >> [1] https://docs.python.org/3/library/functions.html#locals >> Yes, returning the class namespace is a language gaurantee. > > How do you get a guarantee from that text? Oops, my bad -- the gaurantee is in the vars() description on the same page... although that still isn't very clear about during-class-construction; okay, I'll have to chime in on the issue Steven opened. -- ~Ethan~
[toc] | [prev] | [standalone]
Back to top | Article view | comp.lang.python
csiph-web