본문 바로가기
Python/자동화도구

python - 문자열 slicing, 긴 문자에서 원하는 데이터 수집하기/가져오기(예제 : 함수 선언명 데이터 분류)

by 하나다음은둘 2023. 2. 22.

긴 문자에서(특정 템플릿이 있는 경우) 원하는 데이터를 수집해서 가져오는 것에 대하여 설명하겠습니다.

(훈수/잘못된 점/가르침 언제든지 환영합니다. 저 또한 코딩 초보로 좋은 정보 공유해 주시면 정말 감사하겠습니다)

class를 만들어 재활용 가능하게 코딩하는 것으로 객체 자체를 잘 모르는 분들은 다른 글들을 확인하시면 좋을 것 같습니다.

 

문자열 데이터 slicing 은 자동화도구를 만드는 최고의 도구인데 실제로 해보려고 하면 익숙해지는데 시간이 걸립니다~ 그리고 직접 하면 할수록 실력이 늘어나는 것을 계속 느끼게 될 겁니다. 필자도 지금 이번 글의 코드보다 나중에는 더 깔끔하게 코딩할 것이라는 것은 의심의 여지가 없습니다. 

 

본론으로 들어가서 이번 자동화도구의 목적은 함수 선언명칭을 분류하는 것입니다.

1
2
void function_1(int arg1, unsigned int arg2, unsigned char arg3);
int function_2(int arg1, unsigned int arg2, unsigned char arg3, int arg4);
cs

위와 같은 함수가 선언되어 있다고 합시다. 해당 문자열을 리턴값, 함수명, 함수 파라미터, 함수 파라미터 타입을 분류해서 데이터를 가져오려고 합니다. 정리하자면

input data : 함수명, output(4개) : 1) 리턴값 2) 함수명 3) 함수 파라미터 각 명칭 4) 함수 파라미터 각 타입
ex) input data   - void function_1(int arg1, unsigned int arg2, unsigned char arg3);
      output(4개) - 1) void 2) function_1 3) arg1, arg2, arg3 4) int, unsigned int, unsgined char

우선 위와 같이 코딩하기 전에 어떻게 값을 반환할 것인지 생각해봐야 합니다. 저는 1) 리턴값, 2) 함수명은 1개만 return 되기 때문에  문자열로 반환하게 하고 3) 함수 파라미터 각 명칭, 4) 함수 파라미터 각 타입은 개수가 random 하기 때문에 list로 저장하여 반환하게 하겠습니다.

1. 함수명 데이터 추출 객체

