Python program for "0-1 knapsack problem"
The code is well written in terms of style and readability, there is only a few things we could improve there.

The algorithm is also optimal for runtime, but the memory usage/allocation could be improved a little which could speed things up.

Readability


Variable naming: some of the variables are not very descriptive:


grid which represents the dynamic-programming array, it needs a comment 
either way but maybe best_value is better.
items usually means the actual array, since it represents the count, it's 
better to name it items_count. Actually, this variable is not needed since 
the function could use len(weights) or len(values)

You could use a named-tuple (or just tuple) to group the item weights and values.


Algorithm


You can reduce the 2d array to a 1d array saving the values for the current iteration. For this to work, we have to iterate capacity (inner for-loop)
in the opposite direction so we that we don't use the values that were updated in the same iteration (you can try the other way and see what goes wrong).


Incorporating these changes we get the following code:

from collections import namedtuple
from timeit import timeit

Item = namedtuple('Item', ['value', 'weight'])

def knapsack(capacity, items):
    # A DP array for the best-value that could be achieved for each weight.
    best_value = [0] * (capacity + 1)
    # The previous item used to achieve the best-value for each weight.
    previous_item = [None] * (capacity + 1)
    for item in items:
        for w in range(capacity, item.weight - 1, -1):
            value = best_value[w - item.weight] + item.value
            if value &gt; best_value[w]:
                best_value[w] = value
                previous_item[w] = item

    cur_weight = capacity
    taken = []
    while cur_weight &gt; 0:
        taken.append(previous_item[cur_weight])
        cur_weight -= previous_item[cur_weight].weight

    return best_value[capacity], taken

items = [Item(60, 10), Item(100, 20), Item(120, 30)]
capacity = 50

print(knapsack(capacity, items))
print('knapsack() Time: ' + str(
      timeit('knapsack(capacity, items)', 
             setup = 'from __main__ import knapsack, capacity, items')))


This change resulted in a good improvement in runtime. On my machine, it decreased the execution time (1,000,000 iterations) from 37s to 24s.