Don't use a generator expression here, just loop once, updating minimi and maximi in one go:
left = bottom = float('inf')
right = top = float('-inf')
for x in rect:
if x.left < left: left = x.left
if x.bottom < bottom: bottom = x.bottom
if x.right > right: right = x.right
if x.top > top: top = x.top
Yes, this is more verbose, but also more efficient. The longer it
is, the less work the above loop performs compared to your 4 generator expressions.
You can produce dictionaries of results:
minimi = dict.fromkeys(('left', 'bottom'), float('inf'))
maximi = dict.fromkeys(('right', 'top'), float('-inf'))
for x in rect:
for key in minimi:
if getattr(x, key) < minimi[key]: minimi[key] = getattr(x, key)
for key in maximi:
if getattr(x, key) > maximi[key]: maximi[key] = getattr(x, key)
but that's hardly worth the abstraction, not for 2 values each.