Eu me deparei com um problema semelhante e depois de algumas horas de sangue, suor e lágrimas, descobri que a resposta requer simplesmente a adição de um parâmetro.
Ao invés de
cursor = conn.cursor()
escrever
cursor = conn.cursor(name="my_cursor_name")
ou mais simples ainda
cursor = conn.cursor("my_cursor_name")
Os detalhes são encontrados em http://initd.org/psycopg/docs/usage.html#server-side-cursors
Achei as instruções um pouco confusas, pois pensei que precisaria reescrever meu SQL para incluir "DECLARE my_cursor_name ...." e depois "FETCH count 2000 FROM my_cursor_name", mas acontece que o psycopg faz tudo isso para você em o capô se você simplesmente substituir o parâmetro padrão "name=None" ao criar um cursor.
A sugestão acima de usar fetchone ou fetchmany não resolve o problema, pois, se você deixar o parâmetro name indefinido, o psycopg por padrão tentará carregar a consulta inteira na ram. A única outra coisa que você pode precisar (além de declarar um parâmetro de nome) é alterar o atributo cursor.itersize do padrão 2000 para 1000 se você ainda tiver pouca memória.