鱼C论坛

 找回密码
 立即注册
查看: 3493|回复: 16

[已解决]如何让自己的模块可以被()和with调用?

[复制链接]
发表于 2022-9-22 17:33:54 | 显示全部楼层 |阅读模式
30鱼币
例如:
import 自己写的模块

自己写的模块()

with 自己写的模块():
        time.sleep(3)

就是说我写了个.py文件 ,但我希望我可以直接import那个.py文件后就可以直接把那个import的.py文件当成类用?而不是还需要 .子func 这样调用
最佳答案
2022-9-22 17:33:55
这个比较容易实现。

先在你自己的模块中定义一个类(一定要是一个类),并且在类中实现魔法方法__enter__和__exit__。其实with这个关键字就是在内部调用了前面说的两个魔法方法而已。

然后实例化这个类,并且添加到sys.modules这个字典中就行了,具体为sys.modules[__name__] = 你自己定义的类名()。在你的模块最后添加这一行代码即可。

最佳答案

查看完整内容

这个比较容易实现。 先在你自己的模块中定义一个类(一定要是一个类),并且在类中实现魔法方法__enter__和__exit__。其实with这个关键字就是在内部调用了前面说的两个魔法方法而已。 然后实例化这个类,并且添加到sys.modules这个字典中就行了,具体为sys.modules[__name__] = 你自己定义的类名()。在你的模块最后添加这一行代码即可。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2022-9-22 17:33:55 | 显示全部楼层    本楼为最佳答案   
这个比较容易实现。

先在你自己的模块中定义一个类(一定要是一个类),并且在类中实现魔法方法__enter__和__exit__。其实with这个关键字就是在内部调用了前面说的两个魔法方法而已。

然后实例化这个类,并且添加到sys.modules这个字典中就行了,具体为sys.modules[__name__] = 你自己定义的类名()。在你的模块最后添加这一行代码即可。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2022-9-22 17:57:26 | 显示全部楼层
给你准备了两个示例文件。

第一个文件mod.py,假设是你自定义的模块,代码如下:
import sys


class MyClass:
    """自定义MyClass类,主要实现两个魔法方法__enter__和__exit__"""

    def __enter__(self):
        """进入with语句块时自动调用这个__enter__方法"""

        print('进入with语句块')
        print('我自己的业务逻辑代码, balabala...')
        return self  # 返回值根据需要自行设置

    def __exit__(self, exc_type, exc_value, exc_tb):
        """离开with语句块时自动调用这个__exit__方法。

        注意它的几个参数:
        exc_type: 异常类型,如果with语句块内发生了异常,异常的类型会被自动传入
        exc_value: 异常值,如果with语句块内部发生了异常,异常的值会被自动传入
        exc_tb: 异常调用栈,如果with语句块内发生了异常,调用栈信息会被自动传入
        如果没有异常则这三个参数的值都是None
        """

        print('业务逻辑处理完毕')
        print('离开with语句块')


sys.modules[__name__] = MyClass()

第二个文件main.py,用于导入自定义的模块,代码如下
import mod

with mod as m:
    print('hi')

运行main.py,结果为:
进入with语句块
我自己的业务逻辑代码, balabala...
hi
业务逻辑处理完毕
离开with语句块
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2022-9-22 18:33:43 | 显示全部楼层
Brick_Porter 发表于 2022-9-22 17:39
这个比较容易实现。

先在你自己的模块中定义一个类(一定要是一个类),并且在类中实现魔法方法__enter_ ...

sys.modules[__name__] = 你自己定义的类名() 可以 但不算是正常用法吧?
import 自己的模块 as a
在pycharm中 a. 后面弹出来的提示是错误的,弹出的方法和属性都是“自己的模块的” ,而不是“子目标类的”...
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2022-9-22 18:46:05 | 显示全部楼层
想过用from 自己的模块的 import 子目标类

然后就可以:子目标类() 和 with 子目标类():
但感觉还是太繁琐 ,因为这要求“用户”必须知道import的“子目标类”的至少开头(后面可以靠pycharm的智能补充)

其实可能根本还是关于__all__的问题:为什么python的第三方模块 例如 random ,它的__all__就有效果 ,例如:random._test()是可以运行的 ,但你智能补充找不到这个方法 ,能补充的都在random.__all__里,而我自己同样模仿他们建立了个__all__的属性 ,但却无法达到同样的效果,使用智能补充后 ,任可以看到甚至是下横线“_”开头的属性和方法
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2022-9-22 18:55:15 | 显示全部楼层
丨游戏灬需要 发表于 2022-9-22 18:33
sys.modules[__name__] = 你自己定义的类名() 可以 但不算是正常用法吧?
import 自己的模块 as a
在py ...

我先来解释sys.modules这行代码的作用,然后你再看看我给你的示例代码。

