파이썬은 문자열을 다룰때 다양한 형태로 문자열을 다룰 수 있습니다. C나 Java같은 언어에서는 문자열을 쌍따옴표로 둘러싸서 문자열을 표현하지만 파이썬은 ', ", ''' 로 둘러싸서 문자열을 표현할 수 있죠. 이렇게하는 이유는 ', " 가 문자열에 글자로 표현될 때 문자열의 종료나 시작으로 인식하지 않게 하기 위함입니다.

str1 = 'python'
str2 = "python"
str3 = '''python'''

print (str1, str2, str3)

str1 = '"python"'
str2 = "'python'"
str3 = '''"python"'''

print ( str1, str2, str3)
python python python
"python" 'python' "python"

 

이제부터 파이썬의 문자열에 대해서 알아보도록 하겠습니다. 

문자열 포맷

- 포맷 문자를 통한 문자열 포맷

C와 같이 문자열에 다른 데이터를 포함시키려면 format 문자를 사용해서 정수든, 글자든 입력받을 수가 있죠.

포맷 문자 설명
%d 10진수 정수 (Decimal)
%c 문자 (Character)
%f 부동 소수 (Floating Point)
%o 8진수 정수 (Octal)
%x 16진수 정수 (Hexadecimal)
%s 문자열 (String)
%% % 문자

 

C에서 지원하는 무자열 형태의 format은 거의다 지원한다고 보시면됩니다. 그렇기 때문에 아래의 링크를 통해서 더 많은 문자열 포맷을 활용하는 방법을 알아보시기 바랍니다.

reakwon.tistory.com/169

 

[C언어] 출력 형식(format) 총정리 (Feat. sprintf, fprintf) - 일정한 간격으로 문자열 출력 예제 까지

C언어의 다양한 출력 문자들 C언어에서 다양한 출력 형식을 지원합니다. 우리가 너무나 잘 알고 있는 부호있는 정수형은 %d, 문자열 출력은 %s 등이 그 출력형식인데요. 오늘은 자세하게 한번 총

reakwon.tistory.com

 

형식을 지정할 데이터는 문자열 끝 %를 이용해서 나열해줍니다.

print ('구구단 2단')
for i in range(10):
    print ('%d * %d = %d' % (2, i, 2*i) )
구구단 2단
2 * 0 = 0
2 * 1 = 2
2 * 2 = 4
2 * 3 = 6
2 * 4 = 8
2 * 5 = 10
2 * 6 = 12
2 * 7 = 14
2 * 8 = 16
2 * 9 = 18

 

- format함수를 통한 문자열 포맷

이와 같은 방식은 우선 자료형에 따른 포맷 문자를 알고있어야하는 단점이 있습니다. 파이썬 3부터는 포맷을 알고 있지 않아도 사용할 수 있는 방법은 format함수를 사용할 수 있습니다. 여기서 중괄호를 이용합니다. 숫자를 입력하여 차례대로 입력받을 수도 있고, 명시적으로 이름을 지정해서 사용할 수도 있습니다.

str = 'str.{} example'.format('format')
print(str)
                                                #  {0}      {1}        {2}          {3}
str = 'SELECT {0} FROM {1} WHERE {2} = {3}'.format('*', 'accounts', 'email', 'reakwon@gmail.com')
print (str)

str = 'name : {name}, age : {age}'.format(name='kim',age=22)
print (str)
str.format example
SELECT * FROM accounts WHERE email = reakwon@gmail.com
name : kim, age : 22

 

- f String을 사용한 문자열 포맷

문자열을 저장할때 가장 맨 앞에 f를 준 후 변수명을 그대로 문자열에 중괄호로 입력하면 그 변수명의 데이터가 그대로 문자열에 입력됩니다. f는 format의 약자라는 점은 참고해주세요. 이 f string은 파이썬 3.6부터 지원합니다.

name = 'shin'
age = 22
score = 80

result = f'name : {name}, age : {age}, score : {score}'
print (result)
name : shin, age : 22, score : 80

 

산술 연산도 할 수 있습니다.

a = 10
b = 20

