Move/rotate object depending on another object
A driver is a way of calculating the value to use based on other values in the scene, any python expression can be used in a driver. A constraint is basically a visual way of representing a driver. For the most control, you can define a python function to calculate a value used in a driver.

As an example, a driver can calculate the x rotation as 1/4 of the x rotation of the empty. The variable named var is set to get the x rotation of the Empty and is then used in the python expression to get the final value.



Then you can set cube2 to rotate three times the rotation of the first cube



Now let's say we want cube two to have it's rotation a constant offset from the first cube's rotation, we can use a custom property to hold the offset amount (and animate it if we want some variation) and use that in the driver.



You can also add your own function into the driver namespace -

import bpy

def mydriverfunc(val):
    return val * .75

bpy.app.driver_namespace['myfunc'] = mydriverfunc


and then use that in the driver expression, passing any gui configured variables as paramaters.



Note that drivers are python code and might get disabled based on the auto-run python settings.

You can create a driver using python

d = obj.driver_add('rotation_euler', 1)
d.driver.type = 'SCRIPTED'
v = d.driver.variables.new()
v.name = 'var'
v.type = 'TRANSFORMS'
v.targets[0].id = bpy.data.objects['Empty']
v.targets[0].transform_type = 'ROT_X'
d.driver.expression = 'var * .25'