Trying to understand coordinate spaces in python In a sense I have &quot;solved&quot; my original problem but I don't understand why/how its solved.
I have a script that generates a number of objects and then attempts to create straight lines via a Nurbs path, adapted from the question/answer here.  Using Hooks is important because I want the 'lines' between objects to persist in relative terms to their parents.
The solution I've found is that if I create an otherwise unused empty object with defined coordinates (uncomment out line 52 bpy.ops.object.empty_add(location=list(objA.location),)) everything works as expected.  If I remove this hack-ish work around though, the coordinates for the ends of the nurbs paths are all translated/scaled which is not the desired behavior.
I thought the &quot;answer&quot; might be related to object vs world space (and maybe it still is?) or maybe a misunderstanding of context, but this hail-Mary work around of mine worked so thought I'd quit while I was ahead/ask a more specific question: &quot;why does this 'hack' work?&quot; and perhaps &quot;is there a better way?&quot;
Code example showing the undesirable behavior (uncomment out line 52 to see correct behavior)
#names and locations used to drive example
pointDict ={'obj_0': (3.453742273151139, 0.5159551844564874,0.0),
            'obj_1': (12.792816730076403, -16.205364756872424,0.0),
            'obj_2': (14.709455272016719, -11.60690199900985,0.0),
            'obj_3': (8.69197652556564, -13.612261105632582,0.0),
            'obj_4': (9.52728618428947, 2.4890328748868917,0.0)}

import bpy
C = bpy.context
from itertools import combinations

#template cube to clone, used for minimum viable example
bpy.ops.mesh.primitive_cube_add(size=0.5, align='WORLD', location=(10.0,10.0,0.0), scale=(1, 1, 1))

baseCube = bpy.data.objects['Cube'] #known/assumed name

#clone base object with predetermined coordiantes
for keyName in pointDict:
    xVar, yVar, zVar = pointDict[keyName]
    objClone = baseCube.copy()
    objClone.name = keyName
    objClone.location.x = xVar
    objClone.location.y = yVar
    objClone.location.z = zVar
    C.collection.objects.link(objClone)

#create pair-wise line combinations between cubes 
#and hook to cubes to maintain position/connections when moved at later point
# moddified from https://blender.stackexchange.com/questions/194400/adding-a-hook-to-a-nurbs-path-with-python
pCounter = 0
for pair in combinations(list(pointDict.keys()),2):
    pCounter=+1
    
    objA = bpy.data.objects[pair[0]]
    objB = bpy.data.objects[pair[1]]

    path = bpy.data.curves.new('_path_{}'.format(pCounter), 'CURVE')
    path.dimensions = &quot;3D&quot;
    path.bevel_depth=0.05

    spline = path.splines.new(type='NURBS')
    spline.points.add(1)
    spline.use_endpoint_u = True
    spline.use_endpoint_v = True
    path_ob = bpy.data.objects.new('_Path_new_{}'.format(pCounter), path)

    
    spline.points[0].co = (list(objA.location)+[1.0])
    
    #not sure why adding this helps, but it fixes things??  uncoment following line to see &quot;good&quot; results
    #bpy.ops.object.empty_add(location=list(objA.location),)
    hm1 = path_ob.modifiers.new(name=f&quot;Hook_A&quot;,type='HOOK',)
    hm1.object = objA
    hm1.vertex_indices_set([0]) 
    #'Hook' first vertex of spline/path
    spline.points[1].co = (list(objB.location)+[1.0])
    hm2 = path_ob.modifiers.new(name=f&quot;Hook_B&quot;,type='HOOK',)
    hm2.object = objB
    hm2.vertex_indices_set([1])

    C.collection.objects.link(path_ob)
Force an update.
Using the add empty object operator (or any operator) has the side effect of forcing a view_layer update.  In version &lt; 2.8 this was a scene.update().
It can be the case in a script execution that object matrices are not yet updated when their values are being queried.
Updating the viewlayer in the same loop as the operator produces the same result as with operator, without operator. (circa line 30)
pCounter = 0
for pair in combinations(list(pointDict.keys()),2):
    pCounter = +1 # Did  you mean += 1
    bpy.context.view_layer.update()  # &lt;== UPDATE
    objA = bpy.data.objects[pair[0]]