This is a working example using the original post bullet #3 idea and the discussion in the comment section above (including custom theme and theme weighting):
First, we define a function that sets options using UpValues and TagSetDelayed and the other one that clears them:
ClearAll[robustSetOptions, robustClearOptions];
robustSetOptions[function_,
options_?(MatchQ[#, Rule[_, _]] || MatchQ[#, List[Rule[_, _] ...]] &)] :=
Module[{newTheme = "customTheme"},
System`PlotThemeDump`resolvePlotTheme[newTheme, ToString@function] :=
Themes`SetWeight[options, System`PlotThemeDump`$ComponentWeight];
Unprotect[PlotTheme];
If[UpValues[PlotTheme] =!= {},
PlotTheme /: Rule[PlotTheme, x_String] =.;
PlotTheme /: RuleDelayed[PlotTheme, x_String] =.
];
SetOptions[function, PlotTheme -> newTheme];
Unprotect[PlotTheme];
PlotTheme /: Rule[PlotTheme, x_String] :=
RuleDelayed[PlotTheme,
If[MemberQ[Stack[], function], {newTheme, x}, {x}]];
PlotTheme /: RuleDelayed[PlotTheme, x_String] :=
RuleDelayed[PlotTheme,
If[MemberQ[Stack[], function], {newTheme, x}, x]];
Protect[PlotTheme];
]
robustClearOptions[function_] := Module[{},
If[UpValues[PlotTheme] =!= {},
Unprotect[PlotTheme];
PlotTheme /: Rule[PlotTheme, x_String] =.;
PlotTheme /: RuleDelayed[PlotTheme, x_String] =.;
Protect[PlotTheme];
]
SetOptions[function, PlotTheme -> $PlotTheme];
]
Then we can call it to define custom options (applied as a custom theme). Multiple options are possible (in this example there is a custom legend from the OP and the AspectRatio):
ClearAll[g1, g1t, g2, g2t, g1d, g2d, plot1, plot1t, plot2, plot2t];
(* define custom options *)
customLegend = LineLegend[97, {"base 2", "natural", "base 10"}, LegendLabel -> "Logarithms"];
plotTheme = "Detailed";
plot1 := ListPlot[{Log2, Log, Log10}[Range@40] // Through];
plot1t := ListPlot[{Log2, Log, Log10}[Range@40] // Through, PlotTheme -> plotTheme];
plot2 := ListLinePlot[{Log2, Log, Log10}[Range@40] // Through];
plot2t := ListLinePlot[{Log2, Log, Log10}[Range@40] // Through, PlotTheme -> plotTheme];
(* set custom options *)
robustSetOptions[ListPlot, {PlotLegends -> customLegend, AspectRatio -> 1}];
g1 = plot1; (* should see a legend *)
g1t = plot1t; (* should see a legend *)
g2 = plot2; (* should not see a legend *)
g2t = plot2t; (* should not see a legend *)
(* remove custom options *)
robustClearOptions[ListPlot];
g1d = plot1; (* should not see a legend *)
g2d = plot2; (* should not see a legend *)
Grid[{
{"", "robustSetOptions", SpanFromLeft, "default"},
{SpanFromAbove, "No PlotTheme", "With PlotTheme \"" <> plotTheme <> "\"", SpanFromAbove},
{"ListPlot", g1, g1t, g1d}, {"ListLinePlot", g2, g2t, g2d}},
Alignment -> {Left,
Automatic, {{1, 2} -> Center, {1, 4} -> {Center, Center}, {2, 2} ->
Center, {2, 3} -> Center, {2, 1} -> Center, {3, 1} -> Center}},
Dividers -> Center]
As expected, there is a legend and the graphs are squared when ListPlotis called with or without the PlotTheme option. Any other function (ilustrated with ListLinePlot) is not affected by the options, as they are set only for a specific function (ListPlotin this example). After the options are cleared, all functions are back to their default style.

As this is a working example, please comment, critique, improve or suggest better ways of implementing it.
PlotThemesright. One could work to get many others to do the same. – m_goldberg Jul 30 '15 at 16:33SetOptions[ListPlot, PlotLegends -> Priority[customLegend]]and have it supersede Themes. I'll code up something like that in a self-answer at some point I think. – Mr.Wizard Jul 30 '15 at 17:25PlotTheme. It seems like the legends have to be used with everyListPlotcall. Checked on both Windows and Linux. – Stitch Feb 28 '17 at 20:07ListPlot[{Log2, Log, Log10}[Range@40] // Through, PlotTheme -> None]shows that this is caused by the Plot Theme system. – Mr.Wizard Feb 28 '17 at 20:14Automatic(default for$PlotTheme) should not interfere... – Stitch Feb 28 '17 at 20:17None. When you do you find that plots go back to their old colors. So if you see the new colors in use and you did not manually specify them a Plot Theme is active – Mr.Wizard Feb 28 '17 at 20:19PlotTheme /: Rule[PlotTheme, x_String] := Rule[PlotTheme, {"customLegendTheme", x}]. This way you don't need to redo your old ListPlot calls. – Stitch Feb 28 '17 at 21:39PlotTheme. How would one restrict that to only being active insideListPlot? – Mr.Wizard Feb 28 '17 at 21:48PlotTheme /: Rule[PlotTheme, x_String] := RuleDelayed[PlotTheme, If[MemberQ[Stack[], ListPlot], {"customLegendTheme", x}, x]]? For complete solution, one would also need to define theTagSetDelayedfor theRuleDelayed– Stitch Feb 28 '17 at 22:47resolvePlotTheme. TheAddThemeRulesmethod works very strange -- when you create a custom theme, you can only call itPlotTheme->"custom". If you do as simple asPlotTheme->{"custom"}, it doesn't work...resolvePlotThemedoesn't have this kind of weird behavior. – Stitch Feb 28 '17 at 23:32