I'm mostly following, I think. In step 6, how does it interpret that part? I'm thinking I only need lines 248, 251, and 257 in airsign _main_.py to do what I need to do if I instantiate my own rpc object.
Start with a simple example:
the_answer = 42
class Test1(object):
def __getattr__(self, name):
return the_answer
t1 = Test1()
t1.foo # 42
t1.bar # 42
t1.foo + t1.bar # 84
In the first example, wherever you see `t.something`, you can substitute `the_answer`, and that's more or less what Python does, too.
Now let's make it a little more interesting:
def get_the_answer():
return 42
get_the_answer # <function get_the_answer at 0x10906ec80>
get_the_answer() # 42
Once you define a function, you can treat it just like any other variable. `the_answer` is a reference to a variable containing the value `42`, and `get_the_answer` is a reference to a function that returns `42` when invoked.
class Test2(object):
def __getattr__(self, name):
return get_the_answer
t2 = Test2()
t2.foo # <function get_the_answer at 0x10906ec80>
t2.bar # <function get_the_answer at 0x10906ec80>
t2.foo() # 42
`Test2.__getattr__` returns a reference to the `get_the_answer` function, so wherever you see `t2.something`, you can substitute `get_the_answer`:
t2.foo => get_the_answer => <function get_the_answer at 0x10906ec80>
t2.foo() => get_the_answer() => 42
One more example to bring it closer to what you saw in airsign:
class Test3(object):
def __getattr__(self, name):
def create_request(*args):
return {'params': [name, args], 'method': 'query'}
return create_request
t3 = Test3()
t3.foo # <function create_request at 0x10906ec80>
t3.bar # <function create_request at 0x109088668>
t3.foo() # {'params': ['foo', ()], 'method': 'query'}
t3.bar(1, 2, 3) # {'params': ['bar, (1, 2, 3)], 'method': 'query'}
Unlike `Test2.__getattr__` where it returned a reference to the same function every time, `Test3.__getattr__` creates a function on the fly and returns that, but otherwise the idea is the same.
__getattr__ seems like a method for choosing from a variety of methods based on the input, does that seem accurate?
Not quite. In this case, `GrapheneWebsocketRPC.__getattr__` literally creates a new function every time you call it, similar to `Test3.__getattr__`. You can verify this by checking the object ID of the resulting function:
t2.foo # <... at 0x10906ec80>
t2.foo # <... at 0x10906ec80> (same)
t3.foo # <... at 0x10907ccf8>
t3.foo # <... at 0x109075758> (different)
(if you run the code on your system, the exact values will be slightly different, but the result will be the same; `t2.foo` always returns the same instance, but `t3.foo` always returns a different one)
Note: when you write your own classes, you don't
have to use `__getattr__` this way. It has many uses; generating a function on the fly is just one of them.