Create hierarchical python object where arguments to methods are from hierarchy - python

There is an existing module I use containing a class that has methods with string arguments that take the form:
existing_object.existing_method("arg1")
or
existing_object.existing_method("arg1:arg2")
The arguments are in a hierarchical structure.
I would like to create a module that objectifies the arguments and makes them methods of the class of the imported module such that use would look like this:
my_object.arg1.my_method()
or
my_object.arg1.arg2.my_method()
my_method() would call existing_method() while passing it the "arg1:arg2" as an argument.
If someone could point me in the right direction to get started I'd appreciate it.

You can do this with a custom __getattr__ that returns special method caller instances:
class MethodCaller(object):
def __init__(self, args, parent):
self.args = args
self.parent = parent
def __getattr__(self, name):
return MethodCaller(self.args + (name,), self.parent)
def my_method(self):
return self.parent.existing_method(':'.join(self.args))
class MyClass(object):
def __getattr__(self, name):
return MethodCaller((name,), self)
def existing_method(self, arg):
print arg
Example:
>>> MyClass().arg1.my_method()
arg1
>>> MyClass().arg1.arg2.my_method()
arg1:arg2
>>> MyClass().foo.bar.my_method()
foo:bar

Thinking about this more clearly I realized that what I really wanted was to be able to use the IPython introspection of modules to navigate the hierarchy. This meant that I simply needed to create objects like this:
class Foo():
def __init__(self, arg):
self.arg = arg
def my_method(self.arg)
arg1 = Foo("arg1")
arg1.arg2 = Foo("arg1:arg2")

Related

Correct way of returning new class object (which could also be extended)

I am trying to find a good way for returning a (new) class object in class method that can be extended as well.
I have a class (classA) which has among other methods, a method that returns a new classA object after some processing
class classA:
def __init__(): ...
def methodX(self, **kwargs):
process data
return classA(new params)
Now, I am extending this class to another classB. I need methodX to do the same, but return classB this time, instead of classA
class classB(classA):
def __init__(self, params):
super().__init__(params)
self.newParams = XYZ
def methodX(self, **kwargs):
???
This may be something trivial but I simply cannot figure it out. In the end I dont want to rewrite the methodX each time the class gets extended.
Thank you for your time.
Use the __class__ attribute like this:
class A:
def __init__(self, **kwargs):
self.kwargs = kwargs
def methodX(self, **kwargs):
#do stuff with kwargs
return self.__class__(**kwargs)
def __repr__(self):
return f'{self.__class__}({self.kwargs})'
class B(A):
pass
a = A(foo='bar')
ax = a.methodX(gee='whiz')
b = B(yee='haw')
bx = b.methodX(cool='beans')
print(a)
print(ax)
print(b)
print(bx)
class classA:
def __init__(self, x):
self.x = x
def createNew(self, y):
t = type(self)
return t(y)
class classB(classA):
def __init__(self, params):
super().__init__(params)
a = classA(1)
newA = a.createNew(2)
b = classB(1)
newB = b.createNew(2)
print(type(newB))
# <class '__main__.classB'>
I want to propose what I think is the cleanest approach, albeit similar to existing answers. The problem feels like a good fit for a class method:
class A:
#classmethod
def method_x(cls, **kwargs):
return cls(<init params>)
Using the #classmethod decorator ensures that the first input (traditionally named cls) will refer to the Class to which the method belongs, rather than the instance.
(usually we call the first method input self and this refers to the instance to which the method belongs)
Because cls refers to A, rather than an instance of A, we can call cls() as we would call A().
However, in a class that inherits from A, cls will instead refer to the child class, as required:
class A:
def __init__(self, x):
self.x = x
#classmethod
def make_new(cls, **kwargs):
y = kwargs["y"]
return cls(y) # returns A(y) here
class B(A):
def __init__(self, x):
super().__init__(x)
self.z = 3 * x
inst = B(1).make_new(y=7)
print(inst.x, inst.z)
And now you can expect that print statement to produce 7 21.
That inst.z exists should confirm for you that the make_new call (which was only defined on A and inherited unaltered by B) has indeed made an instance of B.
However, there's something I must point out. Inheriting the unaltered make_new method only works because the __init__ method on B has the same call signature as the method on A. If this weren't the case then the call to cls might have had to be altered.
This can be circumvented by allowing **kwargs on the __init__ method and passing generic **kwargs into cls() in the parent class:
class A:
def __init__(self, **kwargs):
self.x = kwargs["x"]
#classmethod
def make_new(cls, **kwargs):
return cls(**kwargs)
class B(A):
def __init__(self, x, w):
super().__init__(x=x)
self.w = w
inst = B(1,2).make_new(x="spam", w="spam")
print(inst.x, inst.w)
Here we were able to give B a different (more restrictive!) signature.
This illustrates a general principle, which is that parent classes will typically be more abstract/less specific than their children.
It follows that, if you want two classes that substantially share behaviour but which do quite specific different things, it will be better to create three classes: one rather abstract one that defines the behaviour-in-common, and two children that give you the specific behaviours you want.

