Python 3.7 and above
The ExitStack described below will also work, but Python 3.7 further introduced contextlib.nullcontext (a couple years after this answer was originally posted, and since that time mentioned in several other answers). In the comments, @Kache points out the most elegant usage of this option:
from contextlib import nullcontext
with get_stuff() if needs_with() else nullcontext() as gs:
# do nearly the same large block of stuff,
# involving gs or not, depending on needs_with()
Note that if needs_with() is False, then gs will be None inside the context block. If you want gs to be something_else in that case, you just replace nullcontext() with nullcontext(something_else).
This simple approach provides just a binary choice between a nullcontext and some other context. While this answers the OP's simple requirements, a more sophisticated is also possible for more complex scenarios: in Python 3.3 and above the ExitStack allows you to add as many exiting things as you want, with complicated logic and so on.
Python 3.3 and above
Python 3.3 introduced contextlib.ExitStack for just this kind of situation. It gives you a "stack", to which you add context managers as necessary. In your case, you would do this:
from contextlib import ExitStack
with ExitStack() as stack:
if needs_with():
gs = stack.enter_context(get_stuff())
# do nearly the same large block of stuff,
# involving gs or not, depending on needs_with()
Anything that is entered to stack is automatically exited at the end of the with statement as usual. (If nothing is entered, that's not a problem.) In this example, whatever is returned by get_stuff() is exited automatically.
If you have to use an earlier version of python, you might be able to use the contextlib2 module, although this is not standard. It backports this and other features to earlier versions of python. You could even do a conditional import, if you like this approach.