fstr = f'{a} * {b} = {a*b}, {a} + {b} = {a+b}'
print (fstr)
10 * 20 = 200, 10 + 20 = 30

 

함수의 반환값도 쓸수 있죠.

def mult(a, b):
    return a*b

def add(a, b):
    return a+b

a = 5
b = 9
fstr = f'{a} * {b} = {mult(a,b)}, {a} + {b} = {add(a,b)}'
print (fstr)
5 * 9 = 45, 5 + 9 = 14

 

문자열 메소드

문자열 메소드는 엄청 많은데, 그 중에서 몇가지 문자열 메소드를 알아보도록 하겠습니다. 문자열은 아래의 문자열을 사용해보지요.

paul_rand = 'Do not to be original, just-try-to-be-good.'

 

- 문자열 길이 : len

문자열 내장 메소드는 아니지만 길이를 알고자 하는 경우 len 내장 함수를 쓰면 됩니다.

print(len(paul_rand))
43

 

- 문자수 세기 : count

문자나 문자열의 수를 새려면 count 메소드를 사용하여 확인할 수 있습니다.

print (paul_rand.count('t'))        #t문자 세기
print (paul_rand.count('to'))      #to 문자열 세기
5
2

 

- 대소문자로 변경 : upper, lower, casefold

알파벳을 모두 소문자로 변경하려면 lower 메소드 사용하면 되는데 소문자로 변경하는 메소드는 casefold라는 메소드도 있습니다. 반대로 모두 대문자로 변경하려면 upper를 사용하면 됩니다.

print (paul_rand.upper())   #모두 대문자로 변경
print (paul_rand.lower())   #모두 소문자로 변경
print (paul_rand.casefold())
DO NOT TO BE ORIGINAL, JUST-TRY-TO-BE-GOOD.
do not to be original, just-try-to-be-good.
do not to be original, just-try-to-be-good.

 

- 문자열 분리 : split

문자열을 공백, 또는 지정된 나누려고 구분된 구분자에 따라서 문자열을 쪼개고 싶다면 split 메소드를 사용할 수 있습니다. 인자를 넣어주지 않는다면 공백을 기준으로 나누고, 지정한 문자열을 넘겨주면 그 문자열을 기준으로 문자열을 나눕니다. 쪼개어진 문자열들은 리스트 형태로 넘겨줍니다.

tokens = paul_rand.split()
print (tokens)

tokens = paul_rand.split('-')
print (tokens)
['Do', 'not', 'to', 'be', 'original,', 'just-try-to-be-good.']
['Do not to be original, just', 'try', 'to', 'be', 'good.']

 

- 문자열 공백 지우기 : strip, lstrip, rstrip

문자열에 공백을 제거하려면 strip 메소드를 사용하면 됩니다. 특별히 왼쪽 공백은 lstrip, 오른쪽 공백은 rstrip을 사용하면 됩니다.

str = '  __name__  __main__    '

print (str.lstrip())    # 왼쪽 공백 제거
print (str.rstrip())    # 오른쪽 공백 제거
print (str.strip())     # 양쪽 공백 제거
__name__  __main__    
  __name__  __main__
__name__  __main__

 

- 문자열 위치 : find, index, rfind, rindex

문자열에서 특정 문자열이 어느 위치에 있는지 확인하려면 find와 index를 사용하면 됩니다. 이때 가장 첫번째로 등장한 위치를 반환합니다. find와 index의 차이점은 문자를 찾지 못할때는 에러를 발생시키느냐 마냐입니다. find는 못찾으면 -1을 반환하고 index는 에러를 발생시킵니다.

print ('o : ', paul_rand.find('o'))
print ('. : ', paul_rand.index('.'))
print ('original : ', paul_rand.find('original'))
print ('just : ' ,paul_rand.index('just'))

print ('6 :', paul_rand.find('6'))      #없는 문자열의 경우 -1 반환
print ('6 :', paul_rand.index('6'))     #없는 문자열의 경우 에러

