如何向sorted()的'key'方法传递多个参数?

10 投票
3 回答
11131 浏览
提问于 2025-04-17 22:50

在一个网站上,我想根据用户的位置来给商店排序。让我来解释一下。

一个商店的样子大概是这样的:

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 个回答

1

你的问题有点难懂,因为你没有定义一个叫做 distance 的函数,而你提供的函数 distance_of_the_shop 其实只接受一个参数。

如果我理解得没错,你希望 distance_of_the_shop 能接收当前用户和要比较的商店。要做到这一点,可以使用 lambda

shop_list.sort(key=lambda shop: distance_of_the_shop(user, shop))

另外,注意调用 sorted 函数时,如果不把结果赋值给某个变量或容器,那是没有意义的。如果你想在原地对一个列表进行排序,可以使用它的 sort 方法,就像上面展示的那样。

2

首先,我们需要一个计算距离的函数(这个函数用来计算大圆距离,也就是在地球表面两点之间的最短距离):

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))
23

只需要把调用包裹在一个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)

撰写回答