There is now!
The LaTeX3 people will flay alive me when they see this :|
Here's an s-type expansion (s stands for “blame siracusa” for this ;-) that, unlike other expansion flavours, takes an argument. The argument is the number of expansions of o-type expansions of the token list. You need then to define your own \exp_args:N... commands that do the expansion of the argument. For example, to expand the first argument four times you can define:
\cs_new:Npn \exp_args:Niv { \::s {4} \::: }
or to f-expand the first, and expand the second two times:
\cs_new:Npn \exp_args:Nft { \::f \::s {2} \::: }
or the contrary (first→twice, second→f):
\cs_new:Npn \exp_args:Ntf { \::s {2} \::f \::: }
or yet expand some argument an arbitrary amount of times:
\cs_new:Npn \exp_times:nNs #1 { \::s {#1} \::: }
and so on. Here's the code and some proofs-of-functionality. I define a macro \a which expands to \b, \b to \c, and so on until \f expands to g (to count expansions easily):

\documentclass{article}
\usepackage{expl3}
\ExplSyntaxOn
\cs_new:Npn \::s #1#2 \::: #3#4
{
\if_int_compare:w #1 > 0 ~
\exp_after:wN \exp_after:wN
\exp_after:wN \__siracusa_exp_step_s:nnnn
\else:
\exp_after:wN \__siracusa_exp_end_s:Nnnnn
\fi:
\exp_after:wN {#4} {#1} {#2} {#3}
}
\cs_new:Npn \__siracusa_exp_step_s:nnnn #1#2#3#4
{
\exp_after:wN \::s \exp_after:wN
{ \int_value:w \__int_eval:w #2-1 \__int_eval_end: } {#3} \::: {#4} {#1}
}
\cs_new:Npn \__siracusa_exp_end_s:Nnnnn #1#2#3#4#5
{ \__exp_arg_next:nnn {#2} {#4} {#5} }
% Examples
\cs_new:Npn \exp_times:nNs #1 { \::s {#1} \::: }
\cs_new:Npn \exp_times:nNnfso #1 { \::n \::f \::s {#1} \::o \::: }
\cs_new:Npn \weird_command:nnnn #1 #2 #3 #4
{ \tl_to_str:n {#1|#2|#3|#4} }
\ExplSyntaxOff
\def\a{\b}
\def\b{\c}
\def\c{\d}
\def\d{\e}
\def\e{\f}
\def\f{g}
\begin{document}
\ttfamily
\ExplSyntaxOn
\exp_times:nNs {0} \tl_to_str:n { \a }\par
\exp_times:nNs {1} \tl_to_str:n { \a }\par
\exp_times:nNs {2} \tl_to_str:n { \a }\par
\exp_times:nNs {3} \tl_to_str:n { \a }\par
\exp_times:nNs {4} \tl_to_str:n { \a }\par
\exp_times:nNs {5} \tl_to_str:n { \a }\par
\exp_times:nNs {6} \tl_to_str:n { \a }\par
\exp_times:nNnfso {0} \weird_command:nnnn{\a}{\b}{\c}{\d}\par
\exp_times:nNnfso {1} \weird_command:nnnn{\a}{\b}{\c}{\d}\par
\exp_times:nNnfso {2} \weird_command:nnnn{\a}{\b}{\c}{\d}\par
\exp_times:nNnfso {3} \weird_command:nnnn{\a}{\b}{\c}{\d}\par
\exp_times:nNnfso {4} \weird_command:nnnn{\a}{\b}{\c}{\d}\par
\ExplSyntaxOff
\end{document}
fexpansion does what you want here. Thefexpansion will expand the head of the token list (as willo) until the the head of the token list is an unexpandable token. – Phelype Oleinik May 27 '19 at 23:25fexpands too far in most of the cases. I'm debugging function definitions and actually want to get the results after a certain number of expansion steps. – siracusa May 27 '19 at 23:34multiexpandpackage? https://ctan.org/pkg/multiexpand – user202729 Jul 11 '22 at 15:14