o :  1
. :  42
original :  13
just :  23
6 : -1
Traceback (most recent call last):
  File "C:\Users\grjwu\PycharmProjects\pythonProject1\main.py", line 9, in <module>
    print ('6 :', paul_rand.index('6'))
ValueError: substring not found

 

또는 start와 end 인덱스를 지정하게 되면 그 부분에 대해서만 찾아오게 됩니다.

print (paul_rand.find('to',4,9))    # 글자위치 4부터 9 전까지 탐색
print (paul_rand.index('or',10,19)) # 글자위치 10부터 19 전까지 탐색
7
13

 

왼쪽이 아니라 오른쪽에서 찾아보고 싶다면 rfind와 rindex를 사용하면 됩니다. 이때 결과는 위의 index와 find와 동일하며 오류내는 것도 동일합니다.

good = 'good, good, good~'

print ('o : ', good.rfind('o'))
print ('g : ', good.rindex(','))
o :  14
g :  10

 

 

- 문자열 변경 : replace

특정 문자열을 변경하고 싶다면 replace메소드를 사용하여 바꿀 수 있습니다.

print (paul_rand.replace('to','TO'))
Do not TO be original, just-try-TO-be-good.

 

- 특정 문자열로 시작하느냐 끝나느냐 - startswith, endswith

우리가 지정한 문자열로 시작하느냐를 알아보고 싶다면 startswith, 끝이 나는가를 알아보려면 endswith 메소드를 사용하면 됩니다. 지정된 문자열로 시작, 끝이 나면 True를, 아니면 False를 반환합니다.

print (paul_rand.startswith('Do'))
print (paul_rand.endswith('.'))

print (paul_rand.startswith('The'))
print (paul_rand.endswith('!'))
True
True
False
False

 

- 문자열 삽입 : join

특정 문자열을 문자마다 삽입하고 싶다면 join을 사용하면 됩니다. 글자마다 우리가 지정한 문자열이 삽입되고 만약 단어마다 문자열 삽입을 원한다면 리스트 형태의 문자열 리스트를 전달해주면 됩니다.

 

str = 'ABCDE'
print ("=".join(str))

str = ['Apple','Banana','Cherry']
print (', '.join(str))
A=B=C=D=E
Apple, Banana, Cherry

 

- 탭 간격 조정 : expandtabs

탭의 간격을 조정하는 메소드는 expandtabs입니다. 

str = "h\te\tl\tl"
print (str)
print (str.expandtabs(2))
print (str.expandtabs(4))
print (str.expandtabs(10))
h	e	l	l
h e l l
h   e   l   l
h         e         l         l

 

여기까지 파이썬 문자열의 활용방법과 메소드 들에 대해서 알아보았습니다. 여기서 소개하지 않은 메소드도 많이 있으므로 그때 그때 구글링하여 사용하시기 바랍니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

쓰레드(Thread)

쓰레드(Thread)는 간단히 정의하면 하나의 프로세스(실행중인 프로그램이라고 합니다.)에서 독립접으로 실행되는 하나의 일, 또는 작업의 단위를 말합니다. 뭐, 더 간단히 말해 쓰레드를 여러개 사용하면 동시에 여러 작업을 할 수 있는 뜻이 되는 것이죠. 

우리가 익히 알고 있는 main 함수 역시 쓰레드입니다. 프로그램 실행 시 실행되는 첫번째 쓰레드이기 때문에 main함수를 우리는 메인 쓰레드라고도 합니다.

 

쓰레드를 사용해서 얻을 수 있는 이점이 있을 텐데 어떤 이점들이 있을까요?

 

  • 우선 가장 두드러진 장점은 바로 동시성입니다. 동시에 여러 일들을 할 수 있습니다. 그렇기 때문에 작업의 효율성을 높일 수 있습니다.
  • 쓰레드끼리 메모리를 공유합니다. 그렇기 때문에 메모리가 절약되는 효과를 볼 수 있습니다. 경제적인 것이죠.

그렇다고 해서 장점만 있는 것은 아닙니다. 아래와 같은 상황을 고려해야하죠.

 

  • 프로그램의 실행 단위가 많아지면 프로그램이 상당히 복잡해질 수 있습니다.
  • 예상치 못한 버그가 생길 수 있습니다. 이를 위해서 적절히 동기화를 걸어주어야 하지요.
  • 교착상태나 기아상태로 빠질 수 있습니다.

 

 

 

