You can use .merge() with indicator turned on and filter the result by the indicator, as follows:
df3 = df2.merge(df1, how='left', indicator=True)
df3[df3['_merge'] == 'left_only'][['ID', 'Number']]
Result:
   ID  Number
2   2     963
Edit
If you have multiple columns and would not like to specify the column names to highlight the differences, you can try:
df2[(df1 != df2)].dropna(how='all', axis=1).dropna(how='all', axis=0)
Demo
df1
   ID Name  Number1  Number2  Number3
0   0  AAA      123       12     1111
1   1  BBB      456       22     2222
2   2  CCC      789       32     3333
df2
   ID Name  Number1  Number2  Number3
0   0  AAA      123       12     1111
1   1  BBB      456       22     2255
2   2  CCC      963       32     3333
df2[df1 != df2].dropna(how='all', axis=1).dropna(how='all', axis=0)
   Number1  Number3
1      NaN   2255.0
2    963.0      NaN
You can see from the non_NaN values the differences.  The ID is the index on the left.
Edit 2
If your numbers are all integers and you don't want Pandas to show the integers as float type together with NaN values, you can use:
df2[df1 != df2].dropna(how='all', axis=1).dropna(how='all', axis=0).fillna('').astype(str).replace(r'\.0', '', regex=True)
  Number1 Number3
1            2255
2     963        
Or, simply use:
df2[df1 != df2].dropna(how='all', axis=1).dropna(how='all', axis=0).astype('Int64')
   Number1  Number3
1     <NA>     2255
2      963     <NA>