假设我们有以下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步失败,因此截断列表):
- 评估上下文表达式( with_item 中给出的表达式)以获取上下文管理器。
- 加载了上下文管理器的
__exit__()
以供以后使用。- 调用上下文管理器的
__enter__()
方法。- 等...
醇>
正如我所理解的那样,没有理由找不到__exit__
。是否有一些我错过了使模块无法作为上下文管理器工作的东西?
答案 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 之前。
请注意,使用这种方法,您可以添加任何魔术方法