如何向sorted()的'key'方法传递多个参数?
在一个网站上,我想根据用户的位置来给商店排序。让我来解释一下。
一个商店的样子大概是这样的:
class Shop(models.Model):
latitude = models.DecimalField(max_digits=9, decimal_places=6)
longitude = models.DecimalField(max_digits=9, decimal_places=6)
我在会话中获取了用户的位置。
request.session['user_latitude']
request.session['user_longitude']
现在我有了一份商店的列表,我想对它们进行排序。所以我试了这个:
def distance_of_the_shop(shop):
# compute the distance between the shop and the user and return it
return computed_distance
sorted(shop_list, key=distance_of_the_shop)
问题很简单,怎么才能给方法 distance_of_the_shop
传递多个参数呢?
3 个回答
你的问题有点难懂,因为你没有定义一个叫做 distance
的函数,而你提供的函数 distance_of_the_shop
其实只接受一个参数。
如果我理解得没错,你希望 distance_of_the_shop
能接收当前用户和要比较的商店。要做到这一点,可以使用 lambda
:
shop_list.sort(key=lambda shop: distance_of_the_shop(user, shop))
另外,注意调用 sorted
函数时,如果不把结果赋值给某个变量或容器,那是没有意义的。如果你想在原地对一个列表进行排序,可以使用它的 sort
方法,就像上面展示的那样。
首先,我们需要一个计算距离的函数(这个函数用来计算大圆距离,也就是在地球表面两点之间的最短距离):
from math import radians, cos, sin, asin, sqrt
def haversine(lon1, lat1, lon2, lat2):
"""
Calculate the great circle distance between two points
on the earth (specified in decimal degrees)
"""
# from stackoverflow.com/questions/4913349/haversine-formula-in-python-bearing-and-distance-between-two-gps-points#4913653
# convert decimal degrees to radians
lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2])
# haversine formula
dlon = lon2 - lon1
dlat = lat2 - lat1
a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
c = 2 * asin(sqrt(a))
# 6367 km is the radius of the Earth
km = 6367 * c
return km
接着,我们为每个用户定义一个距离函数:
def dist_from_user(user):
# This is kind of funky, because the `user` parameter is
# not used, we simply assume that user is the current session-holder.
# It would make more sense if we actually had more than
# one user to choose between.
lat = float(request.session['user_latitude'])
lon = float(request.session['user_longitude'])
def shop_dist(shop):
s_lat = float(shop.latitude)
s_lon = float(shop.longitude)
return haversine(lon, lat, s_lon, s_lat)
return shop_dist
然后我们可以这样调用它:
shop_list.sort(key=dist_from_user(user))
只需要把调用包裹在一个lambda表达式里:
ulong, ulat = request.session['user_latitude'], request.session['user_longitude']
sorted(shop_list, key=lambda shop: distance_of_the_shop(shop, ulong, ulat))
然后在distance_of_the_shop()
函数里添加两个参数,用来接收经度和纬度。
sorted()
函数会对shop_list
中的每个值调用key
,但并没有规定这个可调用对象不能自己调用其他函数。使用lambda
是创建一个新包装函数的最简单方法,它正好可以做到这一点。
你也可以使用一个functools.partial()
对象,前提是经度和纬度的值可以作为关键字参数传入,或者将这两个值作为前两个位置参数传入。把它们当作关键字参数处理可能是最好的选择,即使它们有位置(没有默认值),你也可以在partial()
中使用它们的名字作为关键字参数。
假设定义是:
def distance_of_the_shop(shop, long, lat):
# ...
那么使用
sorted(shop_list, key=partial(distance_of_the_shop, long=ulong, lat=ulat))
这样sorted()
会把每个shop
传给partial()
,而partial()
又会调用distance_of_the_shop(shop, long=ulong, lat=ulat)