objects.delete doesn't remove completely I have this part of code in my script:

for i in bpy.data.objects:
    if ((i.type == "CAMERA") or (i.type == "LAMP") or (i.name == "Material_Holder")):
        i.select = False
bpy.ops.object.delete()


when I run this code, all objects except the specified in the if-statment are deleted in the scene. Later in the script I have this part:

for material in bpy.data.materials:
    if (not (material.users)):
        bpy.data.materials.remove(material)


which checks, if material is assigned to anything and if not, it deletes it. This part does esentiallt nothing. My code creates hundreds of objects and dozens of materials for them. Objects are deleted by the first part, materials are not deleted by the second part. I tried printing material.users and it seems to me, that the objects are still somwhere and have their materials assigned. How can I prevent this?
The whole code:
import bpy

matrix_size = 3

(...)

materials = []
matrix_range = range (matrix_size)

changeable_material = bpy.data.materials.get("Material")

material_value = 0

for i in range (matrix_size):
    (...)
    materials.append (changeable_material.copy ())
    materials [-1].node_tree.nodes.get("Value").outputs [0].default_value = material_value
    material_value += 1 / matrix_size
    for j in range (matrix_size):
        matrix [-1].append ((i * j) % matrix_size)
        cubes [-1].append (0)

bpy.ops.object.select_all(action = "SELECT")

for i in bpy.data.objects:
    if ((i.type == "CAMERA") or (i.type == "LAMP") or (i.name == "Material_Holder")):
        i.select = False
bpy.ops.object.delete()

#removes all unused materials
for material in bpy.data.materials:
    if (not (material.users)):
        bpy.data.materials.remove(material)

bpy.ops.mesh.primitive_cube_add()
base_object = bpy.context.object
base_object.name = "base cube"
base_object.data.materials.append (changeable_material)

base_object.scale = (0.5, 0.5, 0.5)

bpy.context.scene.objects.active = base_object

arrayOne = base_object.modifiers.new (name = "arrayOne", type = "ARRAY")
arrayOne.count = matrix_size
arrayTwo = base_object.modifiers.new (name = "arrayTwo", type = "ARRAY")
arrayTwo.count = matrix_size
arrayTwo.relative_offset_displace [0] = 0
arrayTwo.relative_offset_displace [1] = 1

bpy.ops.object.modifier_apply (modifier = "arrayOne")
bpy.ops.object.modifier_apply (modifier = "arrayTwo")

base_object.select = True
bpy.ops.mesh.separate (type = "LOOSE")

for i in bpy.data.objects:
    if ("cube" in i.name):
        bpy.ops.object.select_all(action = "DESELECT")
        i.select = True
        bpy.ops.object.origin_set (type = "ORIGIN_CENTER_OF_MASS")
        cubes [int (i.location [0])][int (i.location [1])] = i

for i in range (matrix_size):
    for j in range (matrix_size):
        if cubes [i][j].data.materials:
            cubes [i][j].data.materials[0] = materials [matrix [i][j]]


except I want to run it for much higher values of matrix_size (this makes matrix_size number of materials and matrix_size * matrix_size number of objects)
Selecting the objects and letting bpy.ops.object.delete() remove them may not be a good idea, not least due to the performance of the bpy.ops calls. Maybe you can get the list of the selected objects using bpy.context.selected_objects. Then iterate over the objects and call:

for obj in bpy.context.selected_objects:
    if(obj.type == 'MESH'): #Maybe this is superfluous in this case
        bpy.context.scene.objects.unlink(obj)
        #material.users count is not reduced yet, even if object is gone from the scene
        bpy.data.meshes.remove(obj.data)
        #now material.users count is reduced by one
        #(Just be careful with the above call, since multiple objects can share the same data)
        bpy.data.objects.remove(obj)