자바 쓰레드 사용

자바에서는 대표적인 두 가지 쓰레드를 생성하는 방법이 있는데요. 아래의 두 가지 방법을 활용하여 쓰레드를 생성해봅시다.

 

1. Thread 클래스를 상속받는 방법

2. Runnable 인터페이스를 구현하는 방법

 

1. Thread 클래스를 상속받는 방법

아래의 코드처럼 Thread 클래스를 상속합니다.

실행되는 각 쓰레드를 구별 짓기 위해서 쓰레드 이름을 저장하고 단지 for문에서 100까지 도는 아주 간단한 쓰레드입니다. 주의 해야할 것은 쓰레드의 실제 override하는 메소드는 run이지만, 쓰레드 호출 시 실행하는 메소드는 start라는 점을 주의하시기 바랍니다. 

class MyThread extends Thread{
	
	public MyThread(String threadName){
		super(threadName);
	}
	public void run(){
		for(int i=0;i<100;i++){
			System.out.println(this.getName()+":"+i);
		}
		System.out.println();
	}
}
public class ThreadTest {

	public static void main(String[] ar){
		System.out.println("MainThread Start");
		for(int i=1;i<=3;i++){
			new MyThread("Thread"+i).start();
		}
		System.out.println("MainThread End");	
	}
	
}

 

위의 코드에서 프로그램을 예상할 수 있나요? 평소의 프로그램(싱글쓰레드)같았다면 우리는 이렇게 예상을 했을 겁니다. 

MainThread Start

Thread1:0

Thread1:1

Thread1:2

Thread1:3

...

Thread2:0

Thread2:1

Thread3:2

...

Thread3:98

Thread3:99

MainThread End

하지만 우리는 동시성을 원하니까 쓰레드를 돌렸지요. 그래서 위의 결과가 될 수도 있고(진짜 운이 드럽게 좋다거나 없다면) 아닐 수도 있습니다. 

대부분의 결과는 아래와 같이 제 얼굴처럼 뒤죽박죽된 결과가 발생합니다. 아래 결과는 실제 제 똥컴에서 돌린 결과입니다.

MainThread Start 
MainThread End 
Thread1:0 
Thread1:1 
Thread1:2 
Thread1:3

...

Thread1:59 
Thread1:60 
Thread2:0 
Thread2:1

...

Thread2:98 
Thread2:99 

Thread1:61 
Thread3:0 
Thread3:1

...

Thread1:97 
Thread1:98 
Thread1:99

 

쓰레드의 실행 순서는 이렇게 예측할 수 없습니다.

 

 

 

2. Runnable 인터페이스 구현

실제로 쓰레드를 생성할때 많이 사용하는 방법입니다. Runnable 인터페이스를 구현한 클래스를 Thread의 생성자로 주입하여 실행하는 방법이죠. 아래의 코드를 봅시다. 

 

달라진게 있다고 한다면 implements Runnable 쪽인것 같네요. 이렇게 구현한 MyThread라는 클래스를 객체화하여 main 메소드에서 실행시키고 있습니다.

Thread라는 클래스의 생성자로 Runnable 객체를 전달하고 있습니다.

Thread thread=new Thread(new MyThread("Thread"+i));
thread.start();

 

class MyThread implements Runnable{
	private String threadName;
	public MyThread(String threadName){
		this.threadName=threadName;
	}
	public void run(){
		for(int i=0;i<100;i++){
			System.out.println(threadName+":"+i);
		}
	}
}

public class ThreadTest {

	public static void main(String[] ar){
		System.out.println("MainThread Start");
		for(int i=1;i<=3;i++){
			Thread thread=new Thread(new MyThread("Thread"+i));
			thread.start();
		}
		System.out.println("MainThread End");	
	}
	
}

 

결과는 1번의 Thread 클래스를 상속받는 방법과 비슷하게 순서를 예측할 수 없이 실행됩니다.