sys.modules是一个全局的字典,它保存了Python解释器自启动以来导入的所有模块。它的key是模块名,value是加载进内存的模块对象。

所以sys.modules[__name__] = MyClass()这行代码的意思就是修改字典,把原本的模块对象替换为我们自定义的实例对象。正因为这个替换操作的存在所以才保证我们导入之后能直接使用,如:
import mod

with mod:
    pass
而这也确实如你所说不算正常用法,正常情况下我们不会用自己的类或者示例对象替换模块对象。

理解以上内容之后想必你就能理解为什么
import 自己的模块 as a
之后 a. 弹出的提示不是我们预期的内容,因为我们已经使用模块中的某个对象覆盖了模块对象,换言之这里的a其实是一个实例对象的名字而不是模块对象的名字
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2022-9-22 19:09:50 | 显示全部楼层
Brick_Porter 发表于 2022-9-22 18:55
我先来解释sys.modules这行代码的作用,然后你再看看我给你的示例代码。

sys.modules是一个全局的字典 ...

你的说狸猫换太子的方法虽然可行,但不算我的目标,我之所以希望import 自己的模块 ,

然后可以直接:自己的模块() 或 with 自己的模块():pass ,目的都是想能够简便“用户”操作,

而如你说的那个方法 ,反而还需让“用户”必须能默写出“自己的模块”里的方法名才行 ,因为它反而失去了pycharm里智能补充所带来的便利 ,这种增加了“用户”使用负担的方法,使用起来意义实在不大
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2022-9-22 19:19:06 | 显示全部楼层
丨游戏灬需要 发表于 2022-9-22 19:09
你的说狸猫换太子的方法虽然可行,但不算我的目标,我之所以希望import 自己的模块 ,

然后可以直接: ...

看来你还没完全理解这种替换操作。恰恰是因为我写的这种替换操作使得用户只需要知道要导入的模块名即可,模块中有什么方法用户完全不需要知道。

来看看常规操作:
import mod as m

with m.MyClass():
    pass
,在这种常规操作下用户才需要知道模块中有哪些东西,例如此处的MyClass。

对比使用替换操作之后的代码:
import mod as m

with m:
    pass
,由于替换操作,被导入的模块唯一暴露出来的就是m,直接使用即可,不需要知道这个模块中有哪些东西
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2022-9-22 19:36:39 | 显示全部楼层
Brick_Porter 发表于 2022-9-22 19:19
看来你还没完全理解这种替换操作。恰恰是因为我写的这种替换操作使得用户只需要知道要导入的模块名即可, ...

import mod as m

with m:
    pass
以你这个例子为例,如果m中也有个提供给“用户”使用的func呢?那么就需要m.funcname()了,但用户却不能靠智能补充来知道这个funcname。确实尽管可以在import 包文件夹.包文件夹(文件名为mod ),里面__init__.py里去多写个def funcname():pass  ,可以让上面例子里的"m"可以智能补充到那个funcname ,但是在直击去m()时pycharm还是会有提醒(在高亮提示为:所有问题时)
而且,在使用智能补充给的选项里还是有你import sys 等这些并不期望用户去使用的多余的选项
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2022-9-22 20:05:23 | 显示全部楼层
Brick_Porter 发表于 2022-9-22 19:19
看来你还没完全理解这种替换操作。恰恰是因为我写的这种替换操作使得用户只需要知道要导入的模块名即可, ...

这么说吧,
我有一个写好的类
但这个类的正常使用方法是
with 这个类():
        “之后就是用户自己的代码”
        这个类.方法1()
        这个类.方法2()
        ...

这个类能被__call__的实质也是用来:
@这个类()
def 用户自己的func():
        这个类.方法1()
        这个类.方法2()
        ...
        “用户自己的代码”
        pass

所以我希望能在保留可以正常智能补充(用户可以不用背方法名,直接靠智能补充就能知道怎么用)并且不被pycharm报错(显得更标准些),让导入变得更简单些,期望如:
import “这个类”  。这样就能完事,而不是必须 from 包名 import “这个类” 来导入
因为这样用户必然得预先知道模块正常使用方法才行,
例如send2trash模块 ,用户一般只用知道了它是send2trash.send2trash()这么使用,才会知道它一般应该from send2trash import send2trash 这样导入
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2022-9-22 20:07:06 | 显示全部楼层

好吧,我意识到你说的功能可以实现,但是稍微有些复杂了。

思路有二,其一是把所有提供给用户的方法都封装到MyClass这个类中,不过这样还是没法解决你说的智能感知的问题。我用pycharm和vscode都试了试,虽然逻辑上没问题,但是智能感知还是把m当作模块对象而非实例对象。

第二条思路就是修改不替换而是修改模块对象。为模块对象动态添加方法,这个稍微有些复杂
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2022-9-22 20:17:35 | 显示全部楼层
丨游戏灬需要 发表于 2022-9-22 20:05
这么说吧,
我有一个写好的类
但这个类的正常使用方法是

