0

I've got a class of the form:

class MyClass(object):
  def curves(self):
    def plot(self):
       plot a graph
       return something
    return a pd.DataFrame

What I want to do is define something I can call with instance_of_my_class.curves.plot()

Do I need to define curves as an object to make this possible? I'm looking for the shortest way to do it, as this is syntactic sugar only.

Thanks.

cjm2671
  • 18,348
  • 31
  • 102
  • 161
  • 1
    If you want to call `instance_of_my_class.curves.plot()` it has to be an object, or at least something with settable attributes. Currently, your `plot()` is only defined inside the environment of the `curves()` function when it is called. ie.`plot()` is only created when you call `curves()`, and only accessible within `curves()` – Eric Jul 29 '16 at 08:08
  • Is `plot` using any of the variables within `curves`, e.g. `self` or whatever else there might be that you don't show in this short example? If so, what should be the value of those variables when called "outside" of `curves`? If not, why define it within `curves` in the first place? – tobias_k Jul 29 '16 at 08:09
  • @jojo Class methods don’t get the instance passed? – poke Jul 29 '16 at 08:43
  • @jojo And how does that help when `plot` is supposed to act on the `MyClass` instance? – poke Jul 29 '16 at 08:54
  • @poke: no one said it's supposed to, no?! But you can access the class instance `MyClass` from within `plot`, no problemo. Just don't try to access `MyClass` objects as this might lead to problems. – j-i-l Jul 29 '16 at 09:08

1 Answers1

0

In order to add a level of hierarchy, curves needs to be an actual object, yes. There is no difference between foo.curves.plot() and the following:

c = foo.curves
c.plot()

So foo.curves needs to be an object that has a plot method.

Also, since the method is called on the curves object, the method will be bound to that object. So unless you set it up that way, the curves object will not have access to your actual class.

You could pass the instance in the curves constructor though:

class Curves (object):
    def __init__ (self, parent):
        self.parent = parent
    def plot (self):
        self.parent._plot()

class MyClass (object):
    def __init__ (self):
        self.curves = Curves(self)
    def _plot (self):
        print('Actual plot implementation')

Then you can use it as foo.curves.plot():

>>> foo = MyClass()
>>> foo.curves.plot()
Actual plot implementation

You could also automate this a bit by using a descriptor for curves. For example, this is a possible solution:

class Accessor (object):
    def __init__ (self, prefix = ''):
        self.prefix = prefix
    def __get__ (self, instance, owner):
        return AccessorDelegate(instance, self.prefix)

class AccessorDelegate (object):
    def __init__ (self, instance, prefix):
        self.instance = instance
        self.prefix = prefix
    def __getattr__ (self, name):
        return getattr(self.instance, self.prefix + name)

The benefit is obviously that you only need to define those a single time and then they’ll work for all your classes. You would use it like this in your class:

class MyClass (object):
    curves = Accessor('_curves_')

    def _curves_plot(self):
        print('Implementation of curves.plot')

Used exactly as above:

>>> foo = MyClass()
>>> foo.curves.plot()
Implementation of curves.plot
Community
  • 1
  • 1
poke
  • 369,085
  • 72
  • 557
  • 602