The main problem here is that you are using a pytz time zone. pytz zones do not follow the tzinfo interface and cannot be simply attached to datetime objects (either through the constructor or through replace). If you would like to use pytz time zones, you should use pytz.timezone.localize with a naive datetime. If the datetime is already timezone-aware, you can use datetime.astimezone to convert it between zones.
from dateutil import parser
import pytz
LON = pytz.timezone('Europe/London')
dt = parser.parse('2017-05-31T15:00:00')
dt = LON.localize(dt)
print(dt) # 2017-05-31 15:00:00+01:00
This is because pytz's interface uses localize to attach a static time zone to a datetime. For the same reason, if you do arithmetic on the now-localized datetime object, it may give similar improper results and you'll have to use pytz.timezone.normalize to fix it. The reason this is done this way is that, historically, it has not been possible to handle ambiguous datetimes using a Pythonic tzinfo interface, which changed with PEP 495 in Python 3.6, making pytz's workaround less necessary.
If you would like to pass a tzinfo to a datetime using replace or the constructor, or you would prefer to use the pythonic interface, dateutil's time zone suite implements a PEP 495-compliant tzinfo interface. The equivalent using a dateutil zone is:
from dateutil import parser
from dateutil import tz
LON = tz.gettz('Europe/London')
dt = parser.parse('2017-05-31T15:00:00').replace(tzinfo=LON)
print(dt) # 2017-05-31 15:00:00+01:00