티스토리 뷰

matlab

eval을 피하는 방법

게으른 the lazy 2022. 6. 2. 14:10

 

eval은 마법의 명령어입니다. 아무리 복잡한 코드도 eval로 모두 생성할 수 있습니다. 그래서인지 많은 분들이 아직도 eval 사용합니다. 특히 변수명을 A1, A2, A3, …처럼 생성하기 위해 eval 자주 사용합니다. 이러한 연속된 이름의 변수 생성은 eval 없이 없기 때문이죠. 게다가 크기가 일정하지 않은 변수들은 하나의 numeric array 담을 없으므로 eval 이용하는 경우가 많습니다.

 

하지만 eval 양날의 검입니다. global 함께 대표적으로 지양해야 top2입니다. 괜히 eval을 evil이라고 하는게 아닙니다. 왜 eval을 쓰지 말아야 하는지, 어떤 대안이 있는지 살펴보겠습니다.

 


 

eval의 문제점

1. 실행속도가 느려질 있습니다.

매트랩은 코드를 처음 실행할 우선 코드를 한번스캔하고 문법오류를 체크한 컴파일을 합니다. 잘못 쓴 게 아닙니다. 매트랩도 컴파일을 합니다. 전문용어로 JIT(Just-In-Time) 컴파일이라고 부릅니다. 일단 컴파일 하고 나면, 코드가 바뀌지 않는 다시 컴파일하지 않습니다. 이런 방식으로 실행속도가 개선됩니다.

 

하지만 eval 쓰인 부분은 런타임 도중에 string 바뀌지 않는다는 보장이 없으므로 실행할 때마다 컴파일합니다. 이로 인해 코드 실행속도가 느려질 있습니다.

 

2. 가독성이 떨어집니다.

% using eval
for i=1:10
    s=['A_' num2str(i,'%02d') '=load(data' num2str(i,'%04d') '.mat;'];
    eval(s);
end

eval 사용하면 코드의 목적을 빠르게 파악하기 어렵습니다. 위보다 아래의 코드가 훨씬 읽히는 것을 있습니다.

% using cell array and sprint
for i=1:10
    fname = sprintf('data%04d.mat',i);
    A{i} = load(fname);
end

 

3. 디버깅이 어려워집니다.

나온 eval 예문은 사실 문법적으로 잘못된 예문입니다. 하지만 코드를 보고 어디가 잘못됐는지 빠르게 발견하기 어렵습니다. 게다가 매트랩이 제공하는 code helper, syntax highlighting 사용할 없습니다.

 

4. 안정성에 이슈가 있을 수 있습니다.

eval 어떤 string이든 의심하지 않고 실행합니다. 실행할 명령어가 workspace 내의 변수를 덮어쓰든, 파일을 지우든, 컴퓨터를 포맷하든 상관하지 않습니다. 이런 치명적인 오류와 실수를 미연에 방지할 방법이 없습니다.

 

5. eval 한번 쓰기 시작하면 계속 쓰게 됩니다.

eval A1, A2, … , A10 변수를 만들었다고 가정합시다. 이후로는 변수들에 접근해야 때마다 eval 사용해야 합니다. 결국 eval 쓰지 않으면 아무것도 없는 코드가 되어버립니다. 이는 코드의 확장성에 문제를 일으킬 있습니다.

 

6. 함수의 dependency를 찾아갈 수 없습니다.

매트랩에는 m파일이 사용하는 sub-m파일들을 모두 찾아주는 기능이 있습니다. 자세한 내용은 이 글을 참고해주세요. 함수 호출 시 eval을 사용하면 이 기능을 사용할 수 없습니다. (아이디어를 주신 찹쌀님께 감사말씀 드립니다.)

 


 

eval 대안

eval 쓰지 않고도 같은 작업을 있는 방법들이 있습니다. 하나씩 살펴보겠습니다.

 

1. 연속된 이름의 변수를 만들어야 하는 경우 (A1, A2, …)

관련된 데이터는 하나의 array 모두 담는 것이 좋습니다. 데이터의 크기나 종류가 서로 다르다면 struct cell array 이용할 있습니다.

%% using cell and struct array
for i=1:10
    fname = sprintf('data%04d.mat',i);
    A{i} = load(fname); % using cell array
    B(i).data = load(fname); %using struct array
end

 

2. 파일 이름이 연속된 경우 (myfile1.mat, myfile2.mat, …)

sprintf 이용하여 문자열을 만들어 사용할 있습니다. 바로 위 코드를 참고해보세요.

 

3. 함수명을 이용한 함수 호출을 경우

@ str2func 이용하면 function handle 만들 있습니다. function handle cell array 만들 수도 있습니다.

%% using cell array of function handles
fns = {@(n)rand(n), @(n)randn(n), @(n)randi(5,[n,n])};
data = zeros(5,5,length(fns));
for i=1:length(fns)
    data(:,:,i) = fns{i}(5);
end

함수명만을 [ ] 안에 넣어서 numeric array처럼 수도 있습니다.

%% using numeric array of function handles
fn = @(n) [rand(n,1) randn(n,1) randi(5,[n,1])];
 
data = fn(10);

feval 이용할 수도 있습니다.

%% using feval
x = 1:10;
y = randi(10,size(x));
fns = {@plot, @bar, @stem};
figure,
for i=1:length(fns)
    subplot(3,1,i);
    feval(fns{i},x,y);
end

 

4. 구조체의 field 이름을 선언할 경우

구조체의 field 이름은 괄호로 묶으면 문자열 변수를 넣을 있습니다.

%% struct field name
for i=1:10
    ifile = sprintf('data%04d.mat',i);
    ifield = sprintf('data%02d',i);
    data.(ifield) = load(ifile);
end

 


 

마치며

절대 배포하거나 확장될 일이 없는 짧은 코드를 짠다면 eval 써도 무방할 수도 있습니다. 하지만 절대 그런 일이 일어나지 않을 확률은 생각보다 높지 않습니다. str2num 간단한 함수이지만 str2double 것을 권장하는데, 이유 하나가 str2num 내부적으로 eval 쓴다는 점입니다.

 

많은 유경험자들이 이렇게 말합니다. “eval 해결해주는 이점보다 eval 낳는 문제가 크다.”  eval 쓰고 싶은 거의 모든 경우에는 eval 쓰지 않는 해결책이 있습니다. 점을 항상 기억하면 좋은 코드를 있습니다.

 

- 게으른 맽랩

 

참고한 곳들

https://kr.mathworks.com/matlabcentral/answers/51946-systematic-do-not-use-global-don-t-use-eval

https://blogs.mathworks.com/loren/2005/12/28/evading-eval/

https://kr.mathworks.com/matlabcentral/answers/304528-tutorial-why-variables-should-not-be-named-dynamically-eval

https://www.mathworks.com/matlabcentral/answers/56124-eval-is-evil-using-variables-created-dynamically-info-retrieval

https://kr.mathworks.com/help/matlab/matlab_prog/string-evaluation.html                                        

https://matlab.fandom.com/wiki/FAQ

 

댓글