Почему диспетчер контекста не закрывает файловый дескриптор?

Я пытаюсь создать диспетчер контекста, который использует mmap, который сам является диспетчером контекста. Изначально у меня была тупая проблема с открытым файлом Почему нет mmap закрывает связанный файл (получение PermissionError: [WinError 32])? и в ответ быстро объясняется, почему он не работает должным образом.

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

Первый подход заключался в использовании декоратора @contextmanager @contextmanager:

from contextlib import contextmanager
import os
import mmap

#contextmanager
def memory_map(filename, access=mmap.ACCESS_WRITE):
    size = os.path.getsize(filename)
    fd = os.open(filename, os.O_RDWR)
    print('about to yield')
    with mmap.mmap(fd, size, access=access) as m:
        yield m
    print('in finally clause')
    os.close(fd)  # Close the associated file descriptor.

test_filename = 'data'

# First create the test file.
size = 1000000
with open(test_filename, 'wb') as f:
     f.seek(size - 1)
     f.write(b'\x00')

# Read and modify mmapped file in-place.
with memory_map(test_filename) as m:  # Causes AttributeError: __enter__
    print(len(m))
    print(m[0:10])
    # Reassign a slice.
    m[0:11] = b'Hello World'

# Verify that changes were made
print('reading back')
with open(test_filename, 'rb') as f:
     print(f.read(11))

# Delete test file.
# Causes:
# PermissionError: [WinError 32] The process cannot access the file because it
# is being used by another process: 'data'
os.remove(test_filename)

Но это приводит к:

Traceback (most recent call last):
  File "memory_map.py", line 27, in <module>
    with memory_map(test_filename) as m:  # Causes AttributeError: __enter__
AttributeError: __enter__

В следующей попытке я попытался явно создать класс диспетчера контекста:

import os
import mmap

class MemoryMap:
    def __init__(self, filename, access=mmap.ACCESS_WRITE):
        print('in MemoryMap.__init__')
        size = os.path.getsize(filename)
        self.fd = os.open(filename, os.O_RDWR)
        self.mmap = mmap.mmap(self.fd, size, access=access)

    def __enter__(self):
        print('in MemoryMap.__enter__')
        return self.mmap

    def __exit__(self, exc_type, exc_value, traceback):
        print('in MemoryMap.__exit__')
        os.close(self.fd)  # Close the associated file descriptor.
        print('  file descriptor closed')


test_filename = 'data'

# First create the test file.
size = 1000000
with open(test_filename, 'wb') as f:
     f.seek(size - 1)
     f.write(b'\x00')

# Read and modify mmapped file in-place.
with MemoryMap(test_filename) as m:
    print(len(m))
    print(m[0:10])
    # Reassign a slice.
    m[0:11] = b'Hello World'

# Verify that changes were made
print('reading back')
with open(test_filename, 'rb') as f:
     print(f.read(11))

# Delete test file.
# Causes PermissionError: [WinError 32] The process cannot access the file
# because it is being used by another process: 'data'
os.remove(test_filename)

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

in MemoryMap.__init__
in MemoryMap.__enter__
1000000
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
in MemoryMap.__exit__
  file descriptor closed
reading back
b'Hello World'
Traceback (most recent call last):
  File "memory_map2.py", line 47, in <module>
    os.remove(test_filename)
PermissionError: [WinError 32] The process cannot access the file because it is being used by another process: 'data'

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

Решения

Ошибка в обоих фрагментах. Это была простая типографская ошибка. Декоратор contextmanger был закомментирован. Должно было:

@contextmanager  # Leading "#" changed to "@".
def memory_map(filename, access=mmap.ACCESS_WRITE):
    size = os.path.getsize(filename)
    fd = os.open(filename, os.O_RDWR)
    ...

Во втором случае это произошло потому, что mmap сам не был закрыт в __exit__() методе, а только связанный дескриптор файла. Мне это никогда не приходило в голову, потому что возникшее исключение было таким же, как и в первом случае.

    def __exit__(self, exc_type, exc_value, traceback):
        print('in MemoryMap.__exit__')
        self.mmap.close()  # ADDED.
        os.close(self.fd)  # Close the associated file descriptor.
        print('  file descriptor closed')

person martineau    schedule 02.11.2017    source источник
comment
Вам это поможет? Я прочитал только ошибку. stackoverflow.com/questions/27215462/   -  person Elis Byberi    schedule 03.11.2017
comment
#contextmanager не @contextmanager.   -  person user2357112 supports Monica    schedule 03.11.2017
comment
Кроме того, ваше предложение in finally на самом деле не входит в предложение finally.   -  person user2357112 supports Monica    schedule 03.11.2017
comment
@ElisByberi: Спасибо. Я действительно посмотрел на вопрос, и, хотя он похож, не понял, как он применяется, но, судя по приведенному ниже ответу, возможно, нет ...   -  person martineau    schedule 03.11.2017
comment
@ user2357112: Да, проблема с первым фрагментом. Должно быть, я испортил копирование и вставку из моего предыдущего вопроса.   -  person martineau    schedule 03.11.2017
comment
@ user2357112: Также print('in finally clause') - это всего лишь (безобидный) остаток других вещей, которые я пробовал, прежде чем задавать этот вопрос.   -  person martineau    schedule 03.11.2017


Ответы (1)


В случае второй попытки вам нужно закрыть файл с отображением памяти:

def __exit__(self, exc_type, exc_value, traceback):
    self.mm.close()
    print('in MemoryMap.__exit__')
    os.close(self.fd)  # Close the associated file descriptor.
    print('  file descriptor closed')
person Maurice Meyer    schedule 02.11.2017
comment
Я приму это, поскольку вы разместили это как официальный ответ ... спасибо. Он вместе с комментарием к моему вопросу предоставил исправления для обеих версий. Думаю, я все время путаю закрытие объекта mmap и закрытие файла, связанного с ним. - person martineau; 03.11.2017
comment
В Windows к файлу не могут получить доступ более 1 процесса. Вам нужно закрыть объект mmap перед файловым объектом. Ответ правильный. - person Elis Byberi; 03.11.2017