MainThread Start 
MainThread End 
Thread2:0

...

Thread1:62 
Thread2:93 
Thread2:94

...

Thread3:97 
Thread3:98 
Thread3:99

 

저는 항상 Main 쓰레드가 먼저 끝나버리는군요. 

이점에 대해서 불만을 가지고 있는데요. 메인 쓰레드는 항상 다른 쓰레드를 기다렸다가 종료할 수 없을까요?? 자기 할일 끝났다고 먼저 가버리는 것은 조금 매너가 없으니까요. 

 

그래서 join이라는 쓰레드 메소드가 존재합니다.

 

join

join이라는 메소드를 통해서 분기를 어떤 지점에 합칠 수 있습니다. 그러니까 쓰레드를 생성한 쓰레드는 그 지점에서 기다려야합니다. 아래의 코드를 통해서 알아보도록 합시다.

 

알아보려면 코드를 조금 변경해야하는데, 우선 join을 쓰게 되면 InterruptedException이 발생합니다. 처리하기 귀찮으니 throws를 통해서 그냥 던져줍시다. 누군가 알아서 먹든가 하겠죠.

아래 코드처럼 변경해서 실행하면 메인쓰레드는 자신이 생성한 3개의 쓰레드를 끝날때까지 기다렸다가 자신도 종료하게 됩니다.

class MyThread implements Runnable{
	private String threadName;
	public MyThread(String threadName){
		this.threadName=threadName;
	}
	public void run(){
		for(int i=0;i<100;i++){
			System.out.println(threadName+":"+i);
		}
	}
}

public class ThreadTest {

	public static void main(String[] ar) throws InterruptedException{
		System.out.println("MainThread Start");
		Thread[] thread=new Thread[4];
		for(int i=1;i<=3;i++){
			thread[i]=new Thread(new MyThread("Thread"+i));
			thread[i].start();
		}
		
		for(int i=1;i<=3;i++)
			thread[i].join();
		
		System.out.println("MainThread End");	
	}
	
}

 아래의 실행결과처럼 메인쓰레드는 나머지 3개가 종료할때까지 기다립니다.

MainThread Start 
Thread1:0 
Thread1:1

...

Thread3:99

MainThread End

왜 join으로 이름을 정했을까요? 아래의 그림을 보면 이해하기가 쉬울 겁니다. 위 코드의 상황을 그림으로 옮겨놓았습니다.

main은 thread1, thread2, thread3을 수행시킵니다. 이때 thread1, thread2, thread3이 끝나면 다시 자신을 실행시키는 main와 합쳐지지요. 이해하기 쉽죠?

 

 

아 이것도 불만이다. 난 쓰레드를 썼지만 순차적으로 실행하겠다! thread1끝나면 thread2실행하고 thread2끝나면 thread3실행하겠다 하시는 분들은 thread 실행시키자 마자 바로 join거시면 됩니다. 

class MyThread implements Runnable{
	private String threadName;
	public MyThread(String threadName){
		this.threadName=threadName;
	}
	public void run(){
		for(int i=0;i<100;i++){
			System.out.println(threadName+":"+i);
		}
	}
}

public class ThreadTest {

	public static void main(String[] ar) throws InterruptedException{
		System.out.println("MainThread Start");
		Thread[] thread=new Thread[4];
		for(int i=1;i<=3;i++){
			thread[i]=new Thread(new MyThread("Thread"+i));
			thread[i].start();
			thread[i].join();
		}
		
		
		System.out.println("MainThread End");	
	}
	
}

 

아래의 그림에서 점선이 join하는 구간입니다.

 

실행결과는 뭐.. 순차적입니다.

MainThread Start 
Thread1:0 
Thread1:1

...

Thread3:99

MainThread End

 

 

근데 이렇게 구현할 거면 쓰레드 안쓰는 것이 낫죠. 단지 join을 어떻게 사용하는지 보여드린겁니다.

 

간단하게 자바에서 쓰레드를 사용하는 방법을 알아보았습니다. 

 

반응형
블로그 이미지

REAKWON

와나진짜

,