How to handle magic variables in a constructor of a Class

I need a magic variable in my class and started writing it
class myclass:
def __init__(self, name, *args):
self.name = name
????
def myFunc()
for i in args:
print(i)
I just could not find a proper explanation of how to write a class with magic variables in the constructor and use it later. Do I have to create a self. member out of it (and if so how) or can I neglect it and just use args as in myFunc ?
*args are not called magic variables, but arbitrary argument lists, or variadic arguments, and they are used to send arbitrary number of arguments to a function, and they are wrapped in a tuple like the example below
In [9]: def f(a,*args):
...: print(a)
...: print(args)
...:
In [10]: f(1,2,3,4)
1
(2, 3, 4)
So in order to access these variables, you would do what you do for any class instance variable, assign it via self.args = args and access them via self.args
Also note that we use camel-case for class names, so the class name changes to MyClass and snake-case for functions, so the function name changes to my_func
class MyClass:
def __init__(self, name, *args):
self.name = name
#Assigning variadic arguments using self
self.args = args
def my_func(self):
#Accessing variadic arguments using self
for i in self.args:
print(i)
obj = MyClass('Joe',1,2,3)
obj.my_func()
The output will be
1
2
3
If you assign the args to self.args, you can access them from other methods. However this method must accept the instance of the class as its first argument (see below how myFunc takes self now).
class myclass:
def __init__(self, name, *args):
self.name = name
self.args = args
def myFunc(self):
for i in self.args:
print(i)

Python instance Decorator

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.
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)
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.
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.
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.

How can I built in a trace ability to python calls?

