Как языки, чьи переменные не имеют сигил, имеют дело с динамической отправкой / вызовом?
Динамические языки позволяют отправлять и вызывать значения из переменных, значения которых известны только во время выполнения. Контрастные примеры в Perl:
имена классов
постоянная
Foo::Bar->some_method Foo::Bar::->some_method 'Foo::Bar'->some_method
Все они идентичны, за исключением первого случая. Если в области действия определена подпрограмма с таким именем, отправка происходит по возвращаемому значению, что приводит к трудным для понимания ошибкам. Указанные версии всегда безопасны.
динамический
my $class_name = 'Foo::Bar'; $class_name->some_method
имена методов
постоянная
Some::Class->foo_bar
динамический
my $method_name = 'foo_bar'; Some::Class->$method_name
имена функций
постоянная
foo_bar; (\&foo_bar)->()
динамический
my $function_name = 'foo_bar'; (\&$function_name)->()
Интересно, как языки, чьи имена переменных не имеют сигил (обычно или вообще), справляются с этими проблемами, в частности, как их языковые разработчики устраняли неоднозначность следующего?
- разрешение имени класса
FooBar.some_method
где классFooBar
может быть литералом имени или переменной, значение которой является именем класса - отправка в
SomeClass.foo_bar
где методfoo_bar
может быть литералом имени или переменной, значение которой является именем метода - ссылающееся
foo_bar
где функция может быть литералом имени или переменной, значение которой является функцией
Меня в первую очередь интересуют три языка, упомянутые в тегах этого вопроса, но если вы знаете другой динамический язык без сигил, вы тоже можете ответить.
5 ответов
В python и js (мой ruby немного ржавый) невозможно использовать строку в контексте имени. Если вы попытаетесь это сделать, это будет интерпретировано как операция над самой строкой:
class_name = 'Foo'
object = class_name()
> TypeError: 'str' object is not callable
Строка должна быть сначала разрешена путем поиска в словаре контекста / области видимости:
class Foo:
....
object = globals()['Foo']()
или же
function Foo() ....
object = new window['Foo'];
Python предоставляет как глобальные, так и локальные диктовки, а только глобальные, так что нет никакого способа, кроме вездесущего "eval", получить локальное значение из его имени.
То же самое относится и к методам: вы ищите метод, используя getattr
(python) или оператор косвенной ссылки [...]
(JS):
method_name = 'foo'
method = getattr(some_object, method_name)
method()
Код Javascript немного сложнее, потому что, в отличие от python, возвращаемый указатель метода не связан:
method_name = 'foo'
method = some_object[method_name].bind(some_object)
method()
Без bind
вы не получите правильный this
в методе:
var obj = {
xyz: 42,
method: function() {
document.write(this.xyz)
}
}
var method_name = 'method';
var unbound_method = obj[method_name];
unbound_method() // prints undefined
var bound_method = obj[method_name].bind(obj);
bound_method() // prints 42
Чтобы выразиться более формально, оператор точки .
(эквивалентно PHP ::
а также ->
) в python/js требуется идентификатор справа и допускает произвольные выражения слева. Поскольку все является объектом, если левое выражение возвращает строку, точка применяется к этой строке, а не к переменной, чье имя совпадает с этой строкой.
(Как примечание, технически возможно разрешить выражения и справа от точки, так что foo.('bar' + 'baz')
решил бы foo.barbaz
, Я не знаю языков, которые поддерживают этот синтаксис, но это выглядит как хорошая альтернатива getattr
и аналогичные методы).
Точно так же оператор вызова ()
допускает сложные выражения слева, кроме того, это выражение должно преобразовываться в "вызываемый" объект. Следовательно, "someString"()
не имеет смысла, поскольку строки обычно не могут быть вызваны (если только вы не взломаете их так, чтобы они были).
То есть, если у вас есть строка, содержащая имя переменной, вы должны явно разрешить ее перед использованием. В языке нет магии, которая делает это для вас за кадром.
Python не позволяет обрабатывать объекты так же, как строки, содержащие имена переменных, ссылающихся на эти объекты. Если obj
переменная, значением которой является объект, вы можете сделать FooBar.some_method()
, Если у вас есть строка "FooBar"
, вы должны сделать что-то еще полностью. Что именно вы делаете, зависит от того, где вы ожидаете найти эту переменную "FooBar"
(т.е. это глобальная переменная, локальная переменная, имя атрибута или что). Вы должны найти имя в любом пространстве имен, в котором, по вашему мнению, оно должно быть, а затем выполнить свою операцию с результирующим объектом. Например, если вы хотите интерпретировать "FooBar"
в качестве глобальной переменной вы можете сделать globals()["FooBar"].some_method()
,
Ситуация такая же для функций, так как функции в Python - это просто объекты, как и любые другие. Если у вас есть строка "foo_bar"
что вы думаете, относится к функции с именем foo_bar
в глобальном пространстве имен вы можете сделать globals()["foo_bar"]()
попытаться это назвать.
Для методов ситуация в основном такая же, за исключением того, что для методов вы всегда знаете, в каком пространстве имен вы пытаетесь найти имя метода: это пространство имен объекта, для которого вы пытаетесь вызвать метод. Для этого вы используете getattr
функция. Если у вас есть строка "method_name"
и хочу вызвать метод этого имени на FooBar
, ты сделаешь getattr(FooBar, "method_name")()
,
getattr
Подход также может быть использован для поиска глобальных имен в пространстве имен другого модуля. Если вы думаете function_name
относится к функции в глобальном пространстве имен другого модуля, вы можете сделать getattr(other_module, function_name)
,
Вот некоторые примеры:
def some_function():
print "I am a function!"
class SomeClass(object):
def some_method(self):
print "I am a method!"
function_name = "some_function"
class_name = "SomeClass"
method_name = "some_method"
some_function() # call the function
globals()[function_name]() # call the function
getattr(some_module, function_name)() # if the function was in another module
SomeClass() # make an instance of the class
globals()[class_name]() # make an instance of the class
getattr(some_module, class_name)() # if the class was in another module
instance = SomeClass()
instance.some_method() # call the method
getattr(instance, method_name)() # call the method
Короче говоря, нет никакой двусмысленности, потому что Python не позволяет вам использовать тот же синтаксис, чтобы пытаться делать вещи с объектами и со строками, ссылающимися на объекты. Попытка сделать что-то вроде "obj".method()
прямо однозначно в Python: "obj"
является строкой, поэтому это может означать только то, что вы пытаетесь вызвать метод самой строки. Не делается попытка неявного "декодирования" строки, чтобы увидеть, содержит ли она имя переменной. Кроме того, нет никакой концептуальной разницы между операциями по поиску класса, функции или метода, потому что это все первоклассные объекты в Python. Процедура всегда одна и та же: во-первых, получите пространство имен, в котором вы хотите найти имя; тогда посмотрите. Оба шага должны быть явными.
Стоит также отметить, что использование такого вида поиска на основе строк в globals()
обычно считается хакерским в Python. Использование getattr
и тому подобное считается нормальным, только когда вы имеете дело с высокодинамичной структурой данных (например, чтение пользовательских данных из какого-либо файла самодокументируемого файла, который сообщает вам, как называются его собственные поля) или некоторого вида метапрограммирования (например, плагин рамки). Используя что-то вроде globals()["some_class"]()
для таких простых случаев будет считаться очень пифоническим.
В Ruby вы всегда можете использовать const_get
а также send
:
class FooBar
def some_method
return 42
end
end
class_name = 'FooBar'
puts Module.const_get(class_name).new.some_method
class SomeClass
def foo_bar
return 23
end
end
method_name = 'foo_bar'
puts SomeClass.new.send(method_name)
def foo_bar
return 123
end
function_name = 'foo_bar'
puts send(function_name)
В js вам нужен глобальный поиск имен:
xyz = 100; // notice: no "var" here – it's a global
var varname = 'xyz';
window[varname]; // 100
... или вы всегда можете использовать eval
, но это, скорее всего, собирается укусить вас:
var x = eval;
x("var y = 10"); // INDIRECT call to eval
window.y; // 10 (it worked)
eval("var y = 11"); // direct call
window.y; // still 10, direct call in strict mode gets a new context
Пытаясь получить значение от объекта, вы должны знать, что JS не уважает eta-преобразование. Давайте настроим контекст для объяснения.
var object = {
x: 10,
say: function () {
console.log(this.x);
}
}
var method = 'say';
// that version (the "eta abstraction" version):
object.say(); // "this" inside of say is correctly "object"
object[method](); // equivalent
// ... isn't equivalent to this (the "eta reduction" version):
var sayit = object[method];
sayit(); // "this" inside of say is incorrectly "window" (and thus the property x won't exist)
// You can use Function#bind to work around that, but it's only available in ES5-compatible browsers (and currently pretty slow on v8/chrome)
var sayit = object[method].bind(object);
sayit(); // "this" inside of say has been forced to "object", and will print 10 correctly
// You can also, of course, close over it:
var sayit = function () { object[method]() };
sayit(); // prints 10
В двух словах: разработчики этих других языков (и всех других, которые я знаю, для этой цели) не создавали эту двусмысленность, что строку можно интерпретировать как имя сущности, в первую очередь. Такое преобразование всегда явное.
Таким образом, нет ничего особенного.
Java и другие статические языки предлагают инструменты для размышления посредством использования информации о типах во время выполнения. Вам необходимо повторно ввести информацию о типе.
class SomeClass {
int foo(int x) {
return x * 2;
}
public static void reflectionExample()
throws ReflectiveOperationException {
String someMethodName = "foo";
SomeClass obj = new SomeClass();
// You must select the method by both method name and the signature.
// This is because Java supports overloading.
Method method = SomeClass.class.getMethod(
someMethodName, Integer.TYPE
);
// You must cast the object back to the right return type.
// Java will automatically 'box' and 'unbox' the values.
int result = (Integer) method.invoke(obj, 3);
assert result == 6;
}
}