전체 코드는 아래와 같습니다.(코드 설명은 아래에 있습니다!)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
class FunctionDataslicing:
    def __init__(self, input_data):
        self.input_data = input_data
        self.function_name = ""
        self.return_type = ""
        self.argument_name = []
        self.argument_type = [] 
 
    def get_input(self, input_data):
        self.input_data = input_data
 
    def clear_data(self):
        self.function_name = ""
        self.return_type = ""
        self.argument_name.clear()
        self.argument_type.clear()
 
    def get_function_name(self):
        temp_data = self.input_data
        if temp_data.count("("== 1:
            temp_data = temp_data[:temp_data.rfind("(")]
            self.function_name = temp_data.split()[-1]
        else:
            print("들어온 입력값이 잘못되었습니다.")
            self.function_name = "invalid input"
        return self.function_name
 
    def get_argument_name(self):
        temp_data = self.input_data
        argument_name_list_temp = []
        self.argument_name.clear()
        if temp_data.count("("== 1:
            temp_data = temp_data[temp_data.rfind("("+ 1:temp_data.rfind(")")]
            if temp_data != '' and temp_data != 'void':
                argument_name_list_temp = temp_data.split(',')            
                for i in range(len(argument_name_list_temp)):
                    self.argument_name.append(argument_name_list_temp[i].split()[-1])
            else:
                self.argument_name.append('void function')
        else:
            print("들어온 입력값이 잘못되었습니다.")
            self.argument_name.append("invalid input")
        
        return self.argument_name
 
    def get_argument_type(self):
        temp_data = self.input_data
        self.argument_type.clear()
        argument_list_temp = []
        
        if temp_data.count("("== 1:
            temp_data = temp_data[temp_data.rfind("("+ 1:temp_data.rfind(")")]
            argument_list_temp = temp_data.split(',')
            argument_type_list_temp = []
            for i in range(len(argument_list_temp)):
                argument_type_str = ""
                if temp_data != '' and temp_data != 'void':
                    argument_type_list_temp = argument_list_temp[i].split()
                    for j in range(len(argument_type_list_temp) - 1):
                        argument_type_str += argument_type_list_temp[j]
                        if len(argument_type_list_temp) > 2 and j != len(argument_type_list_temp) - 2:                        
                            argument_type_str += " "
                else:
                    argument_type_str += "void function"
                self.argument_type.append(argument_type_str)
        else:
            print("들어온 입력값이 잘못되었습니다.")
            self.argument_type.append("invalid input")
        return self.argument_type
 
    def get_return_type(self):
        temp_data = self.input_data
        self.return_type = ""
        if temp_data.count("("== 1:
            temp_data = temp_data[:temp_data.rfind("(")]
            temp_list = temp_data.split()
            for i in range(len(temp_list) - 1):
                self.return_type += temp_list[i]
                if i != len(temp_list): 
                    self.return_type += " "
        else:
            print("들어온 입력값이 잘못되었습니다.")
            self.return_type = "invalid value because of invalid input value"
 
        print("self.return_type(FcnW) : ",self.return_type)
        return self.return_type
 
    def get_name_type_of_fcnarg(self, input_data):
        self.get_input(input_data)
        fcn_name = self.get_function_name()
        arg_name = self.get_argument_name()
        arg_type = self.get_argument_type()
        return_type = self.get_return_type()
 
        return fcn_name, arg_name, arg_type, return_type
cs

코드 설명 규칙 - 코드 아래에 설명을 적습니다.(이 글부터 코드 아래에 적겠습니다.)

1) 객체 이름은 FunctionDataslicing 및 init 메써드

1
2
3
4
5
6
7
class FunctionDataslicing:
    def __init__(self, input_data):
        self.input_data = input_data
        self.function_name = ""
        self.return_type = ""
        self.argument_name = []
        self.argument_type = [] 
cs

init 메써드는 내에 인스턴스 변수 5개를 넣었습니다.

line3 : 입력데이터를 넣음

line4 : 함수이름 저장변수(여기서 함수는 입력데이터로 들어오는 함수 이름)

line5 : 리턴타입 저장변수(여기서 함수는 입력데이터로 들어오는 리턴값값)

line6 : 파라미터 이름 저장을 위한 리스트(여기서 함수는 입력데이터로 들어오는 argument 이름(파라미터 이름))

line7 : 파라미터 타입을 저장을 위한 리스트(여기서 함수는 입력데이터로 들어오는 argument 이름(파라미터 타입))

2) getter 매써드 추가

1
2
def get_input(self, input_data):
    self.input_data = input_data
cs

데이터 입력을 받기 위한 getter함수 추가

3) 인스턴트 변수를 clear할 매써드 추가(입력제외)

1
2
3
4
5
def clear_data(self):
    self.function_name = ""
    self.return_type = ""
    self.argument_name.clear()
    self.argument_type.clear()
cs

4) 함수이름 추출 매써드

1
2
3
4
5
6
7
8
9
def get_function_name(self):
    temp_data = self.input_data
    if temp_data.count("("== 1:
        temp_data = temp_data[:temp_data.rfind("(")]
        self.function_name = temp_data.split()[-1]
    else:
        print("들어온 입력값이 잘못되었습니다.")
        self.function_name = "invalid input"
    return self.function_name
cs

void function_1(int arg1, unsigned int arg2, unsigned char arg3);

여기서 함수이름을 추출하는 아이디어는 아주 간단합니다. 그래도 라인 바이 라인으로 설명드리면

라인 2 : input_data를 temp_data에 저장(기존 데이터는 그대로 두기 위해서)

라인 3, 6~8 : '(' 괄호가 1개 이상이면 입력 자체가 잘못된 것이므로 else문으로 가도록 설계하고 잘못된 입력값이 들어왔다는 것을 console창에 알리고 function name에는 잘못된 입력 문자열을 넣었습니다.

라인 4 : 함수 우측에 반드시 '(' 괄호가 존재하게 되니 소괄호가 있는 곳 까지 데이터를 slicing 합니다. 

즉, 라인 4가 실행되면 temp_data에 return type + 함수명이 들어오게 됩니다.

ex)  void function_1(int arg1, unsigned int arg2, unsigned char arg3); → slicing! →void function_1 

라인 5 : temp_data.split()를 하면 list가 반환되게되고 저장되는 것은 ['void', 'function_1'] 이 들어오게 됩니다

 

그리고 temp_data.split()[-1] 마지막 데이터 "function_1" 를 가져오게 되고 해당 값을 인스턴스 변수(self.function_name)에 저장합니다. 이유는 함수가 void function_1(int arg1, unsigned int arg2, unsigned char arg3); 처럼 깔끔하게 생기지 않고

void      function_1  (  int arg1,   unsigned int arg2, unsigned char arg3);  이와 같이 띄어쓰기가 중구난방 으로 생겼을 경우에 아주 유리합니다. 저장된 데이터를 split하면 list에 저장될 때 공란을 없애버리기 때문입니다. 

그리고 temp_data.split()[-1] 마지막 데이터 "function_1" 를 가져오게 되고 해당 값을 인스턴스 변수(self.function_name)에 저장합니다.

라인 9 : 인스턴스 변수(self.function_name) 값을 return하면 함수명 추출 완료!

 

5) 파라미터 이름 추출 매써드

라인 2 : input_data를 temp_data에 저장(기존 데이터는 그대로 두기 위해서)

라인 3 : 임시로 list를 사용하기 위해 선언

라인 4 : 기존에 저장되어 있던 인스턴스변수 self.argument_name 삭제

라인 5 : 13~15 : '(' 괄호가 1개 이상이면 입력 자체가 잘못된 것 이므로 else문으로 가도록 설계하고 잘못된 입력값이 들어왔다는 것을 console창에 알리고 function name에는 잘못된 입력 문자열을 넣었습니다.

라인 6 : 소괄호 사이의 데이터 소괄호 '(', ')' 사이의 데이터 slicing

ex) void function_1(int arg1, unsigned int arg2, unsigned char arg3); → slicing! →int arg1, unsigned int arg2, unsigned char arg3

