This seemed like a fun exercise for numpy arrays. My solution is pretty verbose, but I hope it helps illustrate each step.
import numpy as np
import random
in_data = [[10,20,30,40],
[51,61,71,81],
[92,102,112,122],
[133,143,153,163]]
array = np.array(in_data) # Create array
print('Start array:\n', array)
old_shape = array.shape # Preserve shape
long_array = np.reshape(array, array.size) # turn it into a 1xN vector
print('long_array: ', long_array)
index = np.arange(0, array.size) # Create a simple 0-N index
print('index: ', index)
random.shuffle(index) # Shuffle the index
print('shuffled index: ', index)
long_array = long_array[index] # Apply shuffled index to the data array
print('shuffled long array: ', long_array[index])
shuffled_array = long_array.reshape(old_shape) # Create the 4x4 shape using the old_shape
print('reshaped shuffled array:\n', shuffled_array)
### UNSHUFFLING
# Generate a new linear index and sort the shuffled index to obtain a reverse sort
new = np.vstack([index, np.arange(0,len(index))])
new = new.T
unshuffled_index = new[new[:,0].argsort()].T[1,:] # Sort the shuffled_index column
print("unshuffled_index: ", unshuffled_index)
# Condensed reshape, re-index, re-shape of the shuffled data
orig = np.reshape(np.reshape(shuffled_array, array.size)[unshuffled_index], old_shape)
print('original data:\n', orig)
This would be much cleaner if I could avoid the reshaping to 1xN and/or had a more clever way to reverse the shuffle.