开发者

Mapping obj.method({argument:value}) to obj.argument(value)

I don't know if this will make sense, but...

I'm trying to dynamically assign methods to an object.

#translate this
object.key(value)
#into this
object.method({key:value})

To be more specific in my example, I have an object (which I didn't write), lets call it motor, which has some generic methods set, status and a few others. Some take a dictionary as an argument and some take a list. To change the motor's speed, and see the result, I use:

motor.set({'move_at':10})
print motor.status('velocity')

The motor object, then formats this request into a JSON-RPC string, and sends it to an IO daemon. The python motor object doesn't care what the arguments are, it just handles JSON formatting and sockets. The strings move_at and velocity are just two of what might be hundreds of valid arguments.

What I'd like to do is the following instead:

motor.move_at(10)
print motor.velocity()

I'd like to do it in a generic way since I have so many different arguments I can pass. What I don't want to do is this:

# create a new function for every possible argument
def move_at(self,x)
    return self.set({'move_at':x})
def velocity(self)
    return self.status('velocity')
#and a hundred more...

I did some searching on this which suggested the solution lies with lambdas and meta programming, two subjects I haven't been able to get my head around.

UPDATE:

Based on the code from user470379 I've come up with the following...

# This is what I have now....
class Motor(object):
    def set(self,a_dict):
        print "Setting a value", a_dict
    def status(self,a_list):
        print "requesting the status of", a_list
        return 10

# Now to extend it....
class MyMotor(Motor):
    def __getattr__(self,name):
        def special_fn(*value):
            # What we return depends on how many arguments there are.
            if len(value) == 0: return self.status((name))
            if len(value) == 1: return self.set({name:value[0]})
        return special_fn
    def __setattr__(self,attr,value): # This is based on some other answers
        self.set({attr:value})


x = MyMotor()
x.move_at = 20 # Uses __setattr__
x.move_at(10)  # May remove this style from __getattr__ to simplify code.
print x.velocit开发者_如何学Cy()

output:

Setting a value {'move_at': 20}
Setting a value {'move_at': 10}
10

Thank you to everyone who helped!


What about creating your own __getattr__ for the class that returns a function created on the fly? IIRC, there's some tricky cases to watch out for between __getattr__ and __getattribute__ that I don't recall off the top of my head, I'm sure someone will post a comment to remind me:

def __getattr__(self, name):
    def set_fn(self, value):
        return self.set({name:value})
    return set_fn

Then what should happen is that calling an attribute that doesn't exist (ie: move_at) will call the __getattr__ function and create a new function that will be returned (set_fn above). The name variable of that function will be bound to the name parameter passed into __getattr__ ("move_at" in this case). Then that new function will be called with the arguments you passed (10 in this case).

Edit

A more concise version using lambdas (untested):

def __getattr__(self, name):
    return lambda value: self.set({name:value})


There are a lot of different potential answers to this, but many of them will probably involve subclassing the object and/or writing or overriding the __getattr__ function.

Essentially, the __getattr__ function is called whenever python can't find an attribute in the usual way.

Assuming you can subclass your object, here's a simple example of what you might do (it's a bit clumsy but it's a start):

class foo(object):
    def __init__(self):
        print "initting " + repr(self)
        self.a = 5

    def meth(self):
        print self.a

class newfoo(foo):
    def __init__(self):
        super(newfoo, self).__init__()
        def meth2():                           # Or, use a lambda: ...
            print "meth2: " + str(self.a)      # but you don't have to
        self.methdict = { "meth2":meth2 }

    def __getattr__(self, name):
        return self.methdict[name]

f = foo()
g = newfoo()

f.meth()
g.meth()
g.meth2()

Output:

initting <__main__.foo object at 0xb7701e4c>
initting <__main__.newfoo object at 0xb7701e8c>
5
5
meth2: 5


You seem to have certain "properties" of your object that can be set by

obj.set({"name": value})

and queried by

obj.status("name")

A common way to go in Python is to map this behaviour to what looks like simple attribute access. So we write

obj.name = value

to set the property, and we simply use

obj.name

to query it. This can easily be implemented using the __getattr__() and __setattr__() special methods:

class MyMotor(Motor):
    def __init__(self, *args, **kw):
        self._init_flag = True
        Motor.__init__(self, *args, **kw)
        self._init_flag = False            
    def __getattr__(self, name):
        return self.status(name)
    def __setattr__(self, name, value):
        if self._init_flag or hasattr(self, name):
            return Motor.__setattr__(self, name, value)
        return self.set({name: value})

Note that this code disallows the dynamic creation of new "real" attributes of Motor instances after the initialisation. If this is needed, corresponding exceptions could be added to the __setattr__() implementation.


Instead of setting with function-call syntax, consider using assignment (with =). Similarly, just use attribute syntax to get a value, instead of function-call syntax. Then you can use __getattr__ and __setattr__:

class OtherType(object):  # this is the one you didn't write
  # dummy implementations for the example:
  def set(self, D):
    print "setting", D
  def status(self, key):
    return "<value of %s>" % key

class Blah(object):
  def __init__(self, parent):
    object.__setattr__(self, "_parent", parent)

  def __getattr__(self, attr):
    return self._parent.status(attr)
  def __setattr__(self, attr, value):
    self._parent.set({attr: value})

obj = Blah(OtherType())
obj.velocity = 42   # prints setting {'velocity': 42}
print obj.velocity  # prints <value of velocity>
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