Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #109986 > unrolled thread

Bulk Adding Methods Pythonically

Started byRob Gaddi <rgaddi@highlandtechnology.invalid>
First post2016-06-15 17:37 +0000
Last post2016-06-16 12:58 -0700
Articles 11 — 6 participants

Back to article view | Back to comp.lang.python


Contents

  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

#109986 — Bulk Adding Methods Pythonically

FromRob Gaddi <rgaddi@highlandtechnology.invalid>
Date2016-06-15 17:37 +0000
SubjectBulk 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]


#109988

FromRandom832 <random832@fastmail.com>
Date2016-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]


#109989

FromRob Gaddi <rgaddi@highlandtechnology.invalid>
Date2016-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]


#110003

FromSteven D'Aprano <steve@pearwood.info>
Date2016-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]


#110245

FromRob Gaddi <rgaddi@highlandtechnology.invalid>
Date2016-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]


#109991

FromEthan Furman <ethan@stoneleaf.us>
Date2016-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]


#110033

Fromlists@juliensalort.org (Julien Salort)
Date2016-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]


#110035

FromSteven D'Aprano <steve@pearwood.info>
Date2016-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]


#110006

FromLawrence D’Oliveiro <lawrencedo99@gmail.com>
Date2016-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]


#110034

FromRandom832 <random832@fastmail.com>
Date2016-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]


#110036

FromEthan Furman <ethan@stoneleaf.us>
Date2016-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