суббота, 27 июля 2013 г.

Работа с файлами в Python

Вывод в файлы:

file = open('data.txt', 'w')

Если полный путь к файлу не указан, то файл будет создан в текущем рабочем каталоге.
Если в этом каталоге уже существует такой файл, то он будет удален и создан новый.
С режимом "w" нужно быть осторожнее, можно потерять содержимое старого файла.


myfile = open("C:\\projects\\data.txt", "w")
myfile.write("# Name Email Phone\n")
myfile.write("Larry larry@example.com 111-1111\n")
myfile.write("Curly curly@example.com 222-2222\n")
myfile.write("Moe moe@example.com 333-3333\n")
myfile.close()


c:\projects>type data.txt
# Name Email Phone
Larry larry@example.com 111-1111
Curly curly@example.com 222-2222
Moe moe@example.com 333-3333

c:\projects>

В отличии от функции print, метод write объекта файла записывает в точности то,
что ему передано, без дополнительного форматирования.
Поэтому мы явно добавляем символ конца строки.

Метод close завершает формирование содержимого файла и освобождает системные ресурсы.
Обычно файлы автоматически закрываются, когда объект файла уничтожается
интерпретатором при сборке мусора (т.е. когда в сценарии исчезнет послдняя ссылка на объект)

В случае использования временных файлов, метод close можно и не применять:

open('tempfile.txt','w').write("Scott Tiger\n")
open('tempfile.txt','r').read()


Гарантированное закрытие файлов:

- Использовать обработчик исключений

myfile = open("C:\\projects\\data.txt", "w")
try:
    myfile.write("# Name Email Phone\n")
    myfile.write("Larry larry@example.com 111-1111\n")
    myfile.write("Curly curly@example.com 222-2222\n")
    myfile.write("Moe moe@example.com 333-3333\n")
finally:
    myfile.close()


c:\projects>type data.txt
# Name Email Phone
Larry larry@example.com 111-1111
Curly curly@example.com 222-2222
Moe moe@example.com 333-3333

- использовать менеджер контекста

Инструкция with - обеспечивает  более краткий способ реализации заключительных
операций для объектов определенных типов, включая закрытие файлов.

with open("C:\\projects\\data.txt", "w")  as myfile:
    # обработка файла
    myfile.write("# Name Email Phone\n")
    myfile.write("Larry larry@example.com 111-1111\n")
    myfile.write("Curly curly@example.com 222-2222\n")
    myfile.write("Moe moe@example.com 333-3333\n")
    # закрывается автоматически после выхода
    # независимо от возникновения исключений

c:\projects>type data.txt
# Name Email Phone
Larry larry@example.com 111-1111
Curly curly@example.com 222-2222
Moe moe@example.com 333-3333

В версии Python 3.1 появились вложенные менеджеры контекста:

with A() as a, B() as b:
    # инструкции

при выходе из этой конструкции автоматически закроются оба файла, независимо от того,
возникло исключение или нет.

Это действует так:
with A() as a:
    with B() as b:
        # инструкции


Например:

with open("C:\\projects\\data.txt") as fin, open("C:\\projects\\data1.txt", 'w') as fout:
    for line in fin:
        fout.write(line.upper())

C:\Users\angor>cd c:\projects

c:\projects>type data.txt
# Name Email Phone
Larry larry@example.com 111-1111
Curly curly@example.com 222-2222
Moe moe@example.com 333-3333

c:\projects>type data1.txt
# NAME EMAIL PHONE
LARRY LARRY@EXAMPLE.COM 111-1111
CURLY CURLY@EXAMPLE.COM 222-2222
MOE MOE@EXAMPLE.COM 333-3333

c:\projects>

Для записи в файлы можно также использовать метод writelines,
который просто записывает все строки из списка без дополнительного форматирования.

myfile = open("C:\\projects\\data.txt", "w")
myfile.writelines(["# Name Email Phone\n",
                  "Larry larry@example.com 111-1111\n",
                  "Curly curly@example.com 222-2222\n",
                  "Moe moe@example.com 333-3333\n"])
myfile.close()

C:\Users\angor>cd c:\projects

c:\projects>type data.txt
# Name Email Phone
Larry larry@example.com 111-1111
Curly curly@example.com 222-2222
Moe moe@example.com 333-3333

Такой метод может эмулироваться циклом for и его удобно использовать в сценариях,
которые сначала сохраняют выходные данные в списке, а потом записывают этот список в файл.


Чтение из файлов:

file = open('data.txt', 'r')

режим r используется по умолчанию, поэтому можем писать так:

file = open('data.txt')


пример:

myfile = open("C:\\projects\\data.txt")
lines = myfile.readlines()
for line in lines:
    print(line, end='')


# Name Email Phone
Larry larry@example.com 111-1111
Curly curly@example.com 222-2222
Moe moe@example.com 333-3333


Метод readlines загружает содержимое файла в память целиком
и передает его сценарию в виде списка строк, который можно обойти в цикле.

Существует много способов чтения вхлдного файла:

file.read()
возвращает строку, содержащую все символы (или байты), хранящиеся в файле.


file.read(N)
возвращает строку, содержащую очередные N символов (или байтов), из файла.


file.readline()
читает содержимое файла до ближайшего символа \n и возвращает строку.


file.readlines()
читает файл целиком и возвращает список строк


Вызов метода seek(0) перед каждой попыткой чтения переустанавливает текущую позицию чтения в начало файла.

