1

I feel this is a very simple question but I can't find to seem a proper answer. Basically I have a list of functions call from a class named simulation:

simulation.addGroup("teapotarmy")
simulation.populateGroup(20)
simulation.addNode("input",INPUT)
simulation.addNode("output",OUTPUT);
simulation.connectNodes("input","output");
simulation.manipOutputNode("output", "group.xvel");
simulation.manipInputNode("input", 1, 0.05);

Is there a way to call those functions without having to repeat the class name everytime? Something along the line of:

(thethingIwant) simulation:
    addGroup("teapotarmy")
    populateGroup(20)
    addNode("input",INPUT)
    ...

I have done this in other programming languages but I haven't figured out the syntax in Python. I have a faint memory of it having something to do with the 'with' statement...? Thanks in advance.

Leon

leoncvlt
  • 385
  • 2
  • 5
  • 13
  • Do we talk about a class or an instance of a class? – Matthias Dec 02 '12 at 18:16
  • The `with` statement invokes a context manager where you can optionally assign a local name, a name that you still have to reference. So it won't really help you. There is no real way to do this, other than put the names in an implicit scope, such as the global scope. – Keith Dec 02 '12 at 18:20

4 Answers4

6

Simply, no. There is no (good, see my comment at the end) way to do this. The best you could do is assign it to another, shorter name:

s = simulation
s.addGroup("teapotarmy")
...

Which isn't too bad, although I'd argue the normal method is the one that is more readable.

As an extra, it's not strictly true that you can't do this. You could assign all of the methods of simulation to the local namespace programatically, however, this would be pretty confusing to follow, and I'd advise against it.

Example:

from contextlib import contextmanager
import inspect

class some_class:
    def test(self):
        print("test!")

@contextmanager
def map_to_local(inst, locals):
    methods = inspect.getmembers(inst, inspect.ismethod)
    for name, method in methods:
        locals[name] = method
    yield
    for name, method in methods:
        del locals[name]

inst = some_class()
with map_to_local(inst, locals()):
    test()

Note this is pretty fragile - you would have to be careful and do stuff like checking you are not overwriting values, checking values haven't been deleted before the context manager exits, etc... It's also pretty unclear what is going on.

tl;dr: Yes, it's possible, no, you shouldn't do it. Your current code is fine and clear.

Gareth Latty
  • 86,389
  • 17
  • 178
  • 183
  • 1
    @Stuart Context managers do not do this by default, see my edited in example of using a context manager to provide this kind of behaviour, but it's still a bad idea. – Gareth Latty Dec 02 '12 at 18:20
  • Unfortunately I don't think this will work in general: this approach fails in a function. `def f(): with map_to_local(etc.)` will generate a `NameError`. Assignment to `locals()` simply isn't guaranteed to work. – DSM Dec 02 '12 at 18:23
  • @DSM Yeah, I only tested it briefly. As much as I'm sure it'd be possible to make it work (you could make them all globals, I guess, which is even uglier), it's really a bad idea to begin with. I really just did it as it's always interesting to show how insanely flexible Python is. – Gareth Latty Dec 02 '12 at 18:24
  • That does look confusing indeed. Simply put, it's for an embedded C++ script that would be editable by the user, I wanted to save them the hassle of writing "simulation" all the time, but I guess I can save those refinements for later. Thanks for your time! – leoncvlt Dec 02 '12 at 18:24
  • You could make the context manager save locals() state and then restore it on __exit__... But yeah, this is pretty gnarly. – fmoo Dec 02 '12 at 18:25
  • 1
    @MegaLeon Maybe a better solution would be to have them write a file which you parse. – Gareth Latty Dec 02 '12 at 18:26
  • Yeah, that's probly the way I will go for now. – leoncvlt Dec 02 '12 at 18:55
2

To work with your existing class as it is currently designed, the usual solution is to use a shorter variable name:

s = simulation
s.addGroup("teapotarmy")
s.populateGroup(20)
s.addNode("input",INPUT)
s.addNode("output",OUTPUT)
s.connectNodes("input","output")
s.manipOutputNode("output", "group.xvel")
s.manipInputNode("input", 1, 0.05)

That said, an alternative solution is to slightly alter the class to have these methods return self. Then you can write:

(simulation
    .addGroup("teapotarmy")
    .populateGroup(20) 
    .addNode("input",INPUT)
    .addNode("output",OUTPUT)
    .connectNodes("input","output")
    .manipOutputNode("output", "group.xvel")
    .manipInputNode("input", 1, 0.05))

The normal Python style is to have mutating methods return None (to provide a hint that mutation occurred); however, returning self is the norm for APIs such as yours where it is common to apply a succession of transformations and state updates.

Raymond Hettinger
  • 216,523
  • 63
  • 388
  • 485
2

for each method you define in your class you can return the self object.

class schema:
    def __init__(self,name,age):
        self.name = name 
        self.age = age
    def giveName(self):
        print(self.name)
        return self
    def giveAge(self):
        print(self.age)
        return self

obj = schema(name="yo",age=15)
obj.giveName().giveAge()
Dharman
  • 30,962
  • 25
  • 85
  • 135
sai
  • 21
  • 1
1

The closest thing I can think of is taking advantage of the fact that Python's functions are also attributes of your class (callable attributes) and as such you can "get" them by name and call them...

#!/usr/bin/env python
# -*- coding: utf-8 -*-

class Simulation(object):
  def addGroup(self, groupToAdd):
    print "Group to add: %s" % groupToAdd

  def addNode(self, inputType, inputChannel):
    print "My inputs: %s, channel: %s" % (inputType, inputChannel)

if __name__ == "__main__":
  simulation = Simulation()
  functionsToCall = [
      ("addGroup", "teapotarmy"),
      ("addNode", "input", "INPUT"),
    ]
  for functionToCall in functionsToCall:
    getattr(simulation, functionToCall[0])(* functionToCall[1:])

But this probably turns your code more confusing than before. If someone else has to modify your code, it may complicate his task... quite a bit. :)

For more info: Callables, packing parameters.

Community
  • 1
  • 1
Savir
  • 17,568
  • 15
  • 82
  • 136
  • 1
    +1, this is basically what I was talking about in the comments to my answer about parsing a custom data format. Note that in 3.x, you can do ``for name, *args in functionsToCall:`` to avoid the indexing. – Gareth Latty Dec 02 '12 at 18:30