Suppose I have some python code, e.g. some class defined somewhere, which cannot be modified
class MyClass(object):
def __init__(self, arg1, arg2):
do_something...
def foo(self):
do_something
Now I want to add a trace capability, e.g. some mechanism from outside that traces each and every method call for the above class. I want to be able to print out when e.g, __init__ has been called, or foo or even the __del__ method of MyClass.
Is this possible to do, and how is this done best?
Create a proxy class that wraps the original class and then delegates the work after printing a trace:
class MyClassProxy(object):
def __init__(*args, **kwds):
print 'Initializing'
self.o = MyClass(*args, **kwds)
def foo(self):
print 'Fooing'
return self.o.foo()
You can create a trace decorator and attach it to all the methods of the class instance or class definition as shown in decorate_methods function.
import functools
import inspect
import types
class TestClass(object):
def func1(self):
pass
def func2(self, a, b):
pass
def trace(func):
#functools.wraps(func)
def decorator(*args, **kwargs):
print "TRACE:", func.__name__, args, kwargs
return func(*args, **kwargs)
return decorator
def decorate_methods(obj, decorator):
for name, func in inspect.getmembers(obj):
if isinstance(func, types.MethodType):
setattr(obj, name, decorator(func))
# Apply the decorator to a class instance
test1 = TestClass()
decorate_methods(test1, trace)
test1.func1()
test1.func2('bar1', b='bar2')
# Apply the decorator to the class definition
decorate_methods(TestClass, trace)
test2 = TestClass()
test2.func1()
test2.func2('bar1', b='bar2')
The output of the script will be:
TRACE: func1 () {}
TRACE: func2 ('bar1',) {'b': 'bar2'}
TRACE: func1 (<__main__.TestClass object at 0x7f5a8d888150>,) {}
TRACE: func2 (<__main__.TestClass object at 0x7f5a8d888150>, 'bar1') {'b': 'bar2'}
Use decorator as shown below:
def call_trace(orig_func):
def decorated_func(*args, **kwargs):
print "========>In function: " + orig_func.__name__ + "<========"
orig_func(*args, **kwargs)
return decorated_func
Apply this decorator to trace the function. It prints function name before entering the function.
Ex:
#call_trace
def foo(self):
do_something
Hope it helps.
[Update]: You can use metaclass, only thing you got to change is to add "metaclass" parameter to your class as shown below. As you can see, below code applies "call_trace" decorator to every function in the class "ExBase".
I tried this out yesterday, it worked fine. I am also new to python.:)
def call_trace(orig_func):
def inner_func(*args, **kwargs):
print ("function name:" + str(orig_func.__name__))
orig_func(*args, **kwargs)
return inner_func
class ExMeta(type):
def __new__(cls, name, bases, attrs):
for attr in attrs:
if hasattr(attrs[attr], '__call__'):
attrs[attr] = call_trace(attrs[attr])
return type.__new__(cls, name, bases, attrs)
class ExBase(metaclass=ExMeta):
x = "x"
y = "y"
def __init__(self):
self.__name = "name"
def getname(self):
return self.__name
b = ExBase()
b.getname()
Get the code for OnlinePythonTutor from github.com/pgbovine/OnlinePythonTutor/tree/master/v3.
You don't need to bother with all the JS stuff. Extract the files into some directory. You can run your scripts using python /path/to/my/OnlinePythonTutor-master/v3/generate_json_trace my_script.py
This will give you basically everything your program is doing in a step by step manner. It will probably be overkill so if you want look into the source code and the underlying source in bdb http://docs.python.org/2/library/bdb.html. The docs for bdb are horrible so I'm having trouble figuring out what exactly is going on but I think this is a pretty cool problem, good luck.

Access to __init__ arguments

Is is possible to access the arguments which were passed to __init__, without explicitly having to store them?
e.g.
class thing(object):
def __init__(self, name, data):
pass # do something useful here
t = thing('test', [1,2,3,])
print t.__args__ # doesn't exist
>> ('test', [1,2,3])
The use-case for this is creating a super-class which can automatically store the arguments used to create an instance of a class derived from it, without having to pass all the arguments explicitly to the super's __init__. Maybe there's an easier way to do it!
No, you have to store them. Otherwise they are gone after __init__() returns, as all local variables.
If you don't want to pass all arguments on explicitly, you can use **kwargs:
class Base(object):
def __init__(self, name, data):
# store name and data
class Derived(Base):
def __init__(self, **kwargs):
Base.__init__(self, **kwargs)
Derived(name="Peter", data=42)
This is not entirely recommended, but here is a wrapper that automatically stores parameter variables:
from functools import wraps
def init_wrapper(f):
#wraps(f)
def wrapper(self, *args, **kwargs):
func_parameters = f.func_code.co_varnames[1:f.func_code.co_argcount]
#deal with default args
diff = len(func_parameters) - len(args)
if diff > 0:
args += f.func_defaults[-diff:]
#set instance variables
for pos, arg in enumerate(func_parameters):
print pos, arg
setattr(self, arg, args[pos])
f(self, *args, **kwargs) #not necessary to use return on __init__()
return wrapper
Usage:
class A(object):
#init_wrapper
def __init__(self, a, b, c):
print a + b + c
Example:
>>> a = A(1, 2, 3)
6
>>> a.a
1
>>> a.b
2
>>> a.c
3
In a word: No.
What you could do is:
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
If you find yourself needing to do this a lot, you could also use a decorator to abstract the task.
I think that you are looking for arbitrary argument lists and keyword arguments combined with super.__init__.
Give "Python's Super is nifty, but you can't use it" a read before you start down this path though.

Categories

Resources