2

I would like to decorate certain instance functions with a decorator from a "parent" instance, is there a way that I can use the instance to decorate the functions.

Here is the thought on what I need to do;

class Foo(object):

    def __init__(self):
        pass

    def set_configuration(self, function):
        def config(*args, **kwargs):
            # NOTE: this function needs to use instance variables.
            print 'foo ' + function()
        return config()

class Bar(object):

    def __init__(self, parent):
        self.parent = parent

    @self.parent.set_configuration
    def set_config_2(self)
        return 'bar'

foo = Foo()
foo.bar = Bar(foo)
foo.bar.set_config_2

EDIT:

Ok guys here is the actual issue, I have a device that i need to interact with. So a device may have several levels to it ie a device a has multiple interfaces and an interface may have multiple vlans attached. So the idea is that if I want to change a vlan on an interface, instead of building a full command I would like to allow the parent class to handle the building of it's level of the command. So I would like to just call the "change vlan" function and it will send it's part of the command to the next level to be wrapped and sent up the chain till it hits the device level and the full command is sent to the device.

class Device(object):

    def __init__(self):
        self.interfaces = list()
        self.ssh = ssh('blah')

    def set_device(self, function):
        self.ssh.command('setup commands')
        self.ssh.command(wrapped command here)
        self.ssh.command('exit commands')


class Interface(object):

    def __init__(self, name, parent):
        self.name
        self.parent
        self.vlan = Vlan('name')

    def set_interface(self):
        return self.name


class Vlan(object):

    def __init__(self, name, parent):
        self.name = name
        self.parent = parent

    def set_vlan(self):
        return self.name 

I hope this makes more sense. if not please let me know.

4
  • You would have to do it explicitly - @Foo.set_configuration. Neither self nor Bar are in scope during the class definition. Commented Jan 9, 2016 at 15:18
  • 1
    Why aren't you using inheritance? class Bar(Foo): Commented Jan 9, 2016 at 15:21
  • 1
    set_configuration does not have to be a method. Commented Jan 9, 2016 at 15:27
  • The reason that I am not using inheritance is that the may be several instances of "Bar" stored in the "Foo" instance and they will all need to use the instance of the "Foo parent" to decorate their functions. also to note the Foo decorator uses instance variables so cannot be a class method Commented Jan 9, 2016 at 15:31

4 Answers 4

1

No, you cannot use decorators here, because at definition time of Bar, parent is not known.

Simply use set_configuration with a argument:

class Foo(object):

    def __init__(self):
        pass

    def set_configuration(self, function):
        def config(*args, **kwargs):
            # NOTE: this function needs to use instance variables.
            print 'foo ' + function()
        return config

class Bar(object):

    def __init__(self, parent):
        self.parent = parent

    def set_config_2(self, args)
        def inner_function():
            return 'bar'
        return self.parent.set_configuration(inner_function)(args)


foo = Foo()
foo.bar = Bar(foo)
foo.bar.set_config_2(123)
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for this, this solution does work for this simple solution, but the actual code is going to be several levels deep, so Bar may have the same pattern inside it too, and what I would like to do is bubble up so that when a command is built the next level up is automatically wrapped around the called function. So that no matter what level you call a command it will automatically wrap the upper levels on to it.
@JohnDowling: this sounds like normal inheritance to me. Could you edit your question with the necessary details?
1

Python is a dynamic language so many things are possible. I'm making no comment about whether this is a good thing to do or not - and I really can't understand the purpose of your logic.

To make this possible you will need dynamically create the set_config_2 in Bar.__init__ as parent is unknown at the class definition time:

from types import MethodType
class Foo(object):
    def __init__(self):
        pass

    def set_configuration(self, f):
        def config(inst, *args, **kwargs):
            print('foo', f(inst, *args, **kwargs))
        return config

class Bar(object):
    def __init__(self, parent):
        self.parent = parent
        @self.parent.set_configuration
        def set_config_2(inst):
            return 'bar'
        self.set_config_2 = MethodType(set_config_2, self)

foo = Foo()
foo.bar = Bar(foo)
foo.bar.set_config_2()

Output:

foo bar

This is desperately ugly and there must be a better way of doing what you are attempting. Perhaps you can ask a different question explaining what you are trying to achieve.

Comments

0

Your decorator does not have to use instance methods, since that's the wrapping function config who needs them. Therefore, the decorator does not have to be a method. For example:

def set_configuration(func):
    @functools.wraps(func)  # copy function's metadata
    def wrapper(self, *args, **kwargs):
        # do whatever you want to fetch the config data
        return 'foo' + func(self, *args, **kwargs)

    return wrapper

That said, there likely is a more straightforward and explicit way, depending on what exactly you want.

Comments

0

I'm pretty sure you can do this without making the decorator an instance. Here are a couple ideas.

Invert the hierarchy

It seems to me like the hierarchy you have is backwards. My understanding:

  • Device is only providing the ssh instance
  • The common method you want to call is something the VLAN defines
  • The setup and exit commands are constants

By making the hierarchy go the other way, you can define the "change VLAN" method to access stuff from the lower levels that it needs.

class Device(object):
    def __init__(self):
        self.ssh = ssh('blah')

class Interface(object):

    def __init__(self, name, device):
        self.name
        self.device = device

class Vlan(object):
    def __init__(self, name, change_command, interface):
        self.name = name
        # How you actually store this command is completely up to you.
        # You might want to shove it in an abstract method
        # and subclass Vlan, but the point is make it part of the
        # Vlan somehow.
        self.change_command = change_command
        self.interface = interface

    def change_vlan(self):
        ssh = self.interface.device.ssh
        ssh.command('setup commands')
        ssh.command(self.change_command)
        ssh.command('exit commands')

device1 = Device()
device2 = Device()

interface1 = Interface('i1', device1)
interface2 = Interface('i2', device1)
interface3 = Interface('i3', device2)

vlans = [
    Vlan('v1', 'change 1', interface1)
    Vlan('v2', 'change 2', interface1)
    Vlan('v3', 'change 3', interface2)
    Vlan('v4', 'change 4', interface3)
]

This might not show exactly what you want to do, but hopefully it demonstrates how you can set this up with the hierarchy going the other way.

Make The decorator accept a Device

Alternatively, if you still think decorating is a better option, you can make the decorate accept the instances you need.

def ssh_command(device, function):
    def execute_ssh_command(*args, **kwargs):
        device.ssh.command('setup commands')
        device.ssh.command(wrapped command here)
        device.ssh.command('exit commands')
    # Note: no parentheses. You're returning the function itself
    return execute_ssh_command

class Interface(object):
    def __init__(self, name, parent):
        self.name
        self.parent
        self.vlan = Vlan('name')

    @ssh_command
    def set_interface(self):
        return self.name

Note you'll need to make a separate subclass per whatever thing uses the decorator.

Comments

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.