动态语言允许从值仅在运行时知道的变量调度和调用值。 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
,其中函数可能是名称文字或值为函数的变量我主要对这个问题标签中提到的三种语言感兴趣,但是如果你知道一种不同的动态语言,你也可以回答。
答案 0 :(得分: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提供全局和本地dicts,js只提供全局dicts,因此除了无处不在的“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
更正式地说,python / js中的点运算符.
(相当于php的::
和->
)需要右侧的标识符,并允许任意表达式左边。由于一切都是对象,如果左侧表达式返回一个字符串,则该点应用于该字符串,而不是该字符串恰好等于的变量。
(作为旁注,在点的右侧允许表达式在技术上也是可行的,这样foo.('bar' + 'baz')
将解析为foo.barbaz
。我不知道语言支持这种语法,但它似乎是getattr
和类似方法的一个很好的替代方案。)
类似地,调用操作符()
允许左侧的复杂表达式,此外该表达式必须解析为“可调用”对象。因此,"someString"()
没有意义,因为字符串通常不可调用(除非你以某种方式破解它们以便它们)。
也就是说,如果您有一个包含变量名的字符串,则必须在使用前明确解析它。在幕后为你做这件事的语言没有什么神奇之处。
答案 1 :(得分:4)
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 conversion。让我们设置上下文来解释。
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
答案 2 :(得分:4)
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中的hackish。只有当您处理高度动态的数据结构时(例如,从某种自我文档文件中读取用户数据,告诉您自己的字段被称为什么),才可以使用getattr
等等)或某种元编程(例如,插件框架)。对于这些简单的案例,使用像globals()["some_class"]()
这样的东西会被认为是非常不合理的。
答案 3 :(得分:1)
简而言之:这些其他语言的设计者(以及我所知道的其他语言的设计者)并没有创建这种歧义,字符串可以被解释为一个实体名字,首先。这种转换总是明确的。
所以,没有什么可以消除歧义。
答案 4 :(得分:0)
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;
}
}