라인 7, 11~12 : argument가 void(공란 or void일 때)가 아니면 argument존재 만약 void함수면 void function으로 명시함

라인 8 : int arg1, unsigned int arg2, unsigned char arg3 데이터는 ','콤마로 구분되기 때문에 split를 사용하여 구분한다. 구분을 하게 되면 argument_name_list_temp = ['int arg1', 'unsigned int arg2', 'unsigned char arg3'] 가 들어가게 됩니다.

라인 9~10 : argument_name_list_temp = ['int arg1', 'unsigned int arg2', 'unsigned char arg3']  에서 agrgument name만 각각 분류하기 위해서 argument_name_list_temp 의 item 3개 각각을 공란을 split하여 argument명칭을 가져옵니다. 그걸 하기 위해서 argument_name_list_temp 의 length(list의 아이템 개수)만큼 for문을 돌리고 각 원소를 split()를 사용 즉, 공란을 구분하여 리스트로 반환시키면서 거기의 마지막 원소가 argument명칭이기 때문에 -1을 사용하여 데이터를 가져오고 해당 값을 argment_name에 append하여 argument 개수가 아무리 많아도 list에 모두 저장되게 만듭니다.

ex) "int arg1, unsigned int arg2, unsigned char arg3" → split(',') → ['int arg1' , 'unsigned int arg2', 'unsigned char arg3'] → 각 아이템 마지막 원소 split()[-1] → [arg1, arg2, arg3] 

