Define multiple versions of the same function name

How about classes and inheritance:

class Base:
    def main(self):
        print("base")

class RevA(Base):
    def main(self):
        print("RevA")

class RevB(Base):
    def main(self):
        print("RevB")

if version == 'revA':
    obj = RevA()
elif version == 'revB:
    obj = RevB()
else:
    obj = Base()

obj.main()

Also typical are factory functions like:

def get_obj(version, *args, **kwargs):
    omap = { 'revA': revA, 'revB': revB }
    return omap[version](*args, **kwargs)

This allows you to call for example:

obj = get_obj('revA', 23, fish='burbot')

Which will be equivalent to:

if version == 'revA':
    obj = revA(23, fish='burbot')

You can, but doing literally that would be very uncommon:

if version == 'revA':
    def main():
        print("RevA")
elif version == 'revB':
    def main():
        print("RevB")

main()

More usually, you'd define both functions then choose which one to use by assigning it to a variable:

def main_A():
    print("RevA")

def main_B():
    print("RevB")

# select the right version using a dispatch table
main = {
  'RevA': main_A,
  'RevB': main_B,
}[version]

main()

Variants of this latter approach are quite common; both web applications and graphical applications often work this way, with a table mapping URLs or user actions to functions to be called. Often the table is maintained by the framework and your code adds entries to it in multiple places in the code, sometimes in bulk (eg Django), sometimes one by one (eg Flask).

Having both functions defined (not just the selected one) means that you can also call each version directly; that's useful if the main program uses a dispatch table but various subsidiary code (such as the tests) needs to call a particular one of the functions