为什么模块不能成为上下文管理器(对'with'语句)?

时间:2016-11-15 09:09:28

标签: python python-3.x python-module with-statement contextmanager

假设我们有以下mod.py

def __enter__():
    print("__enter__<")

def __exit__(*exc):
    print("__exit__< {0}".format(exc))

class cls:
    def __enter__(self):
        print("cls.__enter__<")

    def __exit__(self, *exc):
        print("cls.__exit__< {0}".format(exc))

以及以下用途:

import mod

with mod:
    pass

我收到错误:

Traceback (most recent call last):
  File "./test.py", line 3, in <module>
    with mod:
AttributeError: __exit__

根据文档文档,with语句应如下执行(我相信它在第2步失败,因此截断列表):

  
      
  1. 评估上下文表达式( with_item 中给出的表达式)以获取上下文管理器。
  2.   
  3. 加载了上下文管理器的__exit__()以供以后使用。
  4.   
  5. 调用上下文管理器的__enter__()方法。
  6.   
  7. 等...
  8.   

正如我所理解的那样,没有理由找不到__exit__。是否有一些我错过了使模块无法作为上下文管理器工作的东西?

3 个答案:

答案 0 :(得分:7)

__exit__特殊方法,因此Python会在类型上查找它。 module类型没有这样的方法,这就是失败的原因。

请参阅Python数据模型文档的Special method lookup section

  

对于自定义类,只有在对象的类型上定义,而不是在对象的实例字典中,才能保证特殊方法的隐式调用正常工作。

请注意,这适用于所有特殊方法。例如,如果您向模块添加了__str____repr__函数,则在打印模块时也不会调用它。

Python这样做是为了确保类型对象既可以清晰也可以表示;如果Python 没有这样做,那么当为该类定义__hash__方法时,尝试将类对象放入字典会失败(因为该方法会期望实例传递in for self)。

答案 1 :(得分:2)

由于@Martijn Pieters answer中所述的原因,您无法轻松完成此操作。但是通过一些额外的工作, 是可能的,因为sys.modules中的值不必是内置模块类的实例,它们可以是您自己的实例自定义类,具有上下文管理器所需的特殊方法。

将此应用于您想要做的事情。鉴于以下mod.py

import sys

class MyModule(object):
    def __enter__(self):
        print("__enter__<")

    def __exit__(self, *exc):
        print("__exit__> {0}".format(exc))

# replace entry in sys.modules for this module with an instance of MyModule
_ref = sys.modules[__name__]
sys.modules[__name__] = MyModule()

以下使用它:

import mod

with mod:
    print('running within context')

将产生此输出:

__enter__<
running within context
__exit__> (None, None, None)

有关为何需要_ref的信息,请参阅this问题。

答案 2 :(得分:1)

比马蒂诺(Marteaueau)提出的版本更柔和的版本,争论性小一些:

import 'package:flutter/material.dart';
import 'package:palette_generator/palette_generator.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Palette Generator',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  Future _updateColors;
  final List<PaletteColor> _colors = [];
  int _currentIndex;
  final List<String> _images = [
    'https://picsum.photos/id/491/200/300',
    'https://picsum.photos/id/400/200/300',
    'https://picsum.photos/id/281/200/300'
  ];

  @override
  void initState() {
    super.initState();
    _currentIndex = 0;
    _updateColors = _updatePalettes();
  }

  Future<bool> _updatePalettes() async {
    for (final String image in _images) {
      final PaletteGenerator generator =
          await PaletteGenerator.fromImageProvider(NetworkImage(image));
      _colors.add(generator.dominantColor != null
          ? generator.dominantColor
          : PaletteColor(Colors.blue, 2));
    }
    setState(() {});
    return true;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Color Palette Generator Demo'),
        elevation: 0,
        backgroundColor: _colors.isNotEmpty
            ? _colors[_currentIndex].color
            : Theme.of(context).primaryColor,
      ),
      body: FutureBuilder<bool>(
        future: _updateColors,
        builder: (context, snapshot) {
          if (snapshot.data == true)
            return Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Container(
                  width: double.infinity,
                  height: 200,
                  color: _colors.isNotEmpty
                      ? _colors[_currentIndex].color
                      : Colors.white,
                  child: PageView(
                    onPageChanged: (value) =>
                        setState(() => _currentIndex = value),
                    children: _images
                        .map((image) => Container(
                              padding: const EdgeInsets.all(16.0),
                              margin: const EdgeInsets.all(16.0),
                              decoration: BoxDecoration(
                                borderRadius: BorderRadius.circular(30.0),
                                image: DecorationImage(
                                  image: NetworkImage(image),
                                  fit: BoxFit.cover,
                                ),
                              ),
                            ))
                        .toList(),
                  ),
                ),
                Expanded(
                  child: Container(
                    padding: const EdgeInsets.all(32.0),
                    width: double.infinity,
                    decoration: BoxDecoration(
                        color: _colors.isNotEmpty
                            ? _colors[_currentIndex].color
                            : Colors.white),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.center,
                      children: <Widget>[
                        Text(
                          "Color Palette",
                          style: TextStyle(
                            color: _colors.isNotEmpty
                                ? _colors[_currentIndex].titleTextColor
                                : Colors.black,
                            fontWeight: FontWeight.bold,
                            fontSize: 30.0,
                          ),
                        ),
                        const SizedBox(height: 10.0),
                        Icon(
                          Icons.ac_unit,
                          size: 100,
                          color: _colors.isNotEmpty
                              ? _colors[_currentIndex].bodyTextColor
                              : Colors.black,
                        )
                      ],
                    ),
                  ),
                ),
              ],
            );
          return const Center(child: CircularProgressIndicator());
        },
      ),
    );
  }
}

代替替换模块(可能会导致无数问题),只需用继承自原始类的一个替换该类。这样,将保留原始模块对象,不需要另一个ref(防止垃圾收集),它将与任何自定义导入程序一起使用。请注意一个重要的事实,即创建模块对象并将其添加到sys.modules 之前

请注意,使用这种方法,您可以添加任何魔术方法