2009-05-22 3 views
46
Foo = Class.new 
Foo.class_eval do 
    def class_bar 
    "class_bar" 
    end 
end 
Foo.instance_eval do 
    def instance_bar 
    "instance_bar" 
    end 
end 
Foo.class_bar  #=> undefined method ‘class_bar’ for Foo:Class 
Foo.new.class_bar #=> "class_bar" 
Foo.instance_bar  #=> "instance_bar" 
Foo.new.instance_bar #=> undefined method ‘instance_bar’ for #<Foo:0x7dce8> 

그냥 메서드의 이름을 기반으로 클래스 메서드를 Foo에 추가하고 instance_eval을 추가하여 Foo에 인스턴스 메서드를 추가 할 수있게 해줄 것으로 기대합니다. 그러나 그들은 그 반대의 것을하는 것처럼 보입니다.class_eval()과 instance_eval()의 차이점을 이해하는 방법은 무엇입니까?

위의 예제에서 Foo 클래스에서 class_bar를 호출하면 정의되지 않은 메서드 오류가 발생하고 Foo.new에서 반환 한 인스턴스에서 instance_bar를 호출하면 정의되지 않은 메서드 오류가 발생합니다. 두 오류 모두 class_eval 및 instance_eval이 수행해야하는 작업을 직관적으로 이해하는 것과 모순되는 것처럼 보입니다.

실제로 이러한 방법의 차이점은 무엇입니까? class_eval위한

문서 :

mod.class_eval (문자열 [파일명 [ LINENO]]) =>

OBJ는 개조의 컨텍스트 문자열 또는 블럭을 평가한다. 이것은 클래스에 메소드를 추가하는 데 사용될 수 있습니다.instance_eval에 대한

문서 :

{obj.instance_eval | |

OBJ 블록} => 수신기 (OBJ)의 컨텍스트 내 루비 소스 코드, 또는 소정의 블록, 를 포함하는 문자열을 평가한다. 컨텍스트를 설정하려면 변수 self가 obj로 설정되고 코드가 실행되고 코드 에 obj의 인스턴스 변수에 대한 액세스 권한이 부여됩니다.

답변

77

입니다. 그래서 다음 코드 조각은 동일합니다 각각의 경우에

class String 
    def lowercase 
    self.downcase 
    end 
end 

String.class_eval do 
    def lowercase 
    self.downcase 
    end 
end 

는, String 클래스가 재개 된 새로운 방법을 정의했다. 그 방법은 그래서, 클래스의 모든 인스턴스에서 볼 수 있습니다 :

"This Is Confusing".lowercase 
=> "this is confusing" 
"The Smiths on Charlie's Bus".lowercase 
=> "the smiths on charlie's bus" 

class_eval 단순히 클래스를 재개 비해 여러 장점을 갖는다.첫째, 변수로 쉽게 호출 할 수 있으며, 의도가 무엇인지 분명합니다. 또 다른 장점은 클래스가 존재하지 않으면 실패합니다. 따라서 Array 철자가 틀리면 아래 예가 실패합니다. 클래스가 단순히 재개 된 경우는 성공 (새로운 잘못된 Aray 클래스를 정의 할 것이다) :

마지막으로 class_eval
Aray.class_eval do 
    include MyAmazingArrayExtensions 
end 

당신이 좀 더 사악한 일을하는 경우 유용 할 수있는 문자열을 취할 수 ... 반면에

instance_eval는 하나의 객체 인스턴스에 대해 코드를 평가 : 그래서

confusing = "This Is Confusing" 
confusing.instance_eval do 
    def lowercase 
    self.downcase 
    end 
end 

confusing.lowercase 
=> "this is confusing" 
"The Smiths on Charlie's Bus".lowercase 
NoMethodError: undefined method ‘lowercase’ for "The Smiths on Charlie's Bus":String 

instance_eval으로,이 방법은 단지 STR의 단일 인스턴스에 대해 정의 ing.

그래서 왜 Classinstance_eval는 클래스 메소드를 정의 하는가?

그냥 "This Is Confusing"로하고 "The Smiths on Charlie's Bus"Array, String, Hash 모두 String 인스턴스 및 기타 모든 클래스는 자신에게 Class의 인스턴스입니다. 당신은 그들에 #class를 호출하여 확인할 수 있습니다 우리가 instance_eval를 호출 할 때

"This Is Confusing".class 
=> String 

String.class 
=> Class 

그래서 그것은 다른 개체에서와 마찬가지로 클래스에서 동일한 않습니다. instance_eval을 사용하여 클래스에 메서드를 정의하면 모든 클래스가 아니라 해당 클래스의 인스턴스에 대한 메서드가 정의됩니다. 이 메소드를 클래스 메소드라고 부를 수도 있지만, 그것은 그 특정 클래스를위한 인스턴스 메소드 일뿐입니다.

