一、问题的提出
在github上jqdatasdk库中,有一段这样的代码,让人感觉到很神奇。
在api.py文件中
https://github.com/JoinQuant/jqdatasdk/blob/master/jqdatasdk/api.py
具体代码如下:
@assert_auth
def get_price(security, start_date=None, end_date=None, frequency='daily',
fields=None, skip_paused=False, fq='pre', count=None, panel=True, fill_paused=True):
if panel and PandasChecker.check_version():
panel = False
security = convert_security(security)
start_date = to_date_str(start_date)
end_date = to_date_str(end_date)
if (not count) and (not start_date):
start_date = "2015-01-01"
if count and start_date:
raise ParamsError("(start_date, count) only one param is required")
return JQDataClient.instance().get_price(**locals())
神奇之处在哪里呢?在
JQDataClient.instance().get_price(**locals())
如果不细看,你感觉很正常呀。不就是对象调用其方法嘛。但是你到client.py文件中:
https://github.com/JoinQuant/jqdatasdk/blob/master/jqdatasdk/client.py
具体代码中,的确有class JQDataClient的定义,但没也没有看到里面有get_price呀
class JQDataClient(object):
_threading_local = threading.local()
_auth_params = {}
_default_host = "39.107.190.114"
_default_port = 7000
request_timeout = 300
request_attempt_count = 3
这是如何一回事?
难道,对象可以调用任意的函数?不会吧,还有这么随便的。
其实…; 黑魔法的确就是class JQDataClien里面的两个方法:
def __call__(self, method, **kwargs):
err, result = None, None
for _ in range(self.request_attempt_count):
try:
result = self.query(method, kwargs)
break
except socket_error as ex:
if not self._ping_server():
self._reset()
err = ex
time.sleep(0.6)
except ResponseError as ex:
err = ex
if result is None and isinstance(err, Exception):
raise err
return self.convert_message(result)
def __getattr__(self, method):
return lambda **kwargs: self(method, **kwargs)
其实,准确的说,还有一个query()。
二、细说一下__call__和__getattr__组合黑魔法
通常我们能单用一个,就已经不常见了,更不用说,把这两个组合起来用了。
下面,做一下简单的POC测试,来验证一下。
class Client:
def __init__(self, host):
self.host = host
def query(self, method, params):
print(f"hello method {method} {params}!")
def __call__(self, method, **kwargs):
print(f"__call__ -> method :{method}!")
result = self.query(method, kwargs)
print('query ok!')
def __getattr__(self, method):
print(f"__getattr__ -> method :{method}")
return lambda **kwargs: self(method, **kwargs)
CLIENT = Client("122.122.122.122")
def get_price(start_date,close_date):
print("get_price : start_date: {start_date} close_date :{close_date}")
print(f"print -> locals : {locals()}")
CLIENT.get_price(**locals())
def test():
start_date = "2020-01-01"
close_date = "2021-01-01"
get_price(start_date,close_date)
test()
输出:
get_price : start_date: {start_date} close_date :{close_date}
print -> locals : {'start_date': '2020-01-01', 'close_date': '2021-01-01'}
__getattr__ -> method :get_price
__call__ -> method :get_price!
hello method get_price {'start_date': '2020-01-01', 'close_date': '2021-01-01'}!
query ok!
看明白了吗?
三、为什么要这一对组合?
两者分开用,不是可以?何必多此一举?
import sys
sys.path.append("/home/songroom/.local/lib/python3.6/site-packages")
import random
import itertools
import uuid
from sqlalchemy.orm import scoped_session, sessionmaker
class Client:
def __init__(self, host):
self.host = host
self.request_id = itertools.count(
random.choice(range(0, 1000, 10))
)
def query(self, method, params):
print(f"hello method {method} {params}!")
def __call__(self, method, **kwargs):
print(f"__call__ -> method :{method}!")
result = self.query(method, kwargs)
print('query ok!')
def __getattr__(self, method):
print(f"__getattr__ -> method :{method}")
return lambda **kwargs: self(method, **kwargs)
CLIENT = Client("122.122.122.122")
def get_price(start_date,close_date):
print("get_price : start_date: {start_date} close_date :{close_date}")
print(f"print -> locals : {locals()}")
CLIENT(get_price,**locals()) #CLIENT.get_price(**locals())
def test():
start_date = "2020-01-01"
close_date = "2021-01-01"
get_price(start_date,close_date)
test()
输出:
get_price : start_date: {start_date} close_date :{close_date}
print -> locals : {'start_date': '2020-01-01', 'close_date': '2021-01-01'}
__call__ -> method :!
hello method {'start_date': '2020-01-01', 'close_date': '2021-01-01'}!
query ok!
的确,__call__本来是可以单玩的,但是这种情况下,只能用以下()调用的形式:
CLIENT(get_price,**locals())
但是,和__getattr__组合后,就不一样了,更加优雅了,可以用“.”号来调用,让人在外感上好象更自然一些:
CLIENT.get_price(**locals())
其实,在内部实际上,_getatrr__把“.”打通,然后再调用()的方法。这就是魔法的全部。