REF: move actual lookup and dispatch to array_op from frame into internals
Background: I am looking at ways to optimize ops for ArrayManager, and currently the manager hasn't much control (apart from the `operate_blockwise`): it is the DataFrame in `_dispatch_frame_op` that decides which `array_op` function to use and actually calls it on the columns or dispatches it to the manager (with apply or operate_blockwise). For ArrayManager, more control would be useful (one example: deciding whether to use numexpr or not now takes a lot of time in the benchmarks for ArrayManager, because it gets done column by column, while we could do it more efficiently inside the ArrayManager).

So the idea would then be:

- DataFrame is responsible for all preprocessing: checking the `right` argument, alignment, etc (which is already done right now in `DataFrame._arith_method` -> `ops.align_method_FRAME`). This happens on the DataFrame level because it involves frame-level logic (alignment) and can be shared for BlockManager/ArrayManager
- Manager is responsible for executing the actual operation on *clean* arguments: it gets either a scalar, array (with correct length to either match to axis 0 or 1) or another Manager (of the same size). 

Note that making the Manager responsible to *call* the execution of the op, doesn't mean the actual code needs to live in the internals. It can still continue to use to array_ops code in /core/ops (and also the ArrayManager will continue to do that, but with some adaptations, eg with some other arguments like passing through a pre-determined `use_numexpr`). 

So for the BlockManger, the diff is only small here (it's mainly moving the case of frame/series which works column by column into BlockManager). I think it's actually an OK change for BlockManager on itself as well, but so the main goal is to allow more customization for ArrayManager in follow-up PRs.

Thoughts on the general idea?

cc @jbrockmendel @jreback 