+4

이것은 훌륭한 설명입니다. 감사. 나는 마지막 단락까지 계속해서 여러분을 따라갔습니다. 여기서 여러분은 클래스에서 호출 될 때 instance_eval이 클래스 메소드를 정의하는 이유를 설명하려고했습니다. 마지막 줄은 특히 나를 소화하기 어렵습니다. "클래스 메서드를 호출하는 것은 실수입니다. 특정 Class 인스턴스의 인스턴스 메서드입니다." 모든 수업 방법이 그 이상이라는 것을 의미합니까? –

+0

그래, 정확히 - 그게 다야. 나는 내일 더 명확하게하려고 노력할 것이다. – tomafro

+0

환상적 - 방금 문제에 대한 새로운 질문을 추가했습니다. "클래스에서 호출 될 때 instance_eval()이 클래스 메서드를 정의하는 이유는 무엇입니까?" http://stackoverflow.com/questions/900704/why-does-instanceeval-define-a-class-method-when-called-on-a-class. –

5

내가 잘못 생각한 것 같습니다. class_eval은 클래스에 메소드를 추가하므로 모든 인스턴스에 메소드가 있습니다. instance_eval은 하나의 특정 객체에만 메소드를 추가합니다.

foo = Foo.new 
foo.instance_eval do 
    def instance_bar 
    "instance_bar" 
    end 
end 

foo.instance_bar  #=> "instance_bar" 
baz = Foo.new 
baz.instance_bar  #=> undefined method 
+1

Foo 클래스에서 this를 호출하는 동안 객체 foo에서 instance_eval을 호출합니다. 나는 이것이 어떻게 관련되는지 보지 못한다. –

3

instance_eval은 문제의 객체 인스턴스에 대한 싱글 톤 메소드를 효과적으로 만듭니다. class_eval은 해당 클래스의 모든 객체에서 사용할 수있는 특정 클래스의 컨텍스트에서 일반 메서드를 만듭니다. 문서가 말한대로

여기 class_eval는 모듈 또는 클래스의 맥락에서 문자열이나 블록을 평가 singleton methods에 대한 링크와 singleton pattern (특정 비 루비)

+0

링크를 가져 주셔서 감사합니다! –

16

다른 대답은 정확하지만 나를 깊이 조금 갈 수 있습니다.

루비 범위의 다른 종류의 번호를 가지고; 6 개는 wikipedia에 따르면 상세한 공식 문서가 부족한 것처럼 보입니다. 이 질문에 관련된 범위의 종류이다 당연한 일로, 예를클래스.

현재 인스턴스 범위

self의 값에 의해 정의된다. 인스턴스 변수에 대한 참조 (예 : @this)와 마찬가지로 모든 규정되지 않은 메소드 호출이 현재 인스턴스에 전달됩니다.

그러나, def는 메소드 호출되지 않습니다. def으로 작성된 메소드의 대상은 현재 클래스 (또는 모듈)이며 Module.nesting[0]에서 찾을 수 있습니다.

은의이 두 개의 서로 다른 평가 맛이 범위에 어떻게 영향을 미치는지 살펴 보자 : 두 경우 모두

String.class_eval { [self, Module.nesting[0]] } => [String, String] String.instance_eval { [self, Module.nesting[0]] } => [String, #<Class:String>]

는 인스턴스 범위는 _eval가 호출되는 *에 객체입니다.

class_eval의 경우 클래스 범위도 대상 객체가되므로 def은 해당 클래스/모듈에 대한 인스턴스 메서드를 만듭니다.

instance_eval의 경우 클래스 범위는 대상 객체의 (별칭 metaclass, eigenclass) 싱글 톤 클래스가됩니다.오브젝트의 싱글 톤 클래스에서 작성된 인스턴스 메소드는 해당 오브젝트에 대한 싱글 톤 메소드가됩니다. 클래스 또는 모듈에 대한 싱글 톤 메서드는 일반적으로 (다소 부정확하게) 클래스 메서드이라고합니다.

클래스 범위는 상수를 해결하는데도 사용됩니다. 클래스 변수 (@@these @@things)는 클래스 범위로 해석되지만 모듈 중첩 체인을 검색 할 때 단일 클래스를 건너 뜁니다. 싱글 톤 클래스에서 클래스 변수에 액세스하는 유일한 방법은 class_variable_get/set입니다.

+0

+1 또한 여기에 범위를 소개하고 +1뿐만 아니라 다른 답변으로도 소개합니다. – bryantsai

+0

btw, 1.9 미만에서만 작동합니다. – bryantsai

+0

좋은 답변입니다. 호기심에서, 최근의 루비 버전에서는 이런 식으로 작동하지 않는 것으로 보이기 때문에'Module.nesting [0]'을 대체 할만한 것이 있습니까? – antinome

관련 문제