简体   繁体   中英

Why catching an exception close the open file, with and without context?

I was working on an issue from a small script I have written. It's a hook between two application. The issue is one application have been updated and now it use yaml instead of json as config file.

Minimal exemple

import os 
import yaml
import json

config = {
    'version': "2.0.2",
    'journals': {
        "default": "/test/yaml/bhla"
    },
    'editor': os.getenv('VISUAL') or os.getenv('EDITOR') or "",
    'encrypt': False,
    'template': False,
    'default_hour': 9,
    'default_minute': 0,
    'timeformat': "%Y-%m-%d %H:%M",
    'tagsymbols': '@',
    'highlight': True,
    'linewrap': 79,
    'indent_character': '|',
}
with open("jrnl.yaml", 'w') as f:
    yaml.safe_dump(config, f, encoding='utf-8', allow_unicode=True, default_flow_style=False)

This will create a yaml file where you will have run the code.

The problem

I first wrote this simple patch to allow my hook to work with both (json and yaml).

JRNL_CONFIG_PATH = "jrnl.yaml"

with open(JRNL_CONFIG_PATH, "r") as f:
    try:
        JRNL_CONFIG = json.load(f)
    except json.JSONDecodeError:
        JRNL_CONFIG = yaml.load(f, Loader=yaml.FullLoader)
TAGS_SYMBOL = JRNL_CONFIG.get("tagsymbols", "@")

But as a big suprise, when error is catched, f is closed because JRNL_CONFIG will return None and produce this error:

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-141-bc1ef847563b> in <module>()
----> 1 JRNL_CONFIG.get("tagsymbols", "@")

AttributeError: 'NoneType' object has no attribute 'get'

Questions

  1. Why catching an exception close the open file, with and without context?
  2. What is the best method to catch the json error and still be able to parse the file as yaml?

Tested

  • Can't pass the file by name, as the config file may not have extension ( .json , .yaml )
  • This work but it's far from elegant...
try:
    f = open(JRNL_CONFIG_PATH, "r")
    JRNL_CONFIG = json.load(f)
except json.JSONDecodeError:
    f = open(JRNL_CONFIG_PATH, "r")
    JRNL_CONFIG = yaml.load(f, Loader=yaml.FullLoader)
finally:
    f.close()

Edit 1

Question 1 : Why catching an exception close the open file, with and without context?

Have been awser by @jedwards

Question 2 : What is the best method to catch the json error and still be able to parse the file as yaml?

Have been awsered by @chepner

The issue isn't that the file is being closed (it's not), it's that the file pointer is no longer at the expected place (the beginning of the file) when you try to use your fallback:

with open("some.yaml") as f:
    try:
        print("before", f.tell())
        data = json.load(f)
    except json.JSONDecodeError:
        print("after", f.tell())
        print("is closed:", f.closed)

Here, the .tell() method returns the location of the file pointer.

One solution would be to reset the file pointer inside the except block:

with open("some.yaml") as f:
    try:
        JRNL_CONFIG = json.load(f)
    except json.JSONDecodeError:
        f.seek(0)
        JRNL_CONFIG = yaml.load(f, Loader=yaml.FullLoader)

What about:

with open(JRNL_CONFIG_PATH, "r") as f:
    data = f.read()
    try:
        JRNL_CONFIG = json.loads(data)
    except json.JSONDecodeError:
        JRNL_CONFIG = yaml.load(data, Loader=yaml.FullLoader)
TAGS_SYMBOL = JRNL_CONFIG.get("tagsymbols", "@")

There's no need to try both json.load and yaml.load , because YAML is a superset of JSON and yaml.load will parse anything json.load can.

JRNL_CONFIG_PATH = "jrnl.json"

with open(JRNL_CONFIG_PATH, "r") as f:
    JRNL_CONFIG = yaml.load(f, Loader=yaml.FullLoader)

TAGS_SYMBOL = JRNL_CONFIG.get("tagsymbols", "@")

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM