根据字符串路由到方法

3 投票
2 回答
1434 浏览
提问于 2025-04-17 21:29

我想在Python中根据一个字符串来调用方法,这个方式有点像Tornado根据URL来处理请求的方式。举个例子,如果我有一个字符串“/hello”,我想调用某个类里的“hello”方法。我查了一下Python的路由,找到了以下的解决方案:

from routes import Mapper

class Test():
    def hello(self):
        print "hello world"

map = Mapper()
map.connect(None, '/hello', controller=Test(), action='hello')

match = map.match("/hello")
if match != None:
    getattr(match['controller'], match['action'])()

不过,我还想增加一个功能,就是能把字符串的一部分当作参数传给这个方法。比如说,对于字符串“hello/5”,我想调用hello(5)(这和Tornado的路由方式非常相似)。这个功能应该能支持任意数量的参数。有没有人有好的办法来实现这个?能不能用Tornado的路由功能来处理一个不是请求URL的字符串呢?

2 个回答

1

想法相似,但方式不同。这里是代码:

class Xroute(tornado.web.RequestHandler):
    def get(self,path):
        Router().get(path,self)
    def post(self,path):
        Router().post(path,self)

class Router(object):
    mapper={}
    @classmethod
    def route(cls,**deco):
        def foo(func):
            url = deco.get('url') or '/'
            if url not in cls.mapper:
                method = deco.get('method') or 'GET'
                mapper_node = {}
                mapper_node['method'] = method
                mapper_node['call'] = func
                cls.mapper[url] = mapper_node
            return func
        return foo

    def get(self,path,reqhandler):
        self.emit(path,reqhandler,"GET")

    def post(self,path,reqhandler):
        self.emit(path,reqhandler,"POST")

    def emit(self,path,reqhandler,method_flag):
        mapper = Router.mapper
        for urlExp in mapper:
            m = re.match('^'+urlExp+'$',path)
            if m:
                params = (reqhandler,)
                for items in m.groups():
                    params+=(items,)
                mapper_node = mapper.get(urlExp)
                method = mapper_node.get('method')
                if method_flag not in method:
                    raise tornado.web.HTTPError(405)
                call = mapper_node.get('call')
                try:
                    call(*params)
                    break
                except Exception,e:
                    print e
                    raise tornado.web.HTTPError(500)
        else:
            raise tornado.web.HTTPError(404)

@Router.route(url=r"hello/([a-z]+)",method="GET")
def test(req, who):
    #http://localhost:8888/hello/billy
    req.write("Hi," + who)

@Router.route(url=r"greetings/([a-z]+)",method="GET|POST")
def test2(req, who):
    #http://localhost:8888/greetings/rowland
    test(req, who)

@Router.route(url=r"book/([a-z]+)/(\d+)",method="GET|POST")
def test3(req,categories,bookid):
    #http://localhost:8888/book/medicine/49875
    req.write("You are looking for a " + categories + " book\n"+"book No. " + bookid)

@Router.route(url=r"person/(\d+)",method="GET|POST")
def test4(req, personid):
    #http://localhost:8888/person/29772
    who = req.get_argument("name","default")
    age = req.get_argument("age",0)
    person = {}
    person['id'] = personid
    person['who'] = who
    person['age'] = int(age)
    req.write(person)

if __name__=="__main__":
    port = 8888
    application = tornado.web.Application([(r"^/([^\.|]*)(?!\.\w+)$", Xroute),])
    application.listen(port)
    tornado.ioloop.IOLoop.instance().start()
1

首先,了解一下你想要做这个“本地”路由系统的原因、用途和最初的任务会很有帮助。

根据你的例子,一个可行的方法就是自己动手做一些东西。

这里有一个非常基础的例子:

class Test():
    def hello(self, *args):
        print args


urls = ['hello/', 'hello/5', 'hello/5/world/so/good']
handler = Test()

for url in urls:
    parts = url.split('/')
    method = parts[0]
    if hasattr(handler, method):
        getattr(handler, method)(*parts[1:])

输出结果是:

('',)
('5',)
('5', 'world', 'so', 'good')

你还可以使用正则表达式来保存网址中的某些部分,分成命名组或位置组。比如,看看djangopython-routes是怎么做的。

希望这些对你有帮助。

撰写回答