顯示具有 python 標籤的文章。 顯示所有文章
顯示具有 python 標籤的文章。 顯示所有文章

星期四, 11月 19, 2020

Python re 筆記 1-1: Regular Expression HOWTO/Simple Patterns

Regular Expression HOWTO  是 Python 官網推薦的 "正規表示法" 學習文件,在此翻譯和筆記心得,順其章節但不逐字翻。

Introduction 介紹正規表示式

正規表示式 Regular expression (REs, reges, reges patterns) 被用以在 string 中找 pattern。

(更務實的理解是用匹配詞 (pattern) 在文 (string) 中找出特定字。做為 python 使用者,在 import re 後,對 re 提供的各函式提供 pattern,也就是符合正規表示式文法的 regular expression。對 re 來說,pattern 字串編譯成 pattern object 後才有搜尋作用)


Simple Patterns 簡易匹配詞 

Matching Characters 

最直觀的匹配詞 pattern 就是想找的特定字。比如想找 test,匹配詞就是 test。
但有許多具有功能性的 metacharacters (匹配元),它們出現在 pattern 中具有其他意義。以下綠底為匹配元:
. ^ $ * + ? { } [ ] \ | ( )

方框 []:提供任一可匹配的[characters] ,和任何不可匹配的 [^characters] 
  • 方框內的任一字元都可找出
    • 比如 [abc] ,表示可找出 a 或 b 或 c 。
    • 連續性的字元集,比如 [abc] ,可用 '-' 表示連續性,表示為 [a-c] 。小寫英文字母常以 [a-z]  表示。
    • 匹配元在方框內也可以當成普通字元用。比如  [akm$] ,錢字號$是匹配元,作用為置後,但在方框內只是個普通可比對的字元。所以[akm$]可找出 'a''k', 'm',和 '$'
    • (私下覺得方框還原了匹配詞就是比對字的原始定義)
  • 在方框內首字元用 '^' 表示,則任何在方框內的字元都不可匹配,也就是 "所有不在方框內的字都符合" !!
    • 比如 [^5] ,5 以外的字元都可找出。
    • 注意當 '^' 不在方框首位,就當作普通字。比如[5^] 可找出 '5' 和 '^'
反斜線 是最重要的匹配元,有跳脫功能性帶入功能性的作用。

