Получение текущего контекста из пакета

У меня было что-то вроде следующего в моей записной книжке.

test1[g_] := (g == 5);
test2[g_] := (g == 6);
tests={"test1", "test2"}
ToExpression[#][5] & /@ tests

Когда я помещаю этот код в пакет, он не работает, потому что test1 теперь называется MyPackage'Private'test1. Как я могу изменить последнюю строку, чтобы этот код запускался как внутри пакета, так и внутри ноутбука?

Обновление Вот почему я использовал ToExpression, а не символы. Оглядываясь назад, может быть, вместо этого проще использовать символы.

У меня была функция, которую я вызываю как getGraphs["LeafFree","Planar",!"Tree",...], чтобы получить все графы без листьев, планарные, а не деревья. Некоторые из этих строк являются классами в GraphData, а другие — моими собственными классами. Для каждого из моих собственных классов у меня была функция с одинаковым именем, например LeafFree, которая проверяла свойство. В записной книжке использование кода ToExpression, как показано выше, было самым быстрым способом реализовать это.

getGraphs[n_Integer, cl__] := getGraphs[{n, n}, cl];
getGraphs[{nmin_Integer, nmax_Integer}, cl__] := 
 Module[{maxgraphnum = 100},
  customClasses = {"isLeafFree", ! "isLeafFree"};
  classes = {cl}\[Backslash]customClasses;
  builtinClasses = 
   GraphData["Classes"] \[Tilde] (Not /@ GraphData["Classes"]);
  Assert[classes \[Subset] builtinClasses];
  isLeafFree[gname_] := 
   FreeQ[GraphData[gname, "DegreeSequence"], 0 | 1];

  posClasses = Cases[classes\[Backslash]customClasses, _String];
  posGroup = 
   If[posClasses == {}, GraphData[nmin ;; nmax], 
    GraphData[posClasses, nmin ;; nmax]];
  negClasses = classes\[Backslash]posClasses;
  negGroups = GraphData[#[[1]], nmin ;; nmax] & /@ negClasses;

  result = Complement[posGroup, Sequence @@ negGroups];
  customTest[g_] := 
   And @@ (ToExpression[#][g] & /@ ({cl} \[Intersection] 
        customClasses));
  (*result=Take[result,Min[Length[result],100]];*)

  result = Select[result, customTest]
  ]

person Yaroslav Bulatov    schedule 28.01.2011    source источник
comment
Вы пробовали StringJoin[Context[], #] & /@ {"test1", "test2"}   -  person Dr. belisarius    schedule 28.01.2011
comment
Я бы назвал ToExpression утками плохого стиля. Есть ли причина использовать строку вместо символов?? Я бы посчитал, что testsAsSymbols = {test1, test2} и Through[testsAsSymbols[5]] намного лучше -- и будет ли это работать в обоих случаях?   -  person Janus    schedule 28.01.2011
comment
@Janus: Вы должны сделать это ответом ...   -  person Simon    schedule 28.01.2011
comment
Ха! Сегодня все стесняются :)   -  person Dr. belisarius    schedule 28.01.2011
comment
belisarius: да, не работает, смотрите мои комментарии ниже. Янус: я использовал ToExpression, потому что я делаю что-то вроде MyPackage'doChecks[{"test1","test2"}], где test1 — это имя функции, определенной внутри функции doChecks.   -  person Yaroslav Bulatov    schedule 29.01.2011


Ответы (2)


Я согласен с комментариями выше, что у вас, возможно, должна быть веская причина для этого, но вот. Вот код, который я использую в таких случаях, который позволяет анализировать ваш символ в любом контексте, который вам нравится, во время выполнения:

SetAttributes[ParseTimeNameSpaceWrapper,HoldFirst];
Options[ParseTimeNameSpaceWrapper] = {
  LocalizingContext->"MyLocalizingContext`",
  DefaultImportedContexts:>{"Imported1`", "Imported2`"},
  ExtraImportedContexts:>   {}
};



ParseTimeNameSpaceWrapper[code_,opts:OptionsPattern[]]:=
Module[{result,
  context = OptionValue[LocalizingContext],
  defcontexts = OptionValue[DefaultImportedContexts],
  extraContexts = OptionValue[ExtraImportedContexts],
  allContexts},
  allContexts = {Sequence@@defcontexts,Sequence@@extraContexts};
  BeginPackage[context,If[allContexts==={},Sequence@@{},allContexts]];      
    result = code;  
  EndPackage[];
  result
]; 

Вы можете использовать параметры, чтобы указать некоторые контексты, в которых существуют эти символы, которые вы хотите импортировать на этапе синтаксического анализа. Вы можете вызвать это из любого пакета или блокнота, и символ будет проанализирован в соответствии с любым контекстом, который вы укажете.

ХТН

Редактировать:

Отвечая на комментарий (поскольку он сделал вопрос более конкретным): нет никаких сомнений в том, что во время выполнения Context[] будет отображать любой текущий контекст, из которого была вызвана функция (глобальный в этом случае). Я имел в виду другое: Context имеет синтаксис Context[symbol], чтобы дать контекст любого символа, если он находится на $ContextPath. Например, Context[getGraphs] возвращает Bulatov'showGraphs'. Поэтому, если вам нужно автоматически определить контекст какой-либо экспортируемой функции, вы вызываете Context[function]. Вы можете использовать это для создания полных имен других (частных) функций этого пакета. Вот самодостаточный пример:

In[1]:= 
BeginPackage["MyTest`"]; 
f[x_, y_, context_: Context[f]] :=
  Module[{f1str = "function1", f2str = "function2", f1, f2}, 
    {f1, f2} = ToExpression[context <> "Private`" <> #] & /@ {f1str, f2str};
    f1[x, y];
    f2[x, y];];

Begin["`Private`"];

function1[x_, y_] :=  Print["In function1: arguments are ", x, " , ", y];
function2[x_, y_] :=  Print["In function2: arguments are ", x, " , ", y];

End[]
EndPackage[];

Out[6]= "MyTest`Private`"

In[8]:= f[1, 2]

During evaluation of In[8]:= In function1: arguments are 1 , 2

During evaluation of In[8]:= In function2: arguments are 1 , 2

где x,y — это просто примеры аргументов. Затем вы никогда не указываете последний аргумент, но вы можете использовать переменную context внутри своей функции для создания длинных имен для других ваших функций, как в приведенном выше примере кода. Или вы можете просто использовать Context[f] внутри тела функции и не добавлять к ней никаких аргументов.

person Leonid Shifrin    schedule 28.01.2011
comment
Проблема в том, что я не вижу способа получить правильный контекст. У меня есть функции f,g,h. f помещается в контекст Global на Needs, тогда как g и h остаются в MyPackage'Private. В идеале f должен получить имя пакета программно, но его нет в Context[] во время выполнения f - person Yaroslav Bulatov; 29.01.2011
comment
@Ярослав: Импорт функции в контекст Global` не делает ее принадлежащей Global. Its context is still the same as before. It is just that for symbols from contexts on the $ContextPath, we can use short names. If I understood correctly and f` принадлежит к тому же пакету, что и g и h (только не подпакет Private), тогда вы можете просто сделать Context[f] ‹› Private" to reconstruct the context where gand h` are, а затем создайте их длинные имена и передайте их ToExpression (конечно, Context[symbol] будет надежно работать только при отсутствии затенения, что я предполагаю верно). - person Leonid Shifrin; 29.01.2011
comment
Если я помещу Print[Context[]] внутрь f[], и я получу Global, несмотря на то, что f определен внутри пакета, вот отдельный пример -- yaroslavvb.com/upload/save/shifrin.zip - person Yaroslav Bulatov; 29.01.2011
comment
@Ярослав Я обновил свой пост, надеюсь, это проясняет, что я имел в виду. Также прошу прощения, что ухожу сейчас, в СПб сейчас 4:50 утра. В настоящее время. - person Leonid Shifrin; 29.01.2011

ToExpression использует текущую привязку $Context при создании символов, поэтому вы можете принудительно интерпретировать ваше выражение в определенном контексте следующим образом:

Block[{$Context="MyPackage`Private`"}, ToExpression[#][5]] & /@ tests

Я не уверен, что понимаю обстоятельства исходного вопроса. Вы можете получить текущий контекст, используя $Context или Context[]... но ToExpression будет автоматически использовать текущий контекст без вмешательства.

Если я запускаю выставленный код в блокноте, он работает нормально. Если я запускаю это так:

Begin["MyPackage`Private`"]
test1[g_] := (g == 5);
test2[g_] := (g == 6);
tests = {"test1", "test2"}
ToExpression[#][5] & /@ tests
End[]

... он также работает нормально. Я могу заставить его потерпеть неудачу, если я запущу его так:

(* in the package file *)
Begin["MyPackage`Private`"]
test1[g_] := (g == 5);
test2[g_] := (g == 6);
End[]

(* in the notebook *)
tests = {"test1", "test2"}
ToExpression[#][5] & /@ tests

... который не только терпит неудачу, но и создает ложные символы в контексте блокнота. Вы можете обойти эту проблему, используя рецепт Block выше.

Если вы хотите зафиксировать контекст, действовавший в момент загрузки кода пакета, вы можете сделать что-то вроде этого:

(* in the package *)
Begin["MyPackage`Private`"]
test1[g_] := (g == 5);
test2[g_] := (g == 6);
tests = {"test1", "test2"};
With[{context = $Context},
  runTests[] := Block[{$Context = context}, ToExpression[#][5]] & /@ tests
]
End[]

(* in the notebook *)
MyPackage`Private`runTests[]

runTests использует With для внедрения контекста закрытого пакета в его определение.

Как отметил Янус, лучше использовать символы, чем строки, поскольку они автоматически управляют всей проблемой контекста, но это предполагает, что ваш фактический вариант использования разрешает использование символов.

person WReach    schedule 28.01.2011
comment
Причина, по которой Context[] не работает, заключается в том, что закрывающая функция помещается в глобальный контекст на Needs, поэтому Context[] является глобальным в этой точке выполнения, а тест1, тест2 являются внутренними функциями, поэтому они остаются в контексте MyPackagePrivate - person Yaroslav Bulatov; 29.01.2011
comment
Метод с Block в верхней части вашего ответа прост и элегантен, но (молча) потерпит неудачу, если символ с таким же коротким (строковым) именем существует в некоторых контекстах, которые в настоящее время находятся в $ContextPath (что не обязательно подразумевает затенение). Простой пример: f[x_] := x^2; Block[{$Context = "Test'Private'"}, ToExpression["f"][x]]. Это одна из причин, по которой я использую BeginPackage - EndPackage в своем ответе - они решают эту проблему. Или вы можете использовать Block[{$Context = "Test'Private'", $ContextPath = {"Test'", "System'"}},...] явно. - person Leonid Shifrin; 29.01.2011