One way to set temporarily python-shell-buffer-name during a function call is to
call this function inside a let statement that sets python-shell-buffer-name.
(defun adl/elpy-shell-send-region-or-buffer ()
"Send python- region or buffer to *Python[ PY-BUFFER ]*."
(interactive)
(let ((python-shell-buffer-name (concat "Python[" (file-name-base (buffer-name)) "]")))
(elpy-shell-send-region-or-buffer)))
Using the function above instead of (elpy-shell-send-region-or-buffer), you will
send the code of file.py to a buffer named *Python[file]*.
Code from different files with the same name will run in the same buffer.
If you change (file-name-base (buffer-name)) to default-directory, all files from
the same directory will run in the same buffer named *Python[path/to/directory/]*.
If you change (file-name-base (buffer-name)) to (buffer-file-name), each file
will run in a different buffer named *Python[path/to/directory/file.py]*. Note that
unsaved buffers, e.g., *scratch*, will run in *Python[]*.
If none of the above satisfies you, choose a better string :).
You can replace "C-c C-c" key binding to the new function (or use other binding):
(add-hook 'elpy-mode-hook
(lambda ()
(define-key elpy-mode-map (kbd "C-c C-c") #'adl/elpy-shell-send-region-or-buffer)))