跳脫功能性之反斜線 \
  • Pattern內存在的匹配元有功能,無法視為比對內容。比如 [ 或 \ ,不可視為 "找左方框" 和 "找反斜線"。
  • 不過,將匹配元前配上反斜線\後,匹配元跳脫功能性回歸成一般字元,比方 \[ 和 \\ ,就視為 "找左方框"  和 "找反斜線"。
帶入功能性之反斜線 \:普通字元要怎麼有功能性呢?用久了,我們會期待數字可用某個字元表示,所有字母也用某字元表示,那這些普通字變成功能字,也是由反斜線帶入
  • \d:等義 [0-9],所有數字。
  • \D:等義 [^0-9],所有非數字。
  • \s:等義 [\t\n\r\f\v],所有空白字元。\t是水平跳格,\v是垂直跳格;\r是回車,\n是新行;\f換頁。
  • \S:等義 [^\t\n\r\f\v],所有非空白字元。
  • \w:等義 [a-zA-Z0-9_],所有字母數字元。
  • \W:等義 [^a-zA-Z0-9_],所有非字母數字元。
最懶點字元 .:可匹配任何非新行\n的字元。re 還提供 re.DOTALL 可匹配一切含新行\n的任何字元。

剩下的匹配元 * + ? { } | ( ) 將在後文說明

重複量詞

描述某字元某詞的重複次數。

米字號 *:0或多次

加字號 +:一次以上

問字號 ?:無或有(只出現一次)
  • 比如 home-?brew 可找出 'homebrew' 或 'home-brew'
大括號 {m,n}:指定重複次數
  • 出現 m~n 次:至少 m 次,最多 n 次。
    • 省略 m 次表示至少 0 次。
    • 省略 n 表示至多任意數量,所以有 {0,}{1,}{0,1} 等義 *,+ ,? 的用法
重複字元 * 有貪婪性,RE 會盡可能找出最多,之後的比對不合後,才減少重複量,返回前字元再比對。以 pattern  a[bcd]*b 找 string  'abcbd' 為例。


Matched

Explanation

1

a

狀態: string  'abcbd'; pattern  a[bcd]*b,工作pattern是 a. 

工作RE找到 a. 

2

abcbd

狀態: string剩  'bcbd'; pattern剩 [bcd]*b, 工作patter是[bcd]*

工作: RE 以 [bcd]* 貪婪比對,一路找到底。狀態:string 剩  '' ,pattern 剩 b

3

Failure

狀態: string剩 ''; pattern 剩 b , 工作patter是 b.

工作RE 找尾字 b,但 string 已空無字元可找,比對失敗

4

abcb

工作返回,讓工作pattern 回到[bcd]*,並將之前[bcd]* 找出字減一字元,也就是 [bcd]* 只找出 bcb,整體只找出 'abcb'

5

Failure

狀態: string剩'd'; pattern剩 b,工作patter是b

工作RE再找尾字 b,但 string 剩 'd' 找不出 b ,失敗。

6

abc

工作返回將之前[bcd]* 找出字減一字元,也就是 [bcd]* 只找出 bc,整體只找出 'abc'

6

abcb

狀態: string剩'bd'; pattern剩b,工作patter是 b. 

工作:再試b。這回找出'b'整體找到符合pattern 的 'abcb'


到 RE末端(pattern 結束)匹配出 'abcb' 。這說明了匹配引擎,會先在貪婪性匹配(比如 [bcd]*)上盡可能地把整個 string 找完,若剩下的不匹配,會返回貪婪性匹配成果的尾字元,再試剩下的匹配。如果不成功會一直試到[bcd]*找到的成果到歸零。而歸零就算失敗找不到。
 
(這段詳述了貪婪性匹配的作法和修正。Simple Patterns 後段就請看原文或機翻)



來源與參考: 
1. https://docs.python.org/3/howto/regex.html#regex-howto,Python 官網推薦文。

星期六, 5月 09, 2015

python:將 list 內部分群

前面如何精簡地找出 list 中特定字的 index.
現實遇上的問題是 :  "一串 list, 自動分群, 同內容為一群"

前述討論串提供了好方法, 將 list 改成 dictionary
http://stackoverflow.com/questions/176918/finding-the-index-of-an-item-given-a-list-containing-it-in-python

a = ['foo','bar','baz','bar','any', 'foo', 'much']
l = dict(zip(set(a), map(lambda y: [i for i,z in enumerate(a) if z is y ], set(a))))
l['foo']
#[0, 5]
l ['much']
#[6]
l
{'baz': [2], 'foo': [0, 5], 'bar': [1, 3], 'any': [4], 'much': [6]}


上述等義先由 set 建立內容單一的集合, 再分群.
(老實說用 pyton, perl 會用太多內建物而失去效率. 建立 set 的過程顯然就分群了)

sub = set(a)
#set(['baz', 'foo', 'bar', 'any', 'much'])
def myset(val, a):
  return [i for i,v in enumerate(a) if v == val]
clusters = [myset(s, a) for s in sub]
# [[2], [0, 5], [1, 3], [4], [6]]

也可以再簡化為
b = [ [i for i,v in enumerate(a) if v is s] for s in sub ]
# [[2], [0, 5], [1, 3], [4], [6]]
跟 map 的結果是一樣的
map(lambda s: [i for i,v in enumerate(a) if v == s], set(a))
# [[2], [0, 5], [1, 3], [4], [6]]

希望有個 dictionary d,
d['baz'] = [2]
d['foo'] = [0,5]
d['bar'] = [1,3]
..

Dictionary 的生成方式, 要不是一個個  key:val, 要不用 zip (keys, val_list)
dict( zip(set(a), clusters) )

dict( zip(set(a), [[i for i,v in enumerate(a) if v == s] for s in set(a)] ) )
之後取用很方便
d['bar']
#[1, 3]










python: is 和 == 的區別

Python 學習筆記

is 是判定 object 相同, 
== 是判定 value 相同


所謂 object 相同,若 a 是某 class,b = a,經過這種只有 copy 外殼的 shadow copy,而非 b 另外配置記憶體,逐 byte copy a 成員的 deep copy,可說 a is b。

簡言之, a = b 只有 shadow copy,內部一樣,所以 a is b 為 True。

# code
class A:
 def f2(x): return x
a = range(100)

a1 = A   
a2 = a1 
print 'id(a1)={}, id(a2)={}'.format(id(a1), id(a2))
print id(a1.a), id(a2.a)
print a1 is a2

# output
id(a1)=34986168, id(a2)=34986168
46065184 46065184
True


而比對字串, value, 使用 is 非常危險. 
除非是同 set 內的 string, 怎知兩個是相等呢? 

a = '123.jpg'
a.split('.')[1] is 'jpg'  # FALSE
a.split('.')[1] == 'jpg' # TRUE

星期六, 2月 28, 2015

python:傳回 list 中符合條件的 index

stackoverflow 有個好問題, 怎麼在 list 用最精簡的方式找出符合條件的 index ?

Finding the index of an item given a list containing it in Python

alist = ["foo", "bar", "baz", "bar"]

**********************************
 list.index() -- list class 之 member function, 但只傳找到第ㄧ各, 
alist.index('bar') 
#1

找不到的話奉上 error, 不見得好啊 
alist.index('haha')
#error ....
要用 try except 處理, 挺麻煩不是嗎


def sublist(e, l):
try:
i = l.index(e)
return i
except ValueError:
return -1

sublist('bar', alist)
#1
sublist('err', alist)
#-1 

自寫
def match(a, alist): return [i for i,v in enumerate(alist) if v == 'bar']
match('bar', alist)
#[1, 3]

更簡化成
a = [ i for i,v in enumerate(alist) if v == 'bar' ]

上述挺簡潔的, 但太依賴 enumerate(), 用更基本的 range 寫呢?
[ i for i in range(len(alist)) if alist[i] == 'bar']
#[1, 3]

用 if .. in 呢? 
其實本題並不要找 list, 這種就夠了    
if 'bar' in alist: alist.index('bar')
#1