好吧,我理解你的意思了。不过还是有两点要指出来

第一,之前那种替换写法不是报错,是警告。pycharm做了类型检查,发现sys.modules的值应该是模块对象,但是却被我改成了实例对象,所以给出了警告。如果是报错代码会在运行期间崩溃,不可能看到结果的。

第二,关于import你可能理解错了,不论是import xxx还是from xxx import yyy,这里的xxx都是模块名。在Python中,最底层我们都是基于模块来导入的,所以必须提供模块名。有时候看起来没有提供模块名是因为在__init__.py这样的文件中开发人员替我们提前导入了而已。也恰恰是利用了模块名Python得以构建模块与模块之间,以及模块内部的层级关系,也就是命名空间这一概念。总结来说,你说的import “这个类”就能直接使用的这种情况是语法不成立的。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2022-9-22 20:21:34 | 显示全部楼层
本帖最后由 阿奇_o 于 2022-9-22 20:26 编辑

我想你应该区分开:
- 模块是模块,类是类,虽然它们都是“对象”。但 模块对象 是不能 callable 的,即无法直接跟 ()  。
- 而 with 语句是“上下文管理”,通常是对于自定义类来说的,且这个类必须实现__enter__和__exit__

所以,如果你想让模块能像类或方法那样 可以 callable,就要玩点“黑魔法”了,也就是 Brick_Porter 给出的例子那样,
sys.modules[__name__] = MyClass  让模块名绑定到这个实现了上下文管理的自定义类的类名上(稍微修改了一下,去掉小括号,只绑定,这样不会在导入时就生成类的对象)
这样,然后既可以像一般模块那样导入,又可以先一般类那样 callable 了,import mod   ;   mod()  ;   with mod() as : ...


另外,不同的IDE所使用“智能补充”模块(插件)和语法检查 不尽相同,即使使用用了同一种模块来提供“智能补充”,也可能因为“自定义配置”有些不同。。
所以,你想要“方便用户”这一目的,还要看用户自己用了哪种IDE,又用了哪种智能补充模块,以及用户如何自定义配置。
所以,如果你真的很在意这方面,就自己去研究那些“智能补充”的插件模块吧。



想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2022-9-22 20:24:55 | 显示全部楼层
Brick_Porter 发表于 2022-9-22 20:07
好吧,我意识到你说的功能可以实现,但是稍微有些复杂了。

思路有二,其一是把所有提供给用户的方法都 ...

这个其一有刚试,结果不太好2333 ,因为我需要用到例如from functools import wraps ,如果加了as _wrapos,那么会被警告为成员保护类而使用不自由 ,而如果不加则又会让“这个类”有多余的.wraps方法...
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2022-9-22 20:31:30 | 显示全部楼层
Brick_Porter 发表于 2022-9-22 20:17
好吧,我理解你的意思了。不过还是有两点要指出来

第一,之前那种替换写法不是报错,是警告。pycharm ...

我想退而求其次 ,问下python内置的库中 ,__all__能屏蔽其他不想被用户调用的方法是怎么做到的么?我自己写的.py文件即使有__all__还是能被智能补充到?而内置库的却不会?例如import random ,random里的__all__有24个,而random能智能补充到的也有24个,但实际random._test()这个不在__all__里的也能调用,所以这个屏蔽_test方法的效果是不是普通用户能自己实现的?还是仅是pycharm对内置模块的使用优化?
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2022-9-22 20:56:07 | 显示全部楼层
丨游戏灬需要 发表于 2022-9-22 20:31
我想退而求其次 ,问下python内置的库中 ,__all__能屏蔽其他不想被用户调用的方法是怎么做到的么?我自 ...

针对你说的__all__这个问题,我要说的是它确实是pycharm的优化!

事实上__all__仅针对from xxx import *这种情况。*表示所有,如果没有指定__all__则把被导入模块中的所有对象(单下划线开头除外)添加到导入方的符号表中,如果是import xxx这样的形式你会发现__all__就失效了,那些不在__all__中的对象还是可以被强行访问到。

上面说的例外情况。单下划线开头的对象称为“受保护”的对象,也只是个形式而已,意思是说你不要使用,这是个底层对象。但是如果你强行要用,Python也挡不住你。pycharm会直接屏蔽这种受保护的方法和私有方法,智能感知中不显示,但是vscode优化没有那么好,受保护的对象就会显示出来。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2022-9-22 21:38:16 From FishC Mobile | 显示全部楼层
Brick_Porter 发表于 2022-9-22 20:56
针对你说的__all__这个问题,我要说的是它确实是pycharm的优化!

事实上__all__仅针对from xxx import ...

谢了,看来只剩用提醒“用户”用
from “这个类” import “这个类”的方法作为标准导入方法去使用了
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-1-10 17:58

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表