Python 3.3.1 (v3.3.1:d9893d13c628, Apr  6 2013, 20:30:21) [MSC v.1600 64 bit (AMD64)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> myfile = open("C:\\projects\\data.txt")
>>> myfile.seek(0)
0
>>> myfile.read()
'# Name Email Phone\nLarry larry@example.com 111-1111\nCurly curly@example.com 222-2222\nMoe moe@example.com 333-3333\n'


>>> myfile.seek(0)
0
>>> myfile.readlines()
['# Name Email Phone\n', 'Larry larry@example.com 111-1111\n', 'Curly curly@example.com 222-2222\n', 'Moe moe@example.com 333-3333\n']


>>> myfile.seek(0)
0
>>> myfile.readline()
'# Name Email Phone\n'

>>> myfile.readline()
'Larry larry@example.com 111-1111\n'

>>> myfile.readline()
'Curly curly@example.com 222-2222\n'

>>> myfile.readline()
'Moe moe@example.com 333-3333\n'

>>> myfile.readline()
''

>>> myfile.readline()
''


>>> myfile.seek(0)
0
>>> myfile.read(1)
'#'
>>>
>>> myfile.read(8)
' Name Em'



>>> myfile.seek(0)
0
>>> myfile.read(1), myfile.read(8)
('#', ' Name Em')
>>>



Методы read() и readlines() загружают в память сразу весь файл.
Их можно использовать при работе с небольшими файлами.

Для работы с потенциально большими файлами необходимо использовать вызовы

readline() и read(N)

Оба метода возвращают пустую строку по достижении конца файла.

seek(0) - означает вернуться в начало файла
это альтернатива повторному открытию файла перед очередной попыткой чтения из него.


Чтение строк с помощью итераторов файлов

В старых версиях Python принято было читать файл в список и одход этого списка в цикле:

myfile = open("C:\\projects\\data.txt")
for line in myfile.readlines():
    print(line, end='')

# Name Email Phone
Larry larry@example.com 111-1111
Curly curly@example.com 222-2222
Moe moe@example.com 333-3333

Больше не делайте так никогда !!!

В новых версиях Python объект файла включает итератор,
который при каждом обращении извлекает только одну строку из файла.

Теперь нет необходимости вызывать метод readlines.

myfile = open("C:\\projects\\data.txt")
for line in myfile:
    print(line, end='')


# Name Email Phone
Larry larry@example.com 111-1111
Curly curly@example.com 222-2222
Moe moe@example.com 333-3333


Более того теперь файл можно открывать непосредственно в инструкции цикла,
как временный, который будет автоматически закрыт после выхода из цикла:

for line in open("C:\\projects\\data.txt"):
    print(line, end='')


# Name Email Phone
Larry larry@example.com 111-1111
Curly curly@example.com 222-2222
Moe moe@example.com 333-3333


Это наиболее предпочтительный способ чтения из файла на сегодняшний день.


Вообще итератор это всего лишь метод

__next__ , вызываемый встроенной функцией next.

Он своим поведением напоминает метод readline, за исключением того ,
что по достижении конца файла методы чтения возвращают пустую строку,
а итератор возбуждает исключение, чтобы прервать итерации.

Python 3.3.1 (v3.3.1:d9893d13c628, Apr  6 2013, 20:30:21) [MSC v.1600 64 bit (AMD64)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> myfile = open("C:\\projects\\data.txt")
>>> myfile.readline()
'# Name Email Phone\n'
>>> myfile.readline()
'Larry larry@example.com 111-1111\n'
>>> myfile.readline()
'Curly curly@example.com 222-2222\n'
>>> myfile.readline()
'Moe moe@example.com 333-3333\n'
>>> myfile.readline()
''
>>>
>>>
>>>
>>> myfile = open("C:\\projects\\data.txt")
>>> myfile.__next__()
'# Name Email Phone\n'
>>> myfile.__next__()
'Larry larry@example.com 111-1111\n'
>>> myfile.__next__()
'Curly curly@example.com 222-2222\n'
>>> myfile.__next__()
'Moe moe@example.com 333-3333\n'
>>> myfile.__next__()
Traceback (most recent call last):
  File "", line 1, in
    myfile.__next__()
StopIteration
>>>


Интересно отметить, что итераторы автоматически используются во всех итерационных контекстах,
включая конструктор списка, генераторы списков, функцию map и оператор in проверки на вхождение:

>>> open("C:\\projects\\data.txt").readlines()
['# Name Email Phone\n', 'Larry larry@example.com 111-1111\n', 'Curly curly@example.com 222-2222\n', 'Moe moe@example.com 333-3333\n']
>>>
 

>>> list(open("C:\\projects\\data.txt"))
['# Name Email Phone\n', 'Larry larry@example.com 111-1111\n', 'Curly curly@example.com 222-2222\n', 'Moe moe@example.com 333-3333\n']
>>>

>>> lines=[line.rstrip() for line in open("C:\\projects\\data.txt")]
>>> lines
['# Name Email Phone', 'Larry larry@example.com 111-1111', 'Curly curly@example.com 222-2222', 'Moe moe@example.com 333-3333']
>>>

>>> lines=[line.upper() for line in open("C:\\projects\\data.txt")]
>>> lines
['# NAME EMAIL PHONE\n', 'LARRY LARRY@EXAMPLE.COM 111-1111\n', 'CURLY CURLY@EXAMPLE.COM 222-2222\n', 'MOE MOE@EXAMPLE.COM 333-3333\n']
>>>

>>> list(map(str.split, open("C:\\projects\\data.txt")))
[['#', 'Name', 'Email', 'Phone'], ['Larry', 'larry@example.com', '111-1111'], ['Curly', 'curly@example.com', '222-2222'], ['Moe', 'moe@example.com', '333-3333']]
>>>

>>> line = '# Name Email Phone\n'
>>> line in open("C:\\projects\\data.txt")
True
>>>


Примеры:

Чтение строк из файла и вывод их с помощью функции print:


f = open("C:\\projects\\data.txt", "r")
while True:
    theline = f.readline()
    if len(theline) == 0:
        break
    print(theline, end="")
f.close()

# Name Email Phone
Larry larry@example.com 111-1111
Curly curly@example.com 222-2222
Moe moe@example.com 333-3333


Чтение строк из файла, сортировка их в памяти и запись в другой файл:

fin = open("C:\\projects\\data.txt", "r")
buf = fin.readlines()
fin.close()
buf.sort()
fout = open("C:\\projects\\data1.txt", "w")
for line in buf:
    fout.write(line)
    print(line)
fout.close()

# Name Email Phone
Curly curly@example.com 222-2222
Larry larry@example.com 111-1111
Moe moe@example.com 333-3333

Чтение строк из файла, создание списка слов из этого файла, и вывод количества слов:

f = open("C:\\projects\\data.txt")
content = f.read()
f.close()
words = content.split()
print(words)
print("There are {0} words in the file.".format(len(words)))


['#', 'Name', 'Email', 'Phone', 'Larry', 'larry@example.com', '111-1111', 'Curly', 'curly@example.com', '222-2222', 'Moe', 'moe@example.com', '333-3333']

There are 13 words in the file.


Пример работы с бинарными файлами:

fin  = open("C:\\projects\\data.txt", "rb")
fout = open("C:\\projects\\data2.txt", "wb")
while True:
    buf = fin.read(1024)
    if len(buf) == 0:
        break
    fout.write(buf)
fin.close()
fout.close()


c:\projects>type data2.txt
# Name Email Phone
Larry larry@example.com 111-1111
Curly curly@example.com 222-2222
Moe moe@example.com 333-3333



Считать все строки из одного файла и записать все строки кроме тех,
которые начинаются на "#" в другой файл:


def filter(oldfile, newfile):
    infile = open(oldfile, "r")
    outfile = open(newfile, "w")
    while True:
        text = infile.readline()
        if len(text) == 0:
            break
        if text[0] == "#":
            continue
        outfile.write(text)
    infile.close()
    outfile.close()

filter("C:\\projects\\data.txt","C:\\projects\\data3.txt")

c:\projects>type data3.txt
Larry larry@example.com 111-1111
Curly curly@example.com 222-2222
Moe moe@example.com 333-3333


Считать все строки из файла в список.
Вывести первые два элемента списка (первые две строки):

wordsfile = open("C:\\projects\\data.txt", "r")
wordlist = wordsfile.readlines()
print(wordlist[:2])


['# Name Email Phone\n', 'Larry larry@example.com 111-1111\n']


Другие режимы открытия файлов

Помимо режимов "r" и "w" большинство платформ поддерживает режим "a"

a - append

 При этом вызов функции open не уничтожает текущее содержимое файла, а методы записи добавляют данные в конец файла.


myfile = open("C:\\projects\\data.txt", "w")
myfile.write("# Name Email Phone\n")
myfile.write("Larry larry@example.com 111-1111\n")
myfile.close()
print(open("C:\\projects\\data.txt").read())
myfile.close()


# Name Email Phone
Larry larry@example.com 111-1111


myfile = open("C:\\projects\\data.txt", "a")
myfile.write("Curly curly@example.com 222-2222\n")
myfile.write("Moe moe@example.com 333-3333\n")
myfile.close()
print(open("C:\\projects\\data.txt").read())
myfile.close()


# Name Email Phone
Larry larry@example.com 111-1111
Curly curly@example.com 222-2222
Moe moe@example.com 333-3333


Чаще всего функцию open используют так:

open(имя_файла, режим_открытия, размер_буфера)

обязательный только первый аргумент

По умолчанию:

режим открытия  r
буферизация     полная


Режимы открытия:


r+
файл доступен как для чтения так и для записи, при этом содержимое существующих файлов сохраняется.

w+
файл доступен как для чтения так и для записи, при этом создается файл заново, уничтожая прежнее его содержимое.


rb
читать файл в двоичном режиме

wb
записывать файл в лвоичном режиме


rb+
файл доступен как для чтения так и для записи в двоичном режиме, при этом содержимое существующих файлов сохраняется.

wb+                                                          
файл доступен как для чтения так и для записи в двоичном режиме, при этом создается файл заново, уничтожая прежнее его содержимое.


Проще говоря, по умолчанию используется режим для чтения r, но вы можете использовать режим
w - для записи
a - для дополнения

Можете добавлять символ + , чтобы обеспечить возможность изменения содержимого файла,
а также указывать b и t  чтобы задать двоичный или текстовый режим.

Размер буфера

Функция open принимает необязятельный третий аргумент с размером буфера

0
отсутствие буферизации, данные передаются немедленно,
это значение допустимо только для двоичных режимов

1
построчная буферизация

Любое другое положительное число означает использование режима полной буферизации
этот режим используется по умолчанию


Двоичные и текстовые файлы

Двоичные файлы:

- изображения JPEG
- аудиоклипы
- упакованные двоичные данные


открыть двоичный файл для записи
myfile = open("C:\\projects\\data.txt", "wb")

открыть двоичный файл для чтения
myfile = open("C:\\projects\\data.txt", "rb")

Далее можно использовать стандартные методы:

read
write

Методы readline и readlines как и построчные итераторы файлов,
по прежнему будут работать с текстовыми файлами, открытыми в двоичном режиме.
Но нет никакого смысла применять их к действительно двоичным данным, которые не имеют построчной организации.
(байты \n никакого смысла там не имеют и их может вообще не быть)

Python 3.3.1 (v3.3.1:d9893d13c628, Apr  6 2013, 20:30:21) [MSC v.1600 64 bit (AMD64)] on win32
Type "copyright", "credits" or "license()" for more information.

>>> open("C:\\projects\\data.txt").read()
'# Name Email Phone\nLarry larry@example.com 111-1111\nCurly curly@example.com 222-2222\nMoe moe@example.com 333-3333\n'
>>>

>>> open("C:\\projects\\data.txt", "rb").read()
b'# Name Email Phone\r\nLarry larry@example.com 111-1111\r\nCurly curly@example.com 222-2222\r\nMoe moe@example.com 333-3333\r\n'
>>>
>>>


>>> myfile = open("C:\\projects\\data.txt", "rb")
>>> for line in myfile:
    print(line)

   
b'# Name Email Phone\r\n'
b'Larry larry@example.com 111-1111\r\n'
b'Curly curly@example.com 222-2222\r\n'
b'Moe moe@example.com 333-3333\r\n'
>>>
>>>


>>> open("C:\\projects\\data.bin", 'wb').write(b'# Name Email Phone\n')
19

>>> open("C:\\projects\\data.bin", 'rb').read()
b'# Name Email Phone\n'
>>>
>>>


>>> open("C:\\projects\\data.bin", 'wb').write('# Name Email Phone\n')
Traceback (most recent call last):
  File "", line 1, in
    open("C:\\projects\\data.bin", 'wb').write('# Name Email Phone\n')
TypeError: 'str' does not support the buffer interface
>>>
аргумент должен быть типа bytes


Кодирование символов Unicode в текстовых файлах

>>> data = 'sp\xe4m'
>>> data
'späm'
>>>
>>> 0xe4, bin(0xe4), chr(0xe4)
(228, '0b11100100', '
ä')
>>>

Закодируем эту строку вручную

>>> data = 'sp\xe4m'
>>> data
'späm'
>>>


>>> 0xe4, bin(0xe4), chr(0xe4)
(228, '0b11100100', '
ä')
>>>


>>> data.encode('latin1')
b'sp\xe4m'
>>>


>>> data.encode('utf8')
b'sp\xc3\xa4m'
>>>


>>> data.encode('ascii')
Traceback (most recent call last):
  File "", line 1, in
    data.encode('ascii')
UnicodeEncodeError: 'ascii' codec can't encode character '\xe4' in position 2: ordinal not in range(128)
>>>


>>> data.encode('utf-16')
b'\xff\xfes\x00p\x00\xe4\x00m\x00'
>>>


>>> data.encode('cp500')
b'\xa2\x97C\x94'
>>>


>>> open("C:\\projects\\data.txt", 'w', encoding='latin1').write(data)
4

>>> open("C:\\projects\\data.txt", 'r', encoding='latin1').read()
'späm'
>>>


>>> open("C:\\projects\\data.txt", 'rb').read()
b'sp\xe4m'
>>>


>>> open("C:\\projects\\data.txt", 'w', encoding='utf8').write(data)
4
>>>


>>> open("C:\\projects\\data.txt", 'r', encoding='utf8').read()
'späm'
>>>


>>> open("C:\\projects\\data.txt", 'rb').read()
b'sp\xc3\xa4m'>>>


>>> open("C:\\projects\\data.txt", 'w', encoding='ascii').write(data)
Traceback (most recent call last):
  File "", line 1, in
    open("C:\\projects\\data.txt", 'w', encoding='ascii').write(data)
UnicodeEncodeError: 'ascii' codec can't encode character '\xe4' in position 2: ordinal not in range(128)
>>>


>>> open(r'C:\Python33\python.exe', 'r').read()
Traceback (most recent call last):
  File "", line 1, in
    open(r'C:\Python33\python.exe', 'r').read()
  File "C:\Python33\lib\encodings\cp1251.py", line 23, in decode
    return codecs.charmap_decode(input,self.errors,decoding_table)[0]
UnicodeDecodeError: 'charmap' codec can't decode byte 0x98 in position 16964: character maps to
>>>


>>> open("C:\\projects\\data.txt", 'w', encoding='cp500').writelines(['spam\n', 'ham\n'])

>>> open("C:\\projects\\data.txt", 'r', encoding='cp500').readlines()
['spam\n', 'ham\n']
>>>


>>> open("C:\\projects\\data.txt", 'r').readlines()
['ў—Ѓ”\n', '%€Ѓ”\n', '%']
>>>


>>> open("C:\\projects\\data.txt", 'rb').readlines()
[b'\xa2\x97\x81\x94\r%\x88\x81\x94\r%']
>>>


>>> open("C:\\projects\\data.txt", 'rb').read()
b'\xa2\x97\x81\x94\r%\x88\x81\x94\r%'
>>>


Имейте ввиду, что текст в кодированном двоичном представлении не может обрабатываться так, как вам хотелось бы.
Текст, закодированный с применением определенной кодировки, не может сравниваться или объединяться с текстом,
закодированным с применением других кодировок.


Преобразование символов конца строки в Windows

Конец строки текста в файле:

\n     в Unix и Linux

\r\n   в Windows


В Python объекты файлов автоматически отображают последовательность DOS \r\n в одиночный символ \n

При выполннии сценариев в Windows:

- для файлов открытых в текстовом режиме, при чтении \r\n преобразуется в \n
  при записи \n  преобразуется в \r\n

- для файлов открытых в двоичном режиме никакие преобразования не производятся.

В UNIX - подобных системах преобразование не производится в любом режиме, т.к. там используется \n

Python скрипт всегда работает с \n
Просто во внешних файлах на платформе Windows он преобразует конец строки в \r\n

Поэтому если на платформе Windows вы ошибочно откроете двоичный файл в текстовом режиме,
то при сохранении вы можете повредить файл, если в файле  случайно втретился символ \n
вы его перезапиите в \r\n
а при чтении \r\n  символ  \r  будет отброшен


>>> open("C:\\projects\\data.txt", 'w').write('shrubbery\n')
10
>>>
\n  преобразован в \r\n


>>> open("C:\\projects\\data.txt", 'rb').read()
b'shrubbery\r\n'
>>>
>>> open("C:\\projects\\data.txt", 'r').read()
'shrubbery\n'
>>>
>>> data = b'a\0b\rc\r\nd'      # 4 байта 4 обычных символа
>>> len(data)
8
>>>

Запись двоичных данных
>>> open("C:\\projects\\data.bin", 'wb').write(data)
8
>>>

Чтение двоичных данных
>>> open("C:\\projects\\data.bin", 'rb').read()
b'a\x00b\rc\r\nd'
>>>
0 - отобразился как шестнадцатеричная последовательность x00


Попробуем прочитать в текстовом режиме:
>>> open("C:\\projects\\data.bin", 'r').read()
'a\x00b\nc\nd'
>>>

символы \r - искажены

При записи двоичных данных в текстовом режимемогут изменяться или вставляться байты,
значения которых совпадают с символами конца строки
(если данные успешно пройдут этап кодирования в кодировку по умолчанию)

>>> open("C:\\projects\\data.bin", 'w').write(data)
Traceback (most recent call last):
  File "", line 1, in
    open("C:\\projects\\data.bin", 'w').write(data)
TypeError: must be str, not bytes
>>>

В текстовом режиме должна передаваться строка типа str
Используйте  bytes.decode() для преобразования типа.

>>> data.decode()
'a\x00b\rc\r\nd'
>>> open("C:\\projects\\data.bin", 'w').write(data.decode())
8
>>>


>>> open("C:\\projects\\data.bin", 'rb').read()
b'a\x00b\rc\r\r\nd'
>>>
запись в текстовом режиме добавила символ \r


>>> open("C:\\projects\\data.bin", 'r').read()
'a\x00b\nc\n\nd'
>>>
опять символы \r искажены



Произвольный доступ к данным в файлах

При открытии файлов текущая позиция обычно устанавливается в смещение 0 от начала файла
и перемещается вперед по мере чтения/записи данных.

Метод seek позволяет переместить текущую позицию для следующей операции чтения/записи в другое место,
для чего ему достаточно передать величину смещения в байтах.

seek(n)

seek(n, mode)

mode = 0 абсолютное смещение на n байтов (по умолчанию)
mode = 1 смещение относительно текущей позиции на n байтов
mode = 2 смещение относительно конца файла на n байтов


seek(0)  - перемотать файл в начало (rewind) т.е. текущую позицию переместить в начало файла.


>>> records = [bytes([char]*8) for char in b'mars']
>>> records
[b'mmmmmmmm', b'aaaaaaaa', b'rrrrrrrr', b'ssssssss']
>>>
 


>>> myfile = open("C:\\projects\\random.bin", "w+b")
>>> for rec in records:
    size = myfile.write(rec) # записать 4 записи

   
>>> myfile.flush()
>>> pos = myfile.seek(0)    # прочитать файл целиком


>>> print(myfile.read())
b'mmmmmmmmaaaaaaaarrrrrrrrssssssss'
>>>


Теперь повторно откроем файл в режиме r+b
он также позволяет читать из файла и писать в него,
но не очищает файл при открытии.

>>> myfile = open("C:\\projects\\random.bin", "r+b")
>>> print(myfile.read())    # прочитать файл целиком
b'mmmmmmmmaaaaaaaarrrrrrrrssssssss'
>>>
 

>>> record = b'X' * 8
>>> myfile.seek(0)
0
>>>
 

>>> myfile.write(record)         # изменим первую запись
8
>>>
 

>>> myfile.seek(len(record)*2)
16
>>>
 

>>> myfile.write(b'Y' * 8)       # изменим 3-ю запись
8
>>>
 

>>> myfile.seek(8)
8
>>>
 

>>> myfile.read(len(record))     # извлечем 2-ю запись
b'aaaaaaaa'
>>>
 

>>> myfile.read(len(record))     # извлечем следующую (3-ю) запись
b'YYYYYYYY'
>>>
 

>>> myfile.seek(0)
0
>>>
 

>>> myfile.read()                # прочитать файл целиком
b'XXXXXXXXaaaaaaaaYYYYYYYYssssssss'
>>>


C:\projects>type random.bin
XXXXXXXXaaaaaaaaYYYYYYYYssssssss






Метод seek можно использовать, даже если файл открыт только для чтения.

Пример:

Чтение произвольных записей фиксированной длины.
Используем текстовый режим r
Данные представляют простой текст ASCII, где каждый символ представлен одним байтом
и текст не содержит символов конца строки.

>>> myfile = open("C:\\projects\\random.bin", "r")
>>>

>>> reclen = 8
>>>

>>> myfile.seek(reclen * 3)
24

>>> myfile.read(reclen)         # извлечь 4-ю запись
'ssssssss'
>>>

>>> myfile.seek(reclen * 1)
8

>>> myfile.read(reclen)         # извлечь 2-ю запись
'aaaaaaaa'
>>>


Двоичный режим с таким файлом работает также:

>>> myfile = open("C:\\projects\\random.bin", "rb")
>>>

>>> myfile.seek(reclen * 2)
16

>>> myfile.read(reclen)
b'YYYYYYYY'
>>>



В общем случае текстовый режим не следует использовать, если вам требуется произвольный доступ к записям
(за исключением файлов с простым некодируемым текстом, подобным ASCII, не содержащим символов конца строки)

Пример:

Соответствие между строкой Python и ее кодированным представлением в файле нарушается сразу же за первым не ASCII символом:


>>> data = 'sp\xe4m'

>>> data, len(data)
('späm', 4)

>>> data.encode('utf-8'), len(data.encode('utf-8'))
(b'sp\xc3\xa4m', 5)

Как видим , до кодирования длина строки составляла 4 байта, а после кодирования ее длина стала 5 байтов.
Это существенно осложняет возможность позиционирования по абсолютному смещению.

>>> myfile = open("C:\\projects\\test", mode = "w+", encoding = 'utf8')
>>>

>>> myfile.write(data)
4
>>>

>>> myfile.flush()
>>>

>>> myfile.seek(0); myfile.read(1)
0
's'                                    # для байтов ASCII все OK
>>>

>>> myfile.seek(2); myfile.read(1)
2
'ä'                                    # двухбайтоый не ASCII
>>>

>>> data[3]
'm'                                    # а в смещении 3 не "m" !!!
>>>

>>> myfile.seek(3); myfile.read(1)
3
Traceback (most recent call last):
  File "", line 1, in
    myfile.seek(3); myfile.read(1)
  File "C:\Python33\lib\codecs.py", line 300, in decode
    (result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xa4 in position 0: invalid start byte
>>>



Пример:

В файл с переводом английских слов, нужно добавить транскрипцию, взяв ее из другого файла:

list.txt

come - приходить                  
get - получить                    
give - давать                     
go - идти                         
keep - держать                    
let - позволять                   
make - сделать                    
put - поместить                   
seem - казаться                   
take - взять                      
be - быть                         

trans.txt

the [ðə:]
think [θiŋk]
or [ɔ:]
got [gɔt]
good [gud]
eye [ai]
before [bi'fɔ:]
long [lɔŋ]
let [let]
take [teik]
saw [sɔ:]
room [ru:m]
place [pleis]
those [ðəuz]
work [wз:k]
put [put]
 

 with open("C:\\test\\list.txt", "r", encoding='utf-8') as f1, open("C:\\test\\trans.txt", "r", encoding='utf-8') as f2, open("C:\\test\\out.txt", "w", encoding='utf-8') as f3:
    for line1 in f1:
        p1 = line1.split()
        f2.seek(0)
        i=0
        for line2 in f2:
            p2 = line2.split()
            if p2 != []:
                if (p2[0].lower() == p1[0].lower()):
                    f3.write("{0:20} {1:20} {2:3} {3:30}\n".format(p1[0],p2[1],p1[1]," ".join(str(x) for x in p1[2:len(p1)])))
                    i+=1
        if i == 0:
            f3.write("{0:20} {1:20} {2:3} {3:30}\n".format(p1[0],"[]",p1[1]," ".join(str(x) for x in p1[2:len(p1)])))







out.txt
 
come                []                   -   приходить                    
get                  []                   -   получить                     
give                 []                   -   давать                       
go                   []                   -   идти                         
keep                 []                   -   держать                      
let                  [let]                -   позволять                    
make                 []                   -   сделать                      
put                  [put]                -   поместить                    
seem                 []                   -   казаться                     
take                 [teik]               -   взять                        
be                   []                   -   быть                         



Или так:

with open("C:\\test\\list.txt", "r", encoding='utf-8-sig') as f1, open("C:\\test\\trans.txt", "r", encoding='utf-8-sig') as f2, open("C:\\test\\out.txt", "w", encoding='utf-8-sig') as f3:
    for line1 in f1:
        p1 = line1.split()
        f2.seek(0)
        i=0
        for line2 in f2:
            p2 = line2.split()
            if p2 != []:
                if (p2[0].lower() == p1[0].lower()):
                    i+=1
                    if i == 1:
                        f3.write("{0:20} {1:20} {2:3} {3:30}\n".format(p1[0],p2[1],p1[1]," ".join(str(x) for x in p1[2:len(p1)])))
        if i == 0:
            f3.write("{0:20} {1:20} {2:3} {3:30}\n".format(p1[0],"[]",p1[1]," ".join(str(x) for x in p1[2:len(p1)])))



воскресенье, 21 июля 2013 г.

DATABASE LINK

Получить метаданные о линке:

SELECT DBMS_METADATA.GET_DDL('DB_LINK',a.db_link,a.owner) || '/' FROM dba_db_links a


Создать линк:

CREATE DATABASE LINK "MY_LINK.SUN.COM"
   CONNECT TO "SCOTT" IDENTIFIED BY VALUES '054B287C5C9D0928639F28CC53F9D6D5043A07D0BE8325534D'
   USING 'TESTDB_REMOTEDB.MSK.SUN.COM'


Удалить линк:

SELECT   'DROP PUBLIC DATABASE LINK ' || object_name ||';'
FROM     SYS.dba_objects
where    object_type = 'DATABASE LINK'
and      owner = 'PUBLIC';


Просмотр информации о линках:

select * from dba_db_links;

select * from dba_objects  where object_type like '%DATABASE LINK%'

select * from sys.v_$dblink;


Проверяем что линк работает:

select * from dual@MY_LINK.SUN.COM;
select * from all_tables@MY_LINK.SUN.COM;
select * from  t1@MY_LINK.SUN.COM;



Работать с линками так:

select * from  t1@MY_LINK.SUN.COM;
rollback;
alter session close database link MY_LINK.SUN.COM;
select * from sys.v_$dblink;
select * from  t1@MY_LINK.SUN.COM;




database links (distributed transaction)



set lines 400
column origin format a45
column GTXID format a50
column LSESSION format a10
column USERNAME format a10
column status format a1
column waiting format a30

select /*+ ordered */ substr(s.ksusemnm,1,35)||'-'|| substr(s.ksusepid,1,35) origin,
substr(g.k2gtitid_ora,1,50) gtxid,
substr(s.indx,1,4)||'.'|| substr(s.ksuseser,1,5) lsession,
s.ksuudlna username,
substr(decode(bitand(ksuseidl,11), 1,'ACTIVE', 0, decode( bitand(ksuseflg,4096) , 0,'INACTIVE','CACHED'), 2,'SNIPED', 3,'SNIPED', 'KILLED'),1,1) status,
e.kslednam waiting
from x$k2gte g, x$ktcxb t, x$ksuse s, x$ksled e
where g.k2gtdxcb=t.ktcxbxba
and g.k2gtdses=t.ktcxbses
and s.addr=g.k2gtdses
and e.indx=s.ksuseopc;



ORIGIN = Machine-PID, машина, на которой работает клиентский инструмент

C eis-web.sun.ru process PID=1234 подключился к нашей базе (SPROM) под пользователем QRUSER


На SPROM

ORIGIN                                                                                                     GTXID             LSESSION         USERNAME    S     WAITING                       
----------------------------------------- ---------------------------------------------            -----------          ----------        -      ------------------------------
eis-web.sun.ru-1234                SPROM.b39bc3cb.23.4.3947425                19.45953           QRUSER          I      SQL*Net message from client   

На ARCH

ORIGIN                                                                                                      GTXID               LSESSION         USERNAME   S        WAITING                       
----------------------------------------- ---------------------------------------------             -------------      ----------       -        ------------------------------
base00dbadm01.domain.corp-88840      SPROM.b39bc3cb.23.4.3947425                1853.31078       ARCHIVE       I         SQL*Net message from client   

Далее порождённый процесс PID=88840 с базы (SPROM)  по dblink подключается к нашей базе (ARCH) под пользователем ARCH.
Причём GTXID (глобальный идентификатор транзакции), у них один (SPROM.b39bc3cb.23.4.3947425) и порождёна эта транзакция была в базе SPROM.

LSESSION  (Local Session),
это сеанс в вашей локальной базе данных (к которой вы подключены и запускаете этот скрипт)

S = статус LSESSION, он имеет 1 букву со следующим значением:

I (inactive)    
A (active)
C (cached)
S (sniped)
K (killed)

           
WAITING (чего ждет LSESSION)



(This SQL was posted to Metalink, now officially in Doc Note:104420.1.)





Метаданные Oracle


Метаданные DATABASE LINK:

SELECT DBMS_METADATA.GET_DDL('DB_LINK',a.db_link,a.owner) || '/' FROM dba_db_links a


Получить метаданные пользователя ANGOR:

set feedback off
set pagesize 25000
set linesize 1000
set long 90000
column script format a1000
set serveroutput on

variable v_username VARCHAR2(30);
exec :v_username := upper('angor');

--This line add a semicolon at the end of each statement
execute dbms_METADATA.SET_TRANSFORM_PARAM(DBMS_METADATA.SESSION_TRANSFORM,'SQLTERMINATOR',true);

-- This will generate the DDL for the user and add his objects,system and role grants
SELECT DBMS_METADATA.GET_DDL('USER',username) as script from DBA_USERS where username= :v_username
UNION ALL
SELECT DBMS_METADATA.GET_GRANTED_DDL('SYSTEM_GRANT',grantee) as script from DBA_SYS_PRIVS where grantee= :v_username and rownum=1
UNION ALL
SELECT DBMS_METADATA.GET_GRANTED_DDL('ROLE_GRANT',grantee) as script from DBA_ROLE_PRIVS where grantee= :v_username and rownum=1
UNION ALL
SELECT DBMS_METADATA.GET_GRANTED_DDL('OBJECT_GRANT',grantee) as script from DBA_TAB_PRIVS where grantee= :v_username and rownum=1;



clear screen
SET LONG 20000
SET LONGCHUNKSIZE 20000
SET PAGESIZE 0
SET LINESIZE 1000
SET FEEDBACK OFF
SET VERIFY OFF
SET TRIMSPOOL ON
column script format a1000
set serveroutput on

variable v_username VARCHAR2(30);
exec :v_username := upper('angor');

BEGIN
   DBMS_METADATA.set_transform_param (DBMS_METADATA.session_transform, 'SQLTERMINATOR', true);
   DBMS_METADATA.set_transform_param (DBMS_METADATA.session_transform, 'PRETTY', true);
END;
/

SELECT dbms_metadata.get_ddl('USER', :v_username) as script FROM dual
UNION ALL
SELECT DBMS_METADATA.GET_GRANTED_DDL('SYSTEM_GRANT', :v_username) as script from dual
UNION ALL
SELECT DBMS_METADATA.GET_GRANTED_DDL('ROLE_GRANT', :v_username) as script from dual
UNION ALL
SELECT DBMS_METADATA.GET_GRANTED_DDL('OBJECT_GRANT', :v_username) as script from dual;

spool off



set long 20000
set longchunksize 20000
set pagesize 25000
set linesize 1000
set feedback off
set verify off
set trimspool on
column ddl format a1000

begin
   dbms_metadata.set_transform_param (dbms_metadata.session_transform, 'SQLTERMINATOR', true);
   dbms_metadata.set_transform_param (dbms_metadata.session_transform, 'PRETTY', true);
end;
/

variable v_username VARCHAR2(30);
exec :v_username := upper('angor');

select dbms_metadata.get_ddl('USER', u.username) AS ddl
from   dba_users u
where  u.username = :v_username
union all
select dbms_metadata.get_granted_ddl('TABLESPACE_QUOTA', tq.username) AS ddl
from   dba_ts_quotas tq
where  tq.username = :v_username
and    rownum = 1
union all
select dbms_metadata.get_granted_ddl('ROLE_GRANT', rp.grantee) AS ddl
from   dba_role_privs rp
where  rp.grantee = :v_username
and    rownum = 1
union all
select dbms_metadata.get_granted_ddl('SYSTEM_GRANT', sp.grantee) AS ddl
from   dba_sys_privs sp
where  sp.grantee = :v_username
and    rownum = 1
union all
select dbms_metadata.get_granted_ddl('OBJECT_GRANT', tp.grantee) AS ddl
from   dba_tab_privs tp
where  tp.grantee = :v_username
and    rownum = 1
union all
select dbms_metadata.get_granted_ddl('DEFAULT_ROLE', rp.grantee) AS ddl
from   dba_role_privs rp
where  rp.grantee = :v_username
and    rp.default_role = 'YES'
and    rownum = 1
union all
select to_clob('/* Start profile creation script in case they are missing') AS ddl
from   dba_users u
where  u.username = :v_username
and    u.profile <> 'DEFAULT'
and    rownum = 1
union all
select dbms_metadata.get_ddl('PROFILE', u.profile) AS ddl
from   dba_users u
where  u.username = :v_username
and    u.profile <> 'DEFAULT'
union all
select to_clob('End profile creation script */') AS ddl
from   dba_users u
where  u.username = :v_username
and    u.profile <> 'DEFAULT'
and    rownum = 1
/






вторник, 16 июля 2013 г.

Настройка ACL

В Oracle11g  для любого пакета, выполняющего внешние сетевые вызовы  (UTL_SMTP, UTL_MAIL) необходимо создать список ACL, включить в него пользователя или роль и предоставить списку привилегию сетевого уровня.

Посмотреть, есть ли какие-либо ACL в базе можно так:

select acl , host , lower_port , upper_port from DBA_NETWORK_ACLS;

  Создаем список ACL, включаем в него пользователя  и предоставляем списку привилегию сетевого уровня:

begin
    dbms_network_acl_admin.create_acl(
        acl                  => 'mail-server.xml'
        ,description    => 'Permission to make connections to mail server'
        ,principal       => 'SCOTT'
        ,is_grant        => TRUE
        ,privilege       => 'connect'
    );
    dbms_network_acl_admin.assign_acl(
        acl                  => 'mail-server.xml'
        ,host               => 'my-SMTP-servername'
        ,lower_port    => 25
        ,upper_port   => NULL /* открывается только порт 25*/
    );
end;


ещё пример:

begin
    -- Создание списка ACL
    dbms_network_acl_admin.create_acl(
        acl                  =>  'oracle-permissions.xml'
        ,description    => 'Network permissions for www.oracle.com'
        ,principal       => 'WEBROLE'
        ,is_grant        => TRUE
        ,privilege       => 'connect'
        ,start_date      => SYSTIMESTAMP
        ,end_date       => NULL
    );
    -- Назначение привилегий
    dbms_network_acl_admin.create_acl(
        acl                  => 'oracle-permissions.xml'
        ,description    => 'Network permissions for www.oracle.com'
        ,principal       => 'WEDROLE'
        ,is_grant        => TRUE
        ,privilege       => 'connect'
        ,start_date      => SYSTIMESTAMP
        ,end_date       => NULL
    );
     -- Определение допустимых адресов
    dbms_network_acl_admin.assign_acl(
        acl                  => 'oracle-permissions.xml'
        ,host               => 'www.oracle.com'
        ,lower_port    =>80
        ,upper_port   => 80
    );
end;



Еще пример:

DECLARE
  l_acl_name         VARCHAR2(30) := 'utl_tcp.xml';
  l_ftp_server_ip    VARCHAR2(20) := '192.168.0.1';
  l_ftp_server_name  VARCHAR2(20) := 'ftp.oracle.com';
  l_username         VARCHAR2(30) := 'SCOTT';
BEGIN
  DBMS_NETWORK_ACL_ADMIN.create_acl (
    acl          => l_acl_name, 
    description  => 'Allow connections using UTL_TCP',
    principal    => l_username,
    is_grant     => TRUE, 
    privilege    => 'connect',
    start_date   => SYSTIMESTAMP,
    end_date     => NULL);

  COMMIT;

  DBMS_NETWORK_ACL_ADMIN.add_privilege ( 
    acl         => l_acl_name, 
    principal   => l_username,
    is_grant    => FALSE, 
    privilege   => 'connect', 
    position    => NULL, 
    start_date  => NULL,
    end_date    => NULL);

  COMMIT;

  DBMS_NETWORK_ACL_ADMIN.assign_acl (
    acl         => l_acl_name,
    host        => l_ftp_server_ip, 
    lower_port  => NULL,
    upper_port  => NULL);

  DBMS_NETWORK_ACL_ADMIN.assign_acl (
    acl         => l_acl_name,
    host        => l_ftp_server_name, 
    lower_port  => NULL,
    upper_port  => NULL);

  COMMIT;
END;
/


Можно использовать пакет:

create or replace package    "pkg_DBA_IR"
AUTHID CURRENT_USER
is
     procedure change_assign_acl;
end  "pkg_DBA_IR";


create or replace package body    "pkg_DBA_IR" is
  --
  -- Константы:
  --
 
  -- Имена баз данных:
  C_DB_NAME_PROD constant varchar2(30) := 'TEST';
  C_DB_NAME_UAT    constant varchar2(30) := 'TESTUAT';
  C_DB_NAME_DEV   constant varchar2(30) := 'TESTDEV';
 

  -- Имена хостов:
  C_HOST_NAME_PROD constant varchar2(30) := 'esbprod.sun.com';
  C_HOST_NAME_UAT    constant varchar2(30) := 'esbuat.sun.com';
  C_HOST_NAME_DEV   constant varchar2(30) := 'esbdev.sun.com';
 
 
  -- Имена параметров:
  C_ACL_NAME       constant varchar2(30) :='networkacl.xml';
 
  -- Смена параметров ACL:
  procedure change_assign_acl
  is
  begin
    if upper(SYS_CONTEXT ('USERENV', 'DB_NAME')) = C_DB_NAME_PROD then
      raise_application_error(-20001, 'Run Only in Test Environment');
   
    elsif upper(SYS_CONTEXT ('USERENV', 'DB_NAME')) = C_DB_NAME_UAT then

      begin
       dbms_network_acl_admin.drop_acl(
       acl          => C_ACL_NAME);               
       dbms_output.put_line('ACL dropped…..');
       exception
        when others then
        dbms_output.put_line('Error dropping ACL: '||C_ACL_NAME);
        dbms_output.put_line(sqlerrm);
      end;

      begin
       dbms_network_acl_admin.create_acl(
       acl          => C_ACL_NAME,
       description  => 'Permissions to access WebServices',
       principal    => 'TS',
       is_grant     => TRUE,
       privilege    => 'connect');
       dbms_output.put_line('ACL created…..');
       exception
        when others then
        dbms_output.put_line('Error creating ACL: '||C_ACL_NAME);
        dbms_output.put_line(sqlerrm);
      end;

      begin
       dbms_network_acl_admin.assign_acl(
       acl          => C_ACL_NAME,               
       host         => C_HOST_NAME_UAT,
       lower_port => NULL,
       upper_port => NULL);
       dbms_output.put_line('ACL assigned…..');
       exception
       when others then
       dbms_output.put_line('Error assigning ACL: '||C_ACL_NAME);
       dbms_output.put_line(sqlerrm);
      end;

      commit;

    elsif upper(SYS_CONTEXT ('USERENV', 'DB_NAME')) = C_DB_NAME_DEV then

      begin
       dbms_network_acl_admin.drop_acl(
       acl          => C_ACL_NAME);               
       dbms_output.put_line('ACL dropped…..');
       exception
        when others then
        dbms_output.put_line('Error dropping ACL: '||C_ACL_NAME);
        dbms_output.put_line(sqlerrm);
      end;

      begin
       dbms_network_acl_admin.create_acl(
       acl          => C_ACL_NAME,
       description  => 'Permissions to access WebServices',
       principal    => 'TS',
       is_grant     => TRUE,
       privilege    => 'connect');
       dbms_output.put_line('ACL created…..');
       exception
        when others then
        dbms_output.put_line('Error creating ACL: '||C_ACL_NAME);
        dbms_output.put_line(sqlerrm);
      end;

      begin
       dbms_network_acl_admin.assign_acl(
       acl          => C_ACL_NAME,               
       host         => C_HOST_NAME_DEV,
       lower_port => NULL,
       upper_port => NULL);
       dbms_output.put_line('ACL assigned…..');
       exception
       when others then
       dbms_output.put_line('Error assigning ACL: '||C_ACL_NAME);
       dbms_output.put_line(sqlerrm);
      end;

      commit;

    else
      commit;

    end if;

  exception
    when others then
      null;
  end change_assign_acl;

end "pkg_DBA_IR";



grant execute on dbms_network_acl_admin TO ts;


begin
  TS."pkg_DBA_IR".change_assign_acl;
end;
/




понедельник, 15 июля 2013 г.

Парольная функция Oracle

1. Создание функции

su - oracle
$export ORACLE_SID=
$export NLS_LANG=AMRICAN_AMERICA.CL8MSWIN1251

SQL>@$ORACLE_HOME/rdbms/admin/testdb_utlpwdmg.sql;

/u01/app/oradb/product/11.2.0.3/dbhome_1/rdbms/admin/utlpwdmg.sql



2. Изменяем профиль DEFAULT

включить функцию:

ALTER PROFILE DEFAULT LIMIT PASSWORD_VERIFY_FUNCTION testdb_verify_function;

отключить функцию:

alter profile default limit password_verify_function NULL;



3. Все другие профили унаследуют эту функцию от профиля DEFAULT
   если у них параметр PASSWORD_VERIFY_FUNCTION установлен в DEFAULT

ALTER PROFILE NAME_PROFILE LIMIT PASSWORD_VERIFY_FUNCTION DEFAULT;


4. Сменить у пользователя профиль по умолчанию можно так:

alter user dba_angor profile unlimited_profile;



Стандартная функция

1. Пароль совпадает с именем пользователя.
2. Длина пароля меньше 4-х символов.
3. Пароль слишком простой (совпвдвет со стандартными словами : welcome, database, oracle и т.д.)
4. Не содержит ни одной цифры. (0123456789)
5. Не содержит ни одной буквы. (abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ)
6. Не содержит ни одного спецсимвола. (!"#$%&()``*+,-/:;<=>?_)
7. Пароль не отличается от предыдущего на 3 символа.

Профиль такой :

ALTER PROFILE DEFAULT    LIMIT

PASSWORD_LIFE_TIME       60          //Количество дней до истечения срока действия пароля.                            
PASSWORD_GRACE_TIME      10          //Количество дней, в течении которых предупреждается о необходимости смены пароля.
PASSWORD_REUSE_TIME      1800        //Количество дней по истечении которого можно повторно использовать пароль.      
PASSWORD_REUSE_MAX       UNLIMITED   //Количество раз повторного использования пароля.                                
FAILED_LOGIN_ATTEMPTS    3           //Количество неудачных попыток входа, прежде чем заблокировать уч. запись.       
PASSWORD_LOCK_TIME       1/1440      //Количество дней на которое блокируется учетная запись.                         
PASSWORD_VERIFY_FUNCTION verify_function;


Функция проверки пароля для TESTDB

1. Пароль совпадает с именем пользователя.
2. Длина пароля меньше 8-х символов.
3. Пароль слишком простой (совпвдвет со стандартными словами : welcome, database, oracle и т.д.)
4. Не содержит ни одной цифры. (0123456789)
5. Не содержит ни одной буквы. (abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ)
6. Пароль не отличается от предыдущего на 1 символ.

Профиль такой :

alter PROFILE "DEFAULT"   LIMIT

SESSIONS_PER_USER         UNLIMITED
CPU_PER_SESSION           UNLIMITED
CPU_PER_CALL              UNLIMITED
CONNECT_TIME              UNLIMITED
IDLE_TIME                 UNLIMITED
LOGICAL_READS_PER_SESSION UNLIMITED
LOGICAL_READS_PER_CALL    UNLIMITED
COMPOSITE_LIMIT           UNLIMITED
PRIVATE_SGA               UNLIMITED

PASSWORD_LIFE_TIME        UNLIMITED  //Количество дней до истечения срока действия пароля.
PASSWORD_GRACE_TIME       UNLIMITED  //Количество дней, в течении которых предупреждается о необходимости смены пароля.
PASSWORD_REUSE_TIME       1/24/60    //Количество дней по истечении которого можно повторно использовать пароль.
PASSWORD_REUSE_MAX        5          //Количество раз повторного использования пароля.
FAILED_LOGIN_ATTEMPTS     5          //Количество неудачных попыток входа, прежде чем заблокировать уч. запись.
PASSWORD_LOCK_TIME        1/24/60*3  //Количество дней на которое блокируется учетная запись.
PASSWORD_VERIFY_FUNCTION  testdb_verify_function;


 Повторно пароль можно использовать по истечении времени  PASSWORD_REUSE_TIME
но при этом параметр PASSWORD_REUSE_MAX не должен иметь значение UNLIMITED
должна быть реальная цифра. Описано в документе [ID 228991.1]



FAILED_LOGIN_ATTEMPTS – определяет количество неудачных попыток входа, прежде чем заблокировать учетную запись. Если указан верный пароль до окончания количества попыток, то счетчик сбрасывается на ноль.
PASSWORD_LOCK_TIME – количество дней, на которое блокируется учетная запись, при превышении количества неудачных попыток регистрации, определяемых параметром FAILED_LOGIN_ATTEMPTS
PASSWORD_LIFE_TIME – количество дней до истечения срока действия пароля
PASSWORD_GRACE_TIME – количество дней до истечения срока действия пароля, когда будет напоминаться о необходимости его смены. В это время старый пароль действует
PASSWORD_REUSE_TIME – количество дней, по истечение которого можно повторно использовать пароль
PASSWORD_REUSE_MAX – количество раз повторного использования пароля
PASSWORD_VERIFY_FUNCTION – имя функции для проверки пароля на соответствие требованиям. Вызывается при смене пароля пользователем
SESSIONS_PER_USER – количество сеансов на одного пользователя (количество входов в системы под одним логином). При превышении установленного количества сеанс блокируется
CPU_PER_SESSION – процессорное время отводимое серверному процессу сеанса, прежде чем сеанс будет завершен
CPU_PER_CALL – Процессорное время отводимое на каждое SQL предложение, прежде чем выполнение будет прервано
LOGICAL_READS_PER_SESSION  - количество блоков прочитываемых за сеанс
LOGICAL_READS_PER_CALL – количество блоков, которые могут быть прочитаны одиночным предложением
PRIVATE_SGA – Для сеансов подключенных по распределенной архитектуре, указывает количество килобайт которые выделяются сеансу
CONNECT_TIME  -время подключения, задается в минутах. Определяет длительность подключения к базе данных. По истечении сеанс принудительно завершается
IDLE_TIME – время простоя, задается в минутах. По истечении этого времени простаивающий сеанс будет принудительно завершен
COMPOSITE_LIMIT – Взвешенная сумма CPU_PER_SESSION, CONNECT_TIME, OGICAL_READS_PER_SESSION и PRIVATE_SGA. Это продвинутое средство настройки, которое требует специальной подготовки
При превышении ограничения на ресурсы сеанс принудительно завершается, транзакция находящаяся в процессе исполнения откатывается.


 

вторник, 9 июля 2013 г.

Регулярные выражения в Python

 

Регулярные выражения - это компактная форма записи представления о коллекции строк.

Регулярные выражения используются для:

- проверки наличия фрагмента текста в строке
- поиска подстрок
- поиска и замены
- разбиения строк по точкам совпадения

Самое простое регулярное выражение - это обычный символ, за которым может следовать квантификатор.

Более сложные регулярные выражения могут состоять из любого числа выражений с квантификаторами
и могут включать проверки и дополнительные флаги.

Простейшие регулярные выражения - это обычные литералы символов:

a, b, c ... 1, 2, 3 ...

Выражение:  11G
состоит из трех простейших регулярных выражений (литералов символов),
каждое из которых неявно определяет одно совпадение.

Выражение 11G будет совпадать с одним символом 1
за которым следует один символ 1
за которым следует один символ G

т.е. будет совпадать со строками:

11G
ORACLE11GR2


В модуле re содержится функция re.search(r, s, f)
которая возвращает объект совпадения,
если совпадение с регулярным выражением r обнаруживается в любом месте строки s
(с учетом флагов f, если они заданы)
В противном случае возвращается None



import re

str = 'ORACLE11GR2'
match = re.search(r'11G', str)
if match:
    print('found', match.group())
else:
    print('did not find')


>>>found 11G

Функция преобразует регулярное выражение во внутренний формат
- этот процесс называется компиляцией, а затем выполняет свою работу.

  Это очень удобно для однократного применения регулярного выражения,
но если одно и то же регулярное выражение требуется применить несколько раз,
можно избежать излишних затратна компиляцию при каждом использовании,
скомпилировав выражение всего один раз с помощью функции:
re.compile(r, f)


import re

c = re.compile(r'11G')
str = 'ORACLE11GR2'
match = re.search(c, str)
if match:
    print('found', match.group())
else:
    print('did not find')


>>>found 11G



А лучше писать так:

import re

c = re.compile(r'11G')
str = 'ORACLE11GR2'
match = c.search(str)
if match:
    print('found', match.group())
else:
    print('did not find')

>>>found 11G



Большинство символов могут использоваться как литералы.

Но некоторые из них имеют специальное значение в языке регулярных выражний:

 \ . ^ $ ? + * { } [ ] ( ) |

Чтобы их использовать как обычные символы (литералы) их нужно экранировать символом \

\\  \.  \$ и т.д.


В пределах регулярных выражений можно также использовать большинство стандартных
экранированных последовательностей языка Python:

\n - перевод строки
\t - символ табуляции

 Экранированные последовательности с шестнадцатеричными кодами символов:

\xHH
\uHHHH
\UHHHHHHHH


Символьный класс - это выражение состоящее из одного или нескольких символов заключенных в квадратные скобки.

В отсутствии явного квантификатора соответствует точно одному символу,
который может быть любым символом из данного символьного класса.

Регулярное выражение r[ea]d
совпадает с red или radar
но не со словом read

Совпадение с единственной цифрой можно отыскать с помощью регулярного выражения:

[0123456789]

или в сокращенном виде:

[0-9]

Имеется возможность инвертировать значение символьного класса:

[^0-9]

соответствует любому не цифровому символу.


В символьном классе все специальные символы кроме \ , теряют свое специальное значение.
Но два символа  ^ и - в символьном классе приобретают новое значение:

^ - если первый символ в классе, то инверсия.
- - если не первый символ в классе, то диапазон.

Внутри символьных классов можно использовать следующие сокращенные формы записи:

\d   - цифра   [0-9]
\D   - не цифра [^0-9]
\s   - пробельный символ [ \t\n\r\f\v]
\S   - не пробельный символ [^ \t\n\r\f\v]
\w   - символ слова [a-zA-Z0-9_]
\W   - символ не слова [^a-zA-Z0-9_]
.    - любой символ за исключением \n  (если флаг re.DOTALL то \n включается)


Например регулярное выражение:

[\dA-Fa-f]
соответствует шестнадцатеричной цифре

Символ точки за пределами символьного класса обозначает набор символов,
а внутри символьного класса сам символ точки.


Флаги модуля регулярных выражений:

reobj = re.compile("regex pattern", re.VERBOSE | re.IGNORECASE | re.DOTALL | re.MULTILINE)
reobj = re.compile(r'(?aimsx)regex pattern')


Oracle11GR2
[Oo][Rr][Aa][Cc][Ll][Ee]11[Gg][Rr]2

Игнорировать регистр в регулярном выражении можно так:
c = re.compile(r'11[Gg]')

Но лучше использовать флаг:
c = re.compile(r'11g', re.IGNORECASE)
c = re.compile(r'(?i)11g')


mport re
c = re.compile(r'11g', re.IGNORECASE)
str = 'ORACLE11GR2'
match = c.search(str)
if match:
    print('found', match.group())
else:
    print('did not find')

>>>found 11G



import re
c = re.compile(r'(?i)11g')
str = 'ORACLE11GR2'
match = c.search(str)
if match:
    print('found', match.group())
else:
    print('did not find')

>>>found 11G



Флаги

re.A
re.ASCII

При наличии этого флага, проверки:
\b, \B, \s, \S, \w и \W
действуют так, как если бы они применялись к тексту,
содержащему только символы ASCII.
По умолчанию действие этих проверок основано на спецификации Юникода.


re.I
re.IGNORECASE

Поиск совпадений выполняется без учета регистра символов.


re.M
re.MULTILINE

При наличии этого флага, символ ^ соответствует началу текста и позиции сразу же после каждого перевода строки,
а символ $ соответствует позиции перед каждым символом перевода строки и концу текста.


re.S
re.DOTALL

При наличии этого флага символ . соответствует любому символу, включая символ перевода строки.


re.X
re.VERBOSE

Позволяет включать в регулярные выражения пробелы и комментарии.



Квантификаторы

Квантификаторы записываются в виде {m,n}
m - минимальное число совпадений с выражением
n - максимальное число совпадений с выражением

Например

e{1,1}e{1,1}     две e подряд ee

или тоже самое

e{2,2}

Слову feel  они соответствуют, а слову felt нет.

Сокращения.

Если в квантификаторе указать одно число

e{2}

то оно означает и минимум и максимум

т.е  e{2}  то же что и  e{2,2}

Если квантификатор не указан, то предполагается что он равен единице {1,1}  или {1}

Иногда удобно использовать различные минимальное и  максимальное значение:

чтобы найти совпадение со словами

travelled
traveled

можно использовать выражение

travel{1,2}ed
или
travell{0,1}ed

Квантификатор {0,1}  имеет сокращение ?

travell?ed

Есть еще два сокращения для квантификаторов:

+ - не менее одного  {1,n}
* - любое число      {0,n}

где n - означает максимально допустимое для квантификатора число, которое обычно не менее 32767

Квантификатор + очень удобен.
Например, для поиска соответствий целым числам  можно было бы использовать выражение \d+
т.к. оно совпадает с одной или боее цифрами.

В строке  4588.91 регулярное выражение \d+ найдет два совпадения: 4588 и 91

С помощью belevel+ed  можно отыскать слова с одним или более символами l

Например ошибки связанные с залипанием клавиш:

beleveled
belevelled
belevellled


Квантификатор * используется реже - потому что он может приводить к получению неожиданных результатов.

Например хотим найти все строки с комментариями  #

#*     - так не годится

Это выражение найдет и пустые строки (т.е. с нулевым совпадением)
Квантификаторы * и ? могут находить соответствие при нулевом числе совпадений.

Если вы используете квантификатор * или ? , то обязательно убедитесь, что хотя бы одно подвыражение
в регулярном выражениииспользует ненулевой квантификатор (т.е. отличный от * и ?)

Часто возможно заменить квантификатор * на ? и наоборот.

tasselled

Найти слово с одним или более символами l можно так:

tassell*ed
или
tassel+ed

а с двумя или более символами l можно так:

tasselll*ed
или
tassell+ed


Регулярное выражение \d+ будет соответствовать строке 136
Почему оно соответствует всем цифрам, а не только первой?

По умолчанию все квантификаторы являются жадными (или максимальными)
они стремятся соответствовать как можно большему числу символов.

Любой квантификатор можно сделать нежадным (или минимальным), добавив после него символ ?.

Знак вопроса имеет два разных значения:

когда он употребляется самостоятельно, он интерпретируется как сокращенная форма записи квантификатора {0,1}

а когда он следует за квантификатором, он говорит о том, что стоящий перед ним квантификатор является минимальным.

Например выражение  \d+?

обнаружит соответствие в строке 136 в трех местах:
1, 3 и 6


Ранее была представлена функция

re.search(r,s,f) 

которая находила и возвращала объект совпадения с регулярным выражением r в любом месте строки s


Существует более ограниченная функция

re.match(r,s,f)

которая находит и возвращает объект совпадения с регулярным выражением r только если он находится в начале строки s


Но существует и более продвинутая функция

re.findall(r,s,f)

которая находит и возвращает все непересекающиеся совпадения с регулярным выражением r в строке s.
Если регулярное выражение содержит сохраняющие группы, для каждого совпадения возвращается кортеж
с сохраненными фрагментами.


import re

c = re.compile(r'11G')
str = 'ORACLE11GR2'
match = re.match(c, str)
if match:
    print('found', match.group())
else:
    print('did not find')


>>did not find




import re

c = re.compile(r'11G')
str = '11GR2'
match = re.match(c, str)
if match:
    print('found', match.group())
else:
    print('did not find')

>>found 11G




import re

c = re.compile(r'11G')
str = 'ORACLE11GR2'
match = c.search(str)
if match:
    print('found', match.group())
else:
    print('did not find')

>>>found 11G



С помощью функции  re.findall
можно увидеть разницу между жадным н не жадным квантификатором:


Жадный (максимальный):

import re

c = re.comьpile(r'\d+')
str = '0123456789'
tuples = re.findall(c, str)
print(tuples)

>>['0123456789']


Не жадный (минимальный):

import re

c = re.compile(r'\d+?')
str = '0123456789'
tuples = re.findall(c, str)
print(tuples)

>>['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']


Еще примеры с функцией  re.findall:

Найдем все слова в строке, котрые начинаются на букву а (русский алфавит):

import re

c = re.compile(r'\bа[а-я]*')
str = 'электронный адрес angor@oracle.com, еще электронный адрес andrey@sun.com'
tuples = re.findall(c, str)
print(tuples)

>>['адрес', 'адрес']


Тоже но теперь буква a (латинский алфавит):

import re

c = re.compile(r'\ba[a-z]*')
str = 'электронный адрес angor@oracle.com, еще электронный адрес andrey@sun.com'
tuples = re.findall(c, str)
print(tuples)

>>['angor', 'andrey']


Найдем все электронные адреса в строке:

import re

c = re.compile(r'[\w\.-]+@[\w\.-]+')
str = 'электронный адрес angor@oracle.com, еще электронный адрес andrey@sun.com'
tuples = re.findall(c, str)
print(tuples)


>>['angor@oracle.com', 'andrey@sun.com']


Если регулярное выражение содержит сохраняющие группы, то их можно вывести в виде кортежа:

import re

c = re.compile(r'([\w\.-]+)@([\w\.-]+)')
str = 'электронный адрес angor@oracle.com, еще электронный адрес andrey@sun.com'
tuples = re.findall(c, str)
print(tuples)

for tuple in tuples:
    print(tuple[0])
    print(tuple[1])


>>[('angor', 'oracle.com'), ('andrey', 'sun.com')]

>>angor
>>oracle.com
>>andrey
>>sun.com


Квантификаторы регулярных выражений:

e?      Соответствует e{0,1}
        ноль или большее число вхождений выражения e
        по умолчанию жадный


e??     Соответствует e{0,1}?
        ноль или более число вхождений выражения e
        минимальный


e+      Соответствует e{1,}
        одно или более число вхождений выражения e
        по умолчанию жадный


e+?     Соответствует e{1,}?
        одно или более число вхождений выражения e
        минимальный


e*      Соответствует e{0,}
        ноль или более число вхождений выражения e
        по умолчанию жадный


e*?     Соответствует e{0,}?
        ноль или большее число вхождений выражения е
        минимальный


e{m}    Cоответствует точно m вхождениям выражения e


e{m,}   Соответствует по меньшей мере m вхождениям выражения e
        по умолчанию жадный


e{m,}?  Соответствует по меньшей мере m вхождениям выражения e
        минимальный


e{,n}   Соответствует не более чем n вхождениям выражения e
        по умолчанию жадный


e{,n}?  Соответствует не более чем n вхождениям выражения e
        минимальный
       
                       
e{m,n}  Соответствует не менее чем m и не более чем n вхождениям выражения e
        по умолчанию жадный
  

e{m,n}? Соответствует не менее чем m и не более чем n вхождениям выражения e
        минимальный



Примеры сокращенных форм:


\d+  соответствует одной или более цифрам
     стремится соответствовать как можно большему числу символов


                                                
\d+?  соответствует одной или более цифрам
      из последовательностей двух или более цифр будут найдены два или более вхождений по   одной цифре.


\d??  соответствует нулевому или большему числу цифр, но предпочтение будет отдано      нулевому числу совпадений.
      т.к. квантификатор минимальный
      он порождает ту же проблему что и *, и может находить соответствие с ничем,
      т.е. соответствовать любому тексту.



Найдем в некотором файле все теги изображений

Пример такого тега:

<IМG height=202 alt="Ленин в Смольном" src="images/lenin-v-smolnom.jpg" width=160 align="middle" title="Ленин в Смольном.">



import re

c = re.compile(r'(?i)<img.*>')
str = '<....  <IМG height=202 alt="Ленин в Смольном" src="images/lenin-v-smolnom.jpg" width=160 align="middle" title="Ленин в Смольном.">   ...>'
tuples = re.findall(c, str)
print(tuples)

>>['<IМG height=202 alt="Ленин в Смольном" src="images/lenin-v-smolnom.jpg" width=160 align="middle" title="Ленин в Смольном.">   ...>']


.*  означает ноль или более любых символов.
Жадный квантификатор захватил все символы > и остановился на последнем символе > в файле.
это не то что мы ожидали.


import re

c = re.compile(r'(?i)<img.*?>')
str = '<....  <IMG height=202 alt="Ленин в Смольном" src="images/lenin-v-smolnom.jpg" width=160 align="middle" title="Ленин в Смольном.">   ...>'
tuples = re.findall(c, str)
print(tuples)

>>['<IMG height=202 alt="Ленин в Смольном" src="images/lenin-v-smolnom.jpg" width=160 align="middle" title="Ленин в Смольном.">']

или так:


import re

c = re.compile(r'(?i)<img[^>]*>')
str = '<....  <IMG height=202 alt="Ленин в Смольном" src="images/lenin-v-smolnom.jpg" width=160 align="middle" title="Ленин в Смольном.">   ...>'
tuples = re.findall(c, str)
print(tuples)

>>['<IMG height=202 alt="Ленин в Смольном" src="images/lenin-v-smolnom.jpg" width=160 align="middle" title="Ленин в Смольном.">']

или скомбинируем два предыдущих решения вместе:

import re

c = re.compile(r'(?i)<img[^>]*?>')
str = '<....  <IMG height=202 alt="Ленин в Смольном" src="images/lenin-v-smolnom.jpg" width=160 align="middle" title="Ленин в Смольном.">   ...>'
tuples = re.findall(c, str)
print(tuples)

>>['<IMG height=202 alt="Ленин в Смольном" src="images/lenin-v-smolnom.jpg" width=160 align="middle" title="Ленин в Смольном.">']


Во всех трех последних примерах на возвращается ожидаемая строка, казалось бы решение найдено.
Но если подумать то наши регулярные выражения найдут и такие строки:
<img>

хотя такой тег является недопустимым и вряд ли появится в реальном html-коде,
но мы все же должны в своем регулярном выражении ориентироваться на допустимые строки

Как известно, тег изображения должен иметь обязательный атрибут src.

Напишем более точное регулярное выражение:

<img\s+[^>]*?src="\w+[^>]*?>

- подстрока  <img

- один или более пробельных символов

- минимально ноль или более любых символов за исключением символа Ю
  (чтобы пропустить любые атрибуты, такие как alt)

- строка src="

- затем хотя бы один символ слова

- затем любой (включая нулевое число) символ за исключением >
  (чтобы пропустить любые другие атрибуты)

- закрывающая угловая скобка


import re

c = re.compile(r'(?i)<img\s+[^>]*?src="\w+[^>]*?>')
str = '<....  <IMG height=202 alt="Ленин в Смольном" src="images/lenin-v-smolnom.jpg" width=160 align="middle" title="Ленин в Смольном.">   ...>'
tuples = re.findall(c, str)
print(tuples)

>>['<IMG height=202 alt="Ленин в Смольном" src="images/lenin-v-smolnom.jpg" width=160 align="middle" title="Ленин в Смольном.">']


Регуряные выражения можно объединять через ИЛИ
тогда будет отыскиваться совпадение с любым альтернативным вариантом:

import re

c = re.compile(r'подпрограмма|алгоритм|подстрока')
str = 'подпрограмма алгоритм подстрока'
tuples = re.findall(c, str)
print(tuples)

>>['подпрограмма', 'алгоритм', 'подстрока']



Если мы применим операцию группировки (она же операция сохранения совпадения)
то то что не попало в сохранение не будет выведено функцией re.findall:

import re

c = re.compile(r'(подпрограмма|алгоритм)|подстрока')
str = 'подпрограмма алгоритм подстрока'
tuples = re.findall(c, str)
print(tuples)

>>['подпрограмма', 'алгоритм', '']


Круглые скобки преследуют две цели:

- сгруппирповать выражения
- сохранить текст, совпавший с выражением

Например:

подпрограмма|подстрока|алгоритм

можно записать и так:

под(программа|строка)|алгоритм

Но во втором выражении будет побочный эффект сохранением выражений

import re

c = re.compile(r'под(программа|строка)|алгоритм')
str = 'подпрограмма алгоритм подстрока'
tuples = re.findall(c, str)
print(tuples)

>>['программа', '', 'строка']

Функция возвратила кортеж для сохранаемых групп.


Если мы  хотим избавится от такого побочного эффекта группировки
(хотим чтобы работала только группировка, но совпадения не сохранались)
то это можно сделать так:

под(?:программа|строка)|алгоритм

поставив вслед за открывающейся круглой скобкой символы  ?:


import re

c = re.compile(r'под(?:программа|строка)|алгоритм')
str = 'подпрограмма алгоритм подстрока'
tuples = re.findall(c, str)
print(tuples)

>>['подпрограмма', 'алгоритм', 'подстрока']


А что будет если  написать так:

(под(программа|строка)|алгоритм)

тут будут сохранены два фрагмента, если будет обнаружено совпадение с первым выражением:

подпрограмма или подстрока - первое сохранение
программа или строка       - второе сохранение

и одно сохранение, если будет обнаружено совпадение со вторым выражением:

алгоритм    - одно сохранение


import re

c = re.compile(r'(под(программа|строка)|алгоритм)')
str = 'подпрограмма алгоритм подстрока'
tuples = re.findall(c, str)
print(tuples)

>>[('подпрограмма', 'программа'), ('алгоритм', ''), ('подстрока', 'строка')]


Пусть некоторый файл состоит из строк вида:

ключ = значение

Ключ содержит только алфавитно-цифровые символы

[a-zA-Z0-9_]   подойдет т.е. ключ соответствует регулярному выражению \w


Значение может содержать любые символы (кроме перевода строки)

[^ \t\n\r\f\v]  любой непробельный символ \S не подойдет
т.к. наличие пробелов внутри значений допускается
поэтому выбираем  .+


Для каждой совпавшей строки выполним два сохранения:

(\w) = (.+)


Например имеется пара:

 предмет  =    квантовая механика

Как видим начальные и конечные пробелы присутствуют и у ключа и у значения
кроме того, значение и внутри себя содержит пробел.

Исключим из первого сохранения и начальные и конечные пробелы
а из второго пока только начальные

для поиска пробельных символов символ \s ([ \t\n\r\f\v]) не годится
т.к. ему также соответствует символ перевода строки
для наших целей вполне подойдет следующий символьный класс [ \t]
(пробел и табуляция)

[ \t]*(\w+)[ \t]*=[ \t]*(.+)


import re

c = re.compile(r'[ \t]*(\w+)[ \t]*=[ \t]*(.+)')
str = ' предмет  =   квантовая механика     '
tuples = re.findall(c, str)
print(tuples)

>>[('предмет', 'квантовая механика     ')]


Обратиться к захваченному тексту можно с помощью обратных ссылок
(ссылок на предшествующие группы)


\i

где i - порядковый номер сохранающей группы
номер i начинается с 1

Сохранение 0 выполняется автоматически, без применения круглых скобок
Оно хранит все соответствие, т.е. всю строку.

(\w+)\s+\1

Этому выражению соответствует слово, за которым следует по меньшей мере один пробельный символ
и затем тоже самое слово, что было захвачено в первой сохраненяющей группе.

Иногда для ссылок на сохраняющие группы лучше использовать имена, а не порядковые номера.

Именуются сохранения так:

За открывающейся круглой скобкой указываем ?P<name>

например

(?P<word>\w+)

а затем обращаемся к ней так:
                           
(?P=word)

наш пример

(\w+)\s+\1

можно переписать так:

(?P<word>\w+)\s+(?P=word)


Проверки регулярных выражений.


^  -  соответствует началу текста.
      с флагои re.MULTILINE соответствует позиции сразу после каждого символа перевода строки.


$  -  соответствует концу текста.
      с флагои re.MULTILINE соответствует позиции сразу перед каждыь символом перевода строки.


\A  -  соответствует началу текста.
\Z  -  соответствует концу текста.


\b  -  соответствует границе слова (re.ASCII)
       внутри символьных классов обозначает символ забоя (backspace)

\B  -  соответствует границе не слова (re.ASCII)


(?=e)    -  совпадение обнаруживается, усли текст справа от позиции проверки соответствует выражению e,
            при этом изменения позиции поиска в тексте не происходит.
            Эта проверка называется опережающей проверкой, или позитивной опережающей проверкой.
            Например выражение:  \w+(?=\s+)
            найдет слово, за которым стоит один или несколько пробельных символов.

(?!e)    -  совпадение обнаруживается, если текст справа от позиции проверки не соответствует выражению e,
            при этом изменения позиции поиска в тексте не происходит.
            Эта проверка называется негативной опережающей проверкой.

(?<=e)   -  совпадение обнаруживается, если текст слева от позиции проверки соответствует выражению e,
            эта проверка называется позитивной ретроспективной проверкой.

(?<!e)   -  совпадение обнаруживается, если текст слева от позиции проверки не соответствует выражению e,
            эта проверка называется негативной ретроспективной проверкой.



Рассмотрим чем отличаются проверки ^,$ и \A, \Z


Так совпадение обнаруживается:

import re

pattern = '''\Aabc de fgh$'''
c = re.compile(pattern)
line ='abc de fgh\n'
match = c.search(line)
if match:
    print(line, end="")

abc de fgh

А так не находит:

import re

pattern = '''\Aabc de fgh\Z'''
c = re.compile(pattern)
line ='abc de fgh\n'
match = c.search(line)
if match:
    print(line, end="")



Так тоже совпадение обнаруживается:

import re

pattern = '''abc de fgh'''
c = re.compile(pattern)
line ='abc de fgh\n'
match = c.search(line)
if match:
    print(line, end="")

abc de fgh


Так совпадение обнаруживается:

import re

pattern = '''abc\nde fgh'''
c = re.compile(pattern)
line ='abc\nde fgh'
match = c.search(line)
if match:
    print(line, end="")

abc
de fgh


Так совпадение обнаруживается:

import re

pattern = '''abc\nde fgh$'''
c = re.compile(pattern)
line ='abc\nde fgh'
match = c.search(line)
if match:
    print(line, end="")

abc
de fgh

И так совпадение обнаруживается:

import re

pattern = '''\Aabc\nde fgh$'''
c = re.compile(pattern)
line ='abc\nde fgh'
match = c.search(line)
if match:
    print(line, end="")

abc
de fgh

И так совпадение обнаруживается:

import re

pattern = '''\Aabc\nde fgh\Z'''
c = re.compile(pattern)
line ='abc\nde fgh'
match = c.search(line)
if match:
    print(line, end="")

abc
de fgh


Так совпадение обнаруживается:

import re

pattern = '''\Aabc\nde fgh$'''
c = re.compile(pattern)
line ='abc\nde fgh\n'
match = c.search(line)
if match:
    print(line, end="")

abc
de fgh

А так нет:

import re

pattern = '''\Aabc\nde fgh\Z'''
c = re.compile(pattern)
line ='abc\nde fgh\n'
match = c.search(line)
if match:
    print(line, end="")



В итоге получается что паттерн "строка\Z"
совпадает со строкой "строка"
и не совпадает со строкой "строка\n"
Паттерн "строка$"
совпадает и строкой "строка"
и со строкой "строка\n"


Паттерн "строка"  также
совпадает и строкой "строка"
и со строкой "строка\n"


Паттерн "строка"
совпадает и строкой "строка"
и со строкой "\nстрока\n"
но ни один из паттернов :

"^строка"
"\Aстрока"

не совпадет со строкой:

"\nстрока"


Проверки \b и \B


Регулярное выражение jet будет находить в тексте:

the jet and jetski are noisy

несколько соответствий.

Проверка \b - граница слова поможет избежать лишних нахождений.

Выражение \bjet\b  будет находить только одно соответствие.


import re

c = re.compile(r'(jet)')
str = 'the jet and jetski are noisy'
tuples = re.findall(c, str)
print(tuples)

C:\projects>python test1.py
['jet', 'jet']


import re

c = re.compile(r'(\bjet\b)')
str = 'the jet and jetski are noisy'
tuples = re.findall(c, str)
print(tuples)

C:\projects>python test1.py
['jet']



Выражение  \bg

найдет соответствие в тексте:

good 

и тексте:

a good  

но не найдет соответствия в тексте:

together

Выражение \Bg

не найдет соответствия в тексте:

good

и тексте:

a good  

но найдет соответствие в тексте:

together


import re

c = re.compile(r'(\bg)')
str = 'together'
tuples = re.findall(c, str)
print(tuples)

C:\projects>python test1.py
[]


import re

c = re.compile(r'(\Bg)')
str = 'together'
tuples = re.findall(c, str)
print(tuples)

C:\projects>python test1.py
['g']



Выражение:

aircraft|airplane|jet

можно записать так:

\baircraft\b|\bairplane\b|\bjetb\

или более просто:

\b(?:aircraft|airplane|jet)\b

т.е.
граница слова, несохраняющее выражение, граница слова

так как, выражения:

aircraft|airplane|jet
\baircraft\b|\bairplane\b|\bjetb\

являются несохраняющими
то и мы должны использовать несохраняющее выражение:

\b(?:aircraft|airplane|jet)\b


Опережающие проверки.

Пусть имеется текст:

Helen Patricia Sharman
Jim Sharman
Sharman Joshi
Helen Kelly

нам необходимо отыскать текст Helen Patricia, но только если он относится к имени Sharman.

Простейший способ такой:

\b(Helen\s+Patricia)\s+Sharman\b

Этому выражению будет соответствовать текст:
Helen Patricia, только если он предшествует границе слова,
за которым следуют пробельные символы и слово Sharman и далее следует граница слова.

Однако того же самого можно добиться с помощью опережающей проверки:

\b(Helen\s+Patricia)(?=\s+Sharman\b)


Чтобы сохранить определенное имя в самых разных его разновидностях:

Helen
Helen P.
Helen Patricia

можно задать такое регулярное выражение:

\b(Helen(?:\s+(?:P\.|Patricia))?)\s+(?=Sharman\b)

Обратите внимание, что сохранение выполняется только двумя синтаксическими конструкциями:

(e)
(?P<name>e)

других форм сохраняющих группировок не существует.


Ретроспективные проверки.

Выражения, которые могут использоваться в ретроспективных проверках,
должны иметь фиксированную длину.
Т.е. в них не могут использоваться квантификаторы ?, + и *.
А интервальные квантификаторы должны задавать фиксированный размер: например {3}.


Регулярное выражение [ \t]*
найдет ноль или более символов пробелов и табуляций, т.к. квантификатор жадный,
то пока он не найдет их все (идущие друг за другом) он не остановится.

Есть строка любых символов  (.+)

Чтобы удалить из сохранения все пробелы, предшествующие данной строке (начальные пробелы) можно поступить так:

[ \t]*(.+)

Вспомнив, что  . (точка) - это любой символ за исключением перевода строки, перепишем это выражение так:

[ \t]*([^\n]+)

И еще вспомнив, что при наличии флага re.MULTILINE символ ^ соответствует началу текста и
позиции сразу же после каждого символа перевода строки, перепишем это выражение так:

^[ \t]*([^\n]+)


Как же удалить конечные пробелы ?

Например пусть имеется строка:

aaabb  cde    fff    z          \t         \n

Вся строка удовлетворяет условию регулярного выражения:

([^\n]+)

Позиция проверки доходит до символа \n
т.к. все символы, кроме \n,  удовлетворяют условию данного регулярного выражения.

далее используем негативную обратную проверку:  (?<![ \t])

([^\n]+)(?<![ \t])

негативная обратная проверка для совпадения требует, чтобы символы,
предшествующие позиции проверки, не были пробелами или символами табуляции.

Вследствии этого последний символ "значения" (в нашем случае это - z) сохраняется в сохраняющей группе,
а завершающие пробелы и символы табуляции - нет.


import re

c = re.compile(r'([^\n]+)(?<![ \t])')
str = 'aaabb  cde    fff    z          \t         \n'
tuples = re.findall(c, str)
print(tuples)

C:\projects>python test.py
['aaabb  cde    fff    z']



Вернемся к нашему регулярному выражению,
которое отыскивает пары ключ-значение:

^(\w+)=([^\n]+)

Чтобы гарантировать, что каждая пара ключ=значение будет извлекаться из единственной строки
и не будет происходить объединения нескольких строк необходимо установить флаг re.MULTILINE

Если потребуется удалять начальные и конечные пробельные символы и использовать именованные
сохраняющие группы, то полное регулярное выражение могло бы выглядеть так:

^[ \t]*(?P<key>\w+)[ \t]*=[ \t]*(?P<value>[^\n]+)(?<![ \t])

В регулярные выражения можно встраивать комментарии:

(?# текст комментария)
но на практике такие комментарии могут еще больше осложнить понимание.

Лучшее решение заключается в использовании флага re.VERBOSE

Он позволяет использовать внутри регулярных выражений пробельные символы
и обычные комментарии языка  Python с одним ограничением:

- когда необходимо описать совпадение с пробельным символом, следует использовать
либо символ \s либо символьный класс, такой как [].

Пример нашего регулярного выражения:

^[ \t]*                 # начало строки и необязательные начальные пробелы
(?P<key>\w+)            # текст ключа
[ \t]*=[ \t]*           # знак равенства с возможными пробелами, окружающими его
(?P<value>[^\n]+)       # текст значения
(?<![ \t])              # негативная ретроспективная проверка для исключения завершающих пробелов.


В контексте программирования на языке Python регулярные выражения, подобные этому,
обычно записываются в виде "сырых" строк, заключенных в тройные кавычки.

Сырые строки используются чтобы избежать необходимости дублировать символы обратного слэша,
а тройные кавычки, чтобы записывать в нескольких строках.

c = re.compile(r"""
               ^[ \t]*                 # начало строки и необязательные начальные пробелы
               (?P<key>\w+)            # текст ключа
               [ \t]*=[ \t]*           # знак равенства с возможными пробелами, окружающими его
               (?P<value>[^\n]+)       # текст значения
               (?<![ \t])              # негативная ретроспективная проверка для исключения завершающих пробелов.
               """, re.MULTILINE|re.VERBOSE)




Ранее мы видели как можно получить доступ к сохраненному тексту внутри регулярного выражения:

- по номеру группы
- по имени группы

(.....)(.....)(.....)\1
   1      2      3


(?P<name>)(.....)(.....)(?P=name)
      1       2      3


Однако имеется возможность принимать решение о наличии соответствия
в зависимости от наличия какого либо предыдущего совпадения:


(.....)(.....)(.....)(?(2) expr1)
   1      2      3

если было совпадение во втором сохранении то expr1



(.....)(.....)(.....)(?(2) expr1|expr2)
   1      2      3

если было совпадение во втором сохранении то expr1, иначе expr2



Рассмотрим несколько вариантов записи атрибута src

значения атрибута могут заключаться в апострофы, кавычки или без кавычек.

src="girl.png"
src='girl.png'
src=girl.png


Как найти такой атрибут?

src=(["'])([^"'>]+)\1
        1      2

Сохранение 1, сохраняет найденное совпадение содержащее либо кавычки либо апострофы.

Сохранение 2, сохраняет найденное совпадение максимальной длины содержащее по крайней мере один символ,
который не является кавычкой, апострофом или ">".

Благодаря обратной ссылке \1, соответствие будет обнаружено, только если кавычки,
обрамляющие имя файла - одного типа.

Какой тип кавычек вернет первое сохранение, таким должно и закрываться.

Но это выражение не позволяет отыскивать имена файлов без кавычек.

Чтобы устранить эту проблему, необходимо сделать символ открывающей кавычки необязательным,
а совпадение с закрывающей кавычкой выполнять, только если было найдено совпадение с открывающей кавычкой.

src=(["'])?([^"'>]+)(?(1)\1)

Окончательный вид регулярного выражения

<img\s+                                       # начало текста
[^>]*?                                        # любые атрибуты предшествующие src
src=                                                # начало атрибута src
(?P<quote>["'])?            # необязательные открывающие кавычки
(?P<image>[^"'>]+)     # имя файла изображения
(?(quote)(?P=quote))                       # закрывающая кавычка, если была открывающая
[^>]*?                                         # любые атрибуты, следующие за src
>                                                 # конец тега


Конечно существует более простая, но менее очевидная альтернатива:

src=(["']?)([^"'>]+)\1

Если имеется символ открывающей кавычки, он сохраняется в группе 1

Далее идут символы, следующие за открывающей кавычкой.

Если открывающая кавычка отсутствовала, то по прежнему будет считаться,
что для группы 1 было найдено совпадение - совпадение с пустой строкой,
т.к. кавычка считается необязательной.
(ее квантификатор означает ноль или более совпадений)

В этом случае обратной ссылке также будет соответствовать пустая строка.