[Perl] 정규식으로 문자열 찾기

#!/usr/bin/perl -w

open(F, "cl.txt");

@strings = <F>;

sub getIssue
{
    foreach $line (@strings) {
        $lihttps://ko.perlmaven.com/matching-numbers-using-perl-regexne =~ s/^\s+//;
        $line =~ s/\s+$//;
        if ($line =~ /\[[I|i][S|s][S|s][U|u][E|e]\s*[#]*\]\s*([A-Za-z0-9_.\-]+)/) {
        $desc = $1;
        
        return $desc;
    }

    $desc;
};

$issue = &getIssue();

close(F);

.* 가 모든 문자.

참고 : https://ko.perlmaven.com/matching-numbers-using-perl-regex

[Algorithm] KMP algorithm

String match algorithm인 KMP algorithm은 text에서 뛰어넘을 수 있는 만큼 뛰어넘으면서 string이 match되는지 확인한다. 여기서 얼마나 뛰어넘을 수 있는지에 대한 정보를 얻기 위해 Failure function이라는 함수를 이용한다.

Failure function은 찾으려는 string의 각 위치마다 공통인 prefix와 suffix의 최대 길이를 찾아 저장해둔다. 예를 들면, “ababa” 는  { 0, 0, 1, 2, 3 } 과 같이 저장한다. 구현 시에는 -1 해서 저장한다. 예에서의 값은 { -1, -1, 0, 1, 2 } 이 된다.

void get_failure_function(char str[], int len, int failure[])
{
	int i;

	failure[0] = -1;

	for (i = 1; i < len; i++) {
		int f = failure[i - 1];

		while (1) {
			if (str[i] == str[f + 1]) {
				failure[i] = f + 1;
				break;
			}
			if (f <= -1) {
				failure[i] = -1;
				break;
			}
			f = failure[f];
		}
	}
}

 

찾을 때는, 위에서 계산한 값을 이용해 string을 찾는 도중에 틀리면 바로 이전까지 맞았던 부분의 failure function value를 찾아 이전까지 같은 부분을 건너뛰고 다음 부분부터 비교한다. 예를 들어, 위의 string을 찾으려고 할 때, text가 “ababcababa” 라면, string의 처음부터 비교하다가 5번째에서 틀리면, 4번째까지는 맞으므로, 4번째의 failure function value를 찾으면 2이다. 이는 앞의 2글자는 맞다는 말이므로, text에서 4-2 만큼을 건너뛴 후, string의 3번째부터 다시 비교한다. string의 3번째도 틀리므로, 2번째의 failure function value를 찾으면 0인데, 이는 0만큼 맞다는 말(=하나도 안맞다는 말)이므로 text를 2-0 만큼 건너뛴 후, string의 1번째와 다시 비교한다. string의 첫번째와 다르면  1만큼만 이동하고, 다시 1번째와 비교한다. 맞았을 때도 맞은 부분만큼을 이용해서 건너뛴다(5번째에서 맞았으므로, 3글자가 맞고, 5-3만큼 이동한 후, 4번째부터 비교).

아래 구현은 match되는 개수를 return하도록 하였다.

int get_matched_count(char text[], int text_len, char string[], int str_len, int failure[])
{
	int count = 0;
	int t = 0;
	int s = 0;

	while (t < text_len - str_len + 1) {
		while (s < str_len) {
			if (text[t + s] != string[s])
				break;
			s++;
		}

		if (s == str_len) {
			// MATCH!
			count++;
		}

		if (s < 1) {
			t++;
			s = 0;
		} else {
			t += s - 1 - failure[s - 1];
			s = failure[s - 1] + 1;
		}
	}

	return count;
}

 

앞 부분과 공통 부분이 없는 string인 경우, 더 빨리 건너뛴다. 예를 들면, “abababababc” 에서 “ababc”({ 0, 0, 1, 2, 0 })를 찾는 경우, 5번째에서 틀리면, 앞에서부터 0글자가 같으므로, 4-0만큼 이동한 후, 1번째부터 다시 비교한다.

위 두 구현 함수의 사용 부분은 다음과 같이 될 것이다.

		int T, S;
		int count;
		int *failure_function = NULL;

		char *text = "ababcababababababababa";
		char *string = "ababa";

		T = strlen(text);
		S = strlen(string);

		failure_function = (int *)malloc(sizeof(int) * S);
		// skip the check of the memory allocation

		get_failure_function(string, S, failure_function);

		count = get_matched_count(text, T, string, S, failure_function);

		printf("%d matched\n", count);

 

[Algorithm] Union & Find – Disjoint-set structure

Union & find 연산은 서로 다른 그룹을 하나의 그룹으로 합칠 때 사용한다. Minimum spanning tree를 만드는 Kruskal 알고리듬 등에서 사용한다.

어떤 A B C D E F G H의 8개의 항목들이 1~4까지의 그룹에 속해 있을 때, 각 항목의 그룹이 다음과 같이 있다고 하자.

{ A – 1, B – 1, C – 2, D – 2, E – 3, F – 3, G – 3, H – 4 }

여기서 각 항목이 가진 그룹만 보면 { 1, 1, 2, 2, 3, 3, 3, 4 } 와 같다.

언뜻 생각해보면, B 항목이 속한 그룹(1번 그룹)과 G 항목이 속한 그룹(3번 그룹)을 합쳐서 { 1, 1, 2, 2, 1, 1, 1, 4 } 와 같이 만든다고 하면, G가 속한 그룹(3번 그룹)을 찾아서 이 그룹인 모든 항목(E, F, G)을 찾아서 1번 그룹으로 바꿔주어야 하므로 모든 노드를 확인해서 3인 그룹이 있다면 1로 바꾸어야 한다. 이걸 모든 노드를 돌면서 확인하면서 바꾸지 않아도 할 수 있게 만드는 게 disjoint-set structure의 union & find 연산이다.

그룹을 합칠 때는, 둘 중 어느 하나를 parent로 만드는 tree 구조를 이용한다. 즉, 하나의 tree에 있는 모든 노드는 하나의 그룹이 된다. 이걸 다시 쓰면, 어떤 노드의 root 노드와 다른 어떤 노드의 root가 같다면, 같은 그룹이다.

 

n개의 항목이 있다면 다음과 같이 n개 항목에 대한 배열(parent)을 잡아 각각의 항목의 index로 그룹명을 잡는다(rank 배열은 모두 1로 하고, union 연산에서 설명한다).

void makeset(int parent[], int rank[], int n)
{
	int i;
	
	for (i = 0; i < n; i++) {
		parent[i] = i;
		rank[i] = 1;
	}
}

 

Find 연산은 어떤 항목이 속한 그룹 번호, 즉, tree에서의 가장 상위 노드, root 노드의 번호를 반환한다. Optimization을 위해 어떤 항목이 find 연산 도중에 parent를 거쳐서 root까지 도달한다면, 그 항목과 그 parent모두를 해당 노드의 parent만 찾으면 root가 되도록 parent를 root로 바꾸어준다. 예를 들어, { A, B, C, D, E, F, G, H }가 { 0, 0, 2, 2, 0, 4, 3, 3 }과 같이 있을 때의 그림은 다음 그림의 왼쪽 그림과 같다. 이 때, F 항목에 대해 Find 연산을 하면 root 까지 가는 과정 중에 있는 노드들을 모두 parent가 root가 되도록 다음 그림의 오른쪽 그림과 같이 바꾼다. 이로써 parent 노드들 및 자신 중 어떤 노드가 어떤 그룹에 속하는지를 나중에 찾을 때(root까지 갈 때), 바로 찾아갈 수 있도록 한다.

 

int Find(int parent[], int x)
{
	if (x != parent[x])
		parent[x] = find(parent, parent[x]);

	return parent[x];
}

 

Union 연산은 두 항목의 root를 같게 만들어 두 항목이 속한 두 그룹을 하나의 그룹으로 합친다. 다시 쓰면, 자신들의 root를 찾아, 이 root 중 한 노드를 다른 root노드의 parent로 만들어 하나의 root로 만든다. 이 과정에서 각각의 항목이 find 연산을 하게 되는데, 이 때, 위에서와 같이 각각의 root까지 찾아가면서 거치는 모든 parent들도 모두 각각의 root를 바로 위 parent로 갖게 만들어 tree의 깊이를 낮춘다. Tree의 깊이가 깊어지면 자신의 그룹이 어떤 것인지를 아는데(root까지 찾아가는데) 시간이 오래 걸리므로, tree의 깊이를 깊게 하지 않기 위해서 find에서 깊이를 낮추는 것 외에 union 연산을 할 때, rank를 이용한다. 두 root 노드의 rank를 비교해 두 root 노드 중 rank가 높은 노드가 새로운 root가 된다. 또한, rank는 어떤 노드가 자신과 rank가 같은 다른 노드의 parent가 되면 1씩 증가한다. 따라서 rank 값은 자신의 sub tree가 최대로 깊이가 깊어졌을 때 rank + 1이 된다. find 연산을 거치면 깊이가 낮아지지만, rank가 증가한 후, root부터 어떤 leap node까지의 모든 노드가 한번도 find를 수행하지 않았다면, 최악의 경우 rank 깊이의 노드가 존재한다. 하지만, 서로 다른 rank의 두 root 가 합쳐진다 rank가 낮은 root노드의 tree는 항상 rank가 더 높은 tree의 sub tree가 된다. 이는 항상 최대 깊이를 rank 이하로 보장한다. 위의 예에서 F와 H의 그룹끼리 합친다고 하면 다음 그림과 같이 된다. 빨간색으로 표시한 값은 rank 값이다.

int Union(int parent[], int rank[], int x, int y)
{
	int rootX = find(parent, x);
	int rootY = find(parent, y);

	if (rootX == rootY)
		return 0;

	if (rank[rootX] < rank[rootY]) {
		parent[rootX] = rootY;
	} else if (rank[rootX] > rank[rootY]) {
		parent[rootY] = rootX;
	} else {
		parent[rootX] = rootY;
		rank[rootY]++;
	}
	
	return 1;
}

쓰고나니 주저리주저리 너무 길다.

구글의 Python Tutorial

[번역] 구글의 파이썬 튜토리얼 요약.
원문은 Google’s Python Class(https://developers.google.com/edu/python/)

* Python 환경 설정

나올 때 윈도우에서는 Ctrl-Z, 다른 OS에서는 Ctrl-D

* Introduction

if __name__ == ‘__main__’:
    main()

sys.argv[0]는 프로그램 이름, sys.argv[1]은 첫번째 인자, sys.argv[2]는 두번째 인자…

TAB 문자 대신 Space들을 입력.

help(len) : len 함수에 대한 문서를 출력
dir(sys): sys 모듈에 대한 전반적인 문서
help(sys.exit): sys 내의 exit() 함수에 대한 문서

* Strings

파이썬 스트링은 “immutable”하다. 즉, 생성 후에 값을 변경할 수 없다. 따라서 다른 값이 되면 새로운 스트링이 생성된다.

“”” 로 여러줄 string 가능

[ ]로 문자 접근 가능
Slice로 substring 얻기
len(string) 은 string의 길이. sequence 타입에는 다 사용 가능
‘+’ 로 concatenate.

Value를 string 으로 변경하고 싶을 때는 str() 함수를 사용

pi = 3.14
text = ‘The value of pi is ‘ + str(pi)

정수로 나눗셈을 할 때는 // 를 사용. 6 // 5 == 1

print 뒤에 줄바꿈을 하고 싶지 않으면 마지막에 , 를 붙인다.
r 을 앞에 붙이면 ‘raw’ string. \ 를 특별히 취급하지 않는다. r’x\nx’ 는 ‘x\nx’ 가 됨. u 를 앞에 붙이면 unicode.

s.lower(), s.upper(): 소문자, 대문자로
s.strip(): 앞 뒤의 whitespace 제거
s.isalpha() / s.isdigit() / s.isspace(): 알파벳인지, 숫자인지, space인지
s.startswith(‘other’), s.endswith(‘other’): s 스트링이 다른 특정 스트링으로 시작하거나 끝나는지.
s.find(‘other’): s 스트링 안의 다른 스트링을 찾는다. 찾으면 인덱스 리턴, 못찾으면 -1
s.replace(‘old’, ‘new’): 모든 ‘old’를 ‘new’로 변경한 string 리턴
s.split(‘delim’): 주어진 delimiter로 자른 substring 리스트를 리턴,
s.join(list): split()의 반대, s를 delimiter로 리스트를 합친다.

길이가 1인 string 에 ==, <= 등의 작업을 할 수 있다. char 타입이 따로 없다.

H  e  l  l  o
0  1  2  3  4
-5 -4 -3 -2 -1

s[1:4]는 ‘ell’, s[1:]는 ‘ello’, s[:]는 ‘Hello’의 복사본, s[1:100]은 ‘ello’ – 더 큰 index를 사용하면 string의 length나 마찬가지가 된다.
s[-1]은 ‘o’, s[-4]은 ‘e’, s[:-3]은 ‘He’, s[-3:]은 ‘llo’
s[:n] + s[n:] == s 는 항상 참. n이 음수거나, index영역을 벗어났을 때도 성립.

% 연산자를 사용해 printf 같은 format string. %d는 int, %s는 string, %f/%g는 float, 오른쪽에 튜플.

text = “%d little pigs come out or I’ll %s and %s and %s” % (3, ‘huff’, ‘puff’, ‘blow down’)

너무 길면 전체 expression을 괄호로 싸면 된다.

text = (“%d little pigs come out or I’ll %s and %s and %s” %
    (3, ‘huff’, ‘puff’, ‘blow down’))

unistring.encode()와 unicode로 encoding 변환.

s = unistring.encode(‘utf-8’)
t = unicode(s, ‘utf-8’)
t == unistring

if / elif/ else. “zero” 값들(None, 0, “”, ”, [], {}, False)은 false, True/False로 boolean 값, and, or, not 사용. ()로 expression 을 감싸지 않는다.

* Lists

= 을 사용하면 그냥 reference.
‘+’ 를 사용해 list를 합칠 수 있다.

for와 in 으로 iteration

squares = [1, 4, 9, 16]
sum = 0
for num in squares:
    sum += num
print sum ## 30

in 은 독립적으로 어떤 원소가 list(또는 다른 collection)에 있는지 테스트 할 수 있다.

list = [‘larry’, ‘curly’, ‘moe’]
if ‘curly’ in list:
    print ‘yay’

range(n)은 0, 1, 2, …, n-1 까지의 숫자를 리턴. range(a, b) 는 a, a+1, a+2, …, b-1 까지를 리턴.

for i in range(100):
    print i

xrange()는 전체 리스트를 만드는 과정을 없애 성능 상 좋다.

while

i = 0
while i < len(a):
    print a[i]
    i = i + 3

list.append(elem): 리스트를 수정하여 가장 뒤에 elem 추가. 리턴하지 않는다.
list.insert(index, elem): index에 elem을 추가. 리턴하지 않는다.
list.extend(list2): list2의 원소들을 list에 추가. 리턴하지 않는다.
list.index(elem): elem이 있으면 index 리턴, 없으면 ValueError를 발생. ValueError 없이 확인하려면 in 을 사용.
list.remove(elem): 첫번째 elem을 제거, 없으면 ValueError. 리턴하지 않는다.
list.sort(): list를 sort. 리턴하지 않는다. sorted()를 더 자주 사용.
list.reverse(): list를 역순으로 변경. 리턴하지 않는다.
list.pop(index): index의 원소를 제거하고 리턴. index를 생략하면 가장 끝의 원소를 리턴(append()와 반대)

* Sorting

sorted()를 사용. reverse=True를 전달하면 역순. Case sensitive하다.

strs = [‘aa’, ‘BB’, ‘zz’, ‘CC’]
print sorted(strs) ## [‘BB’, ‘CC’, ‘aa’, ‘zz’] 
print sorted(strs, reverse=True) ## [‘zz’, ‘aa’, ‘CC’, ‘BB’]

key로 함수를 전달하면 해당 함수를 key로 정렬. key=len, key=str.lower 하면 대소문자 동일하게 취급하여 정렬.

print sorted(strs, key=str.lower) ## [‘aa’, ‘BB’, ‘CC’, ‘zz’]

key로 custom 함수 전달 가능

def MyFn(s):
    return s[-1]
print sorted(strs, key=MyFn)

cmp=cmpFn 선택 인자를 전달할 수도 있음. 내장 함수는 cmp(a, b)로 -/0/+ 로 순서를 리턴

Tuple은 struct와 비슷한 역할. 변경 불가능, 크기가 변하지 않음. () 를 통해 만듦. 크기가 1인 튜플은 ( 1, ) 처럼 , 을 넣어 만듦.

변수 할당에 사용 가능. 반환값이 여러 값을 가진 경우도 사용 가능
(x, y, z) = (42, 13, “Hike”)
(err_string, err_code) = foo()

[ _expr_ for var in list ] 형태로 원하는 형태의 리스트 생성 가능.

fruits = [‘apple’, ‘cherry’, ‘banana’, ‘lemon’]
afruits = [ s.upper() for s in fruits if ‘a’ in s ] ## [‘APPLE’, ‘BANANA’]

* Dictionaries and Files

dict = {}
dict[‘a’] = ‘alpha’
dict[‘g’] = ‘gamma’
dict[‘o’] = ‘omega’

print dict[‘a’] ## ‘alpha’
dict[‘a’] = 6
‘a’ in dict ## True
## print dict[‘z’] ## Throws KeyError
if ‘z’ in dict: print dict[‘z’] ## KeyError를 피한다.
print dict.get(‘z’) ## None

dict.get(key, not-found) 형태로 키가 없을 경우 not-found 로 설정한 값을 리턴하도록 할 수도 있다.

dict.keys(): key 리스트
dict.values(): value 리스트
dict.items(): (key, value) 튜플의 리스트

for k, v in dict.items(): print k, ‘>’, v

iterkeys(), itervalues(), iteritems()는 전체 리스트를 만들지 않아 성능 상 좋다.

hash = {}
hash[‘word’] = ‘garfield’
hash[‘count’] = 42
s = ‘I want %(count)d copies of %(word)s’ % hash # ‘I want 42 copies of garfield’

del 로 변수, list 원소, dict key/value를 지울 수 있다.

Files open(), close(). open 시 ‘rU’를 사용하면 줄바꿈을 ‘\n’으로 변형하여 준다.

f = open(‘foo.txt’,’rU’)
for line in f: ## 파일을 한 줄씩 방문
    print line, ## line 끝에 이미 줄바꿈이 포함되어 있으므로 print가 줄바꿈하지 않도록 한다.
f.close()

f.readlines()는 전체를 메모리에 올리고 줄들의 list를 리턴, read()는 전체 파일을 하나의 string으로.

파일에 쓰려면, f.write(string). print를 사용하려면, print >> f, string. python 3에서는 print(string, file=f)

codes는 unicode를 읽을 때 사용 가능

import codecs
f = codecs.open(‘foo.txt’, ‘rU’, ‘utf-8’)
for line in f: # line은 unicode string이 됨.

* Regular expression

import re
str = ‘an example word:cat!!’
match = re.search(r’word:\w\w\w’, str)
if match:
    print ‘검색 성공’, match.group() ## 발견 word:cat
else
    print ‘검색 실패’

패턴 스트링은 항상 r로 시작.

a, X, 9: 있는 그대로의 문자를 매치
.: 아무 문자 하나를 매치. \n 은 제외
\w: 하나의 word에 쓰이는 문자 하나를 매치(단어가 아님). a-z, A-Z, 0-9, _ [a-zA-Z0-9_]를 매치.
\W: non-word에 쓰이는 문자 하나를 매치
\b: word와 non-word의 경계
\s: 하나의 whitespace 문자[ \n\r\t\f]를 매치
\S: whitespace 이외의 문자를 매치
\d: 숫자 [0-9]
^: 시작
$: 끝
\를 붙여 위의 것들을 그저 문자로 사용 가능

+: 왼쪽 패턴 1개 이상.
*: 왼쪽 패턴 0개 이상.
?: 왼쪽 패턴 0개 혹은 1개
+와 *는 가장 왼쪽의 것을 찾고, greedy 하다. 

r'[\w.-]+@[\w.-]+’ 로 @ 주위에 ‘.’와 ‘-‘도 매치하도록 할 수 있다. -가 구간을 매치하지 않도록 하려면 가장 마지막에 넣는다.
[] 안의 내용을 ^로 시작하면 집합을 뒤집는다. [^ab]는 ‘a’와 ‘b’를 제외한 모든 문자를 뜻한다.

패턴 안에 ( )를 넣으면 그룹으로 구분할 수 있게 해준다.

str = ‘purple alice-b@google.com monkey dishwasher’
match = re.search(r'([\w.-]+)@([\w.-]+)’, str)
if match
    print match.group() ## ‘alice-b@google.com’
    print match.group(1) ## ‘alice-b’
    print match.group(2) ## ‘google.com’

findall(): 모든 패턴에 해당하는 string 리스트를 리턴

f = open(‘text.txt’, ‘r’)
strings = re.findall(r’some pattern’, f.read())

findall 에 ( )를 넣으면 그룹에 해당하는 튜플 리스트를 만든다.

str = ‘purple alice@google.com, blah monkey bob@abc.com blah dishwasher’
tuples = re.findall(r'(\w\.-]+)@([\w\.-]+)’, str)
print tuples ## [(‘alice’, ‘google.com’), (‘bob’, ‘abc.com’)]
for tuple in tuples:
    print tuple[0] ## username
    print tuple[1] ## host

re.search(pat, str, re.IGNORECASE) 처럼 추가 옵션 가능
IGNORECASE: 대소문자 구별하지 않음
DOTALL: 마침표 . 이 newline을 매치하도록 한다. 보통은 newline을 제외한 모든것들을 매치.
MULTILINE: 여러줄로 이루어진 string에서 ^와 $가 줄의 시작과 끝을 매치하도록 한다. 보통은 ^ $는 전체 스트링의 시작과 끝을 매치한다.

정규 표현식의 확장으로 .*? 나 .+?처럼 ?를 끝에 더해서 greedy 하지 않게 변경할 수 있다.

str = ‘<b>foo</b> and <i>so on</i>’
match = re.search(r'(<.*>)’, str)
if match:
    print match.group() ##'<b>foo</b> and <i>so on</i>’
match = re.search(r'(<.*?>)’, str)
if match:
    print match.group() ## ‘<b>’

re.sub(pat, replacement, str) 은 str에서 pat과 매치되는 모든 스트링들을 찾아 replacement로 치환한다. replacement 스트링은 \1, \2 를 이용하여 group(1), group(2)를 표현할 수 있다.

str = ‘purple alice@google.com, blah monkey bob@abc.com blah dishwasher’
print re.sub(r'([\w\.-]+)@([\w\.-]+)’, r’\1@yo-yo-dyne.com’, str)
# purple alice@yo-yo-dyne.com, blah monkey bob@yo-yo-dyne.com blah dishwasher

* Utilities

os 모듈
filenames = os.listdir(dir): dir에 있는 .과 ..을 제외한 파일이름들의 리스트, 절대경로가 아님.
os.path.join(dir, filename): filename과 dir을 합쳐서 path를 만든다.
os.path.abspath(path): path를 받아서 절대 경로를 리턴
os.path.dirname(path), os.path.basename(path): dir/foo/bar.html을 받아서 dirname ‘dir/foo’와 basename’bar.html’을 리턴
os.path.exists(path): path 가 존재하면 True를 리턴
os.mkdir(dir_path): dir 하나를 만든다.
os.makedirs(dir_path): dir_path를 만들기 위한 모든 디렉토리들을 만든다.
shutil.copy(source-path, dest-path): 파일을 복사한다. 복사될 디렉토리가 존재해야 한다.

commands 모듈: 외부 명령을 실행하고 결과물을 얻어옴.
(status, output) = commands.getstatusoutput(cmd): cmd를 실행하고 exit할 때까지 기다려서,
status int값과 output text를 tuple로 리턴한다. stdout과 stderr이 하나의 출력물로 합쳐져서 나타난다.
output = commands.getoutput(cmd): 위와 동일, status값을 받지 않는다는 것만 다름.
commands.getstatus() 라는 함수는 사용하지 말아라.
sub-process에 대한 더 많은 제어권을 원하면 “popen2” 모듈을 보라.
os.system(cmd): cmd의 결과물을 여러분 코드의 output으로 출력하고, error 코드를 리턴.

try/except. ‘except IOError, e:’ 의 형태로 exception 객체에 대한 포인터를 얻을 수 있다.

try:
  f = open(filename, ‘rU’)
  text = f.read()
  f.close()
except IOError:
  sys.stderr.write(‘problem reading:’ + filename)

urllib 모듈: url을 마치 file처럼 읽을 수 있게 한다. urlparse 모듈은 url을 분해하거나 합치는 기능을 제공
ufile = urllib.urlopen(url): url에 대한 file 같은 객체를 리턴
text = ufile.read(): 파일처럼 읽음. readlines()도 사용 가능
info = ufile.info(): 요청에 대한 meta 정보를 리턴. info.gettype() 으로 ‘text/html’ 같은 mime 타입을 얻어 옴.
baseurl = ufile.geturl(): 요청에 대한 “base” url을 얻어옴. redirect로 인해 open할 때 사용한 url과 다를 수 있다.
urllib.urlretrieve(url, filename): url의 데이터를 다운받아 filename의 file로 저장한다.
urlparse.urljoin(baseurl, url): full url을 만든다.

def wget2(url):
  try:
    ufile.urllib.urlopen(url)
    if ufile.info().gettype() == ‘text/html’:
      print ufile.read()
  except IOError:
    prit ‘problem reading url:’, url