라인 17 : 인스턴스변수 self.argument_name 에 저장된 argument list return! 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def get_argument_name(self):
    temp_data = self.input_data
    argument_name_list_temp = []
    self.argument_name.clear()
    if temp_data.count("("== 1:
        temp_data = temp_data[temp_data.rfind("("+ 1:temp_data.rfind(")")]
        if temp_data != '' and temp_data != 'void':
            argument_name_list_temp = temp_data.split(',')            
            for i in range(len(argument_name_list_temp)):
                self.argument_name.append(argument_name_list_temp[i].split()[-1])
        else:
            self.argument_name.append('void function')
    else:
        print("들어온 입력값이 잘못되었습니다.")
        self.argument_name.append("invalid input")
        
    return self.argument_name
cs

6) 파라미터 타입 추출 매써드

"5) 파라미터 이름 추출 매써드"와 거의 비슷하나 한가지 추가되는 점이 있다. 해당 줄부터 설명하겠다.

라인13 : 아래와 같은 문제가 있어서 한번 더 데이터를 가공하기 위해 일단 각 list내 저장되어 있는 값을 공란을 비교하여 저장하여 list값을 만든다. int arg1 -> ['int, 'arg1']

ex) 문제점은 "int arg1, unsigned int arg2, unsigned char arg3" → split(',') → ['int arg1' , 'unsigned int arg2', 'unsigned char arg3'] →가져오고자 하는 문자수가 int 1개, unsgined int 2개처럼 개수가 다양하다는 게 문제이다.

라인14~17 : ['int, 'arg1']를 다시 문자열로 넣을 것인데 argument_type_str 문자열 변수에다가 int + arg1을 사용한다 라인 16, 17은 중간에 공란이 필요하기 때문에 마지막 데이터가 아니면 공란을 추가하도록 코딩되어 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def get_argument_type(self):
    temp_data = self.input_data
    self.argument_type.clear()
    argument_list_temp = []
        
    if temp_data.count("("== 1:
        temp_data = temp_data[temp_data.rfind("("+ 1:temp_data.rfind(")")]
        argument_list_temp = temp_data.split(',')
        argument_type_list_temp = []
        for i in range(len(argument_list_temp)):
            argument_type_str = ""
            if temp_data != '' and temp_data != 'void':
                argument_type_list_temp = argument_list_temp[i].split()
                for j in range(len(argument_type_list_temp) - 1):
                    argument_type_str += argument_type_list_temp[j]
                    if len(argument_type_list_temp) > 2 and j != len(argument_type_list_temp) - 2:                        
                        argument_type_str += " "
            else:
                argument_type_str += "void function"
            self.argument_type.append(argument_type_str)
    else:
        print("들어온 입력값이 잘못되었습니다.")
        self.argument_type.append("invalid input")
    return self.argument_type
cs

7) 리턴 타입 추출 매써드

"4) 함수이름 추출 매써드" 의 방법과 거의 동일하나 타입이 추가되는 것으로 타입의 문자열이 개수가 여러 개일 수 있으므로 "6) 파라미터 타입 추출 매써드" 에서의 공란을 추가하는 것과 비슷하게 공란을 추가하였다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def get_return_type(self):
    temp_data = self.input_data
    self.return_type = ""
    if temp_data.count("("== 1:
        temp_data = temp_data[:temp_data.rfind("(")]
        temp_list = temp_data.split()
        for i in range(len(temp_list) - 1):
            self.return_type += temp_list[i]
            if i != len(temp_list): 
                self.return_type += " "
    else:
        print("들어온 입력값이 잘못되었습니다.")
        self.return_type = "invalid value because of invalid input value"
 
    print("self.return_type(FcnW) : ",self.return_type)
    return self.return_type
cs

8) 모든 데이터(함수명, 파라미터 명, 파라미터 타입, 리턴 타입) 추출 매써드

유저가 한 번에 데이터를 받아올 수 있도록  위에 정의한 각각의 매써드를 실행하여 데이터를 가져올 수 있도록 만들고 return을 아래와 같은 순서대로 리턴한다.

1
2
3
4
5
6
7
8
def get_name_type_of_fcnarg(self, input_data):
    self.get_input(input_data)
    fcn_name = self.get_function_name()
    arg_name = self.get_argument_name()
    arg_type = self.get_argument_type()
    return_type = self.get_return_type()
 
    return fcn_name, arg_name, arg_type, return_type
cs

2. 실제 사용

DataSlicingOnt.py 로 모듈을 만들었습니다.

import하여 객체를 생성하고 해당 객체에 데이터를 넣어 아래와 같이 print하여 출력값 4개를 각각 가져왔습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import os
 
import DataSlicingOnt
 
input_data = "void function_1(int arg1, unsigned int arg2, unsigned char arg3);"
input_data2 = "int      function_2(int arg1, unsigned int arg2, unsigned char arg3, int arg4);"
input_data3 = "void function_3()"
input_data4 = "void function_4(void)"
 
myDataSlicing = DataSlicingOnt.FunctionDataslicing(input_data)
 
fcn_name, arg_name, arg_type, return_type = myDataSlicing.get_name_type_of_fcnarg(input_data2)
print("fcn_name : ",fcn_name)
print("arg_name : ",arg_name)
print("arg_type : ",arg_type)
print("return_type : ",return_type)
cs

결과 값은 아래와 같이 잘 나오는 것을 확인할 수 있습니다.

 

ps. 너무 복잡하게 코딩을 한 게 아닌가 싶습니다. 점점 더 노력하여 더 깔끔하고 간결한 코딩을 만들 수 있도록 노력하겠습니다. 감사합니다.

댓글