【问题标题】:Use module as class instance in Python在 Python 中使用模块作为类实例
【发布时间】:2017-01-19 17:49:48
【问题描述】:

TL;博士

基本上,问题是关于向用户隐藏我的模块具有类实现的事实,以便用户可以使用该模块,就好像它具有像my_module.func()这样的直接函数定义

详情

假设我有一个模块 my_module 和一个类 MyThing 。例如:

# my_module.py

class MyThing(object):
    def say():
        print("Hello!")

在另一个模块中,我可能会这样做:

# another_module.py

from my_module import MyThing

thing = MyThing()
thing.say()

但假设我不想做这一切。我真正想要的是 my_moduleimport 上自动创建 MyThing 的实例,这样我就可以执行以下操作:

# yet_another_module.py

import my_module

my_module.say()

换句话说,无论我在模块上调用什么方法,我都希望它直接转发到其中包含的类的默认实例。因此,对于模块的用户来说,似乎其中没有类,只是模块本身中的直接函数定义(其中函数实际上是其中包含的类的方法)。那有意义吗?有没有捷径可以做到这一点?

我知道我可以在my_module 中执行以下操作:

class MyThing(object):
    def say():
        print("Hello!")

default_thing = MyThing()

def say():
    default_thing.say()

但是假设MyThing 有许多我想使用的“公共”方法,那么我必须为每个方法显式定义一个“转发”函数,这是我不想做的。

作为我上面问题的扩展,有没有办法实现我上面想要的,但也能够使用像from my_module import *这样的代码,并且能够直接在另一个模块中使用MyThing的方法,比如@ 987654334@?

【问题讨论】:

  • 可以但是你为什么要真的呢?你通常做这种事情的方式是from something import something,然后只需使用something.(...),其中something 是你的类实例...
  • 你可以使用MyThing().say()
  • @JonClements 我想你是对的,但我想这样做的原因是我正在将一些代码重构为类,并在代码库中加载了已经使用此模块的其他代码正如我在问题中描述的那样使用它,所以我不希望每个人都改变他们使用模块的方式,因为我将模块代码重构为类。 (我想将模块代码重构为类的原因是因为目前有很多代码异味,我可以使用类继承来重用部分代码。)

标签: python class module


【解决方案1】:

不知道为什么这不起作用。

say = MyClass().say()
from my_module import *

say
>>Hello!

【讨论】:

  • 不完全。如果我要执行from my_module import *,那么我希望能够为输出Hello! 执行say()
【解决方案2】:

在模块my_module 中执行以下操作:

class MyThing(object):
    ...

_inst = MyThing()
say = _inst.say
move = _inst.move

正是random module 使用的模式。

自动执行此操作有些做作。首先,需要找出哪些实例/类属性是要导出的方法...也许只导出不以_开头的名称,类似于

import inspect
for name, member in inspect.getmembers(Foo(), inspect.ismethod):
    if not name.startswith('_'):
        globals()[name] = member

但在这种情况下,我会说显式优于隐式。

【讨论】:

  • 这正是我想做的。唯一的问题是我必须将每个函数(如saymove)转发到它们相应的方法(如_instance.say_instance.move)。因此,如果我(或其他人)想向MyThing 添加公共方法,他们也必须记住进行转发。如果这是唯一的方法,这很好,但它是唯一的方法吗?没有办法一次性转发所有方法,这样我(或其他任何人)就不必记得为他们可能添加到MyThing 的每个方法明确地进行转发?
  • 是的,有,但是问题为什么继续这样做,因为它会导致代码异味
  • 啊,我刚刚读到你指出这就是random 所做的事情。它以某种方式让我放心,其中一个标准模块会做这种事情。不过,我对如何一次性转发所有方法在学术上很好奇。另外,我现在无法想象它会如何导致代码异味。我只能想象它会摆脱代码异味,但那是因为我缺乏经验。
  • 我认为@SteveJessop 的回答包含了一些关于为什么我不想一次性转发所有方法的解释。我想我会坚持显式转发模式。谢谢你的回答。
  • @Ray 添加了一种可能的解决方案
【解决方案3】:

你可以替换:

def say():
    return default_thing.say()

与:

say = default_thing.say

您仍然必须列出所有转发的内容,但样板文件相当简洁。

如果您想用更自动的东西替换该样板,请注意(详细信息取决于 Python 版本),MyThing.__dict__.keys() 类似于['__dict__', '__weakref__', '__module__', 'say', '__doc__']。所以原则上你可以遍历它,跳过__ Python 内部,并在当前模块上调用setattr(可以作为sys.modules[__name__] 使用)。你以后可能会后悔没有在代码中明确列出这些东西,但你当然可以这样做。

或者,您可以完全摆脱类,因为使用模块作为封装单元。只要对象上有数据,就用全局变量替换它。 “但是”,你可能会说,“我被警告不要使用全局变量,因为据说它们会导致问题”。坏消息是您已经创建了一个全局变量default_thing,所以船已经在那个变量上航行了。更糟糕的消息是,如果对象上有任何数据,那么您想要做的整个概念:改变共享全局状态的模块级函数,会带来大部分全局问题。

【讨论】:

  • 我不想使用模块作为封装单元的主要原因是我想利用类继承。例如,实际上我有两个模块,我需要其中一个从另一个继承很多功能,而不是全局变量。最好的方法(我可以想象)是将它们变成类并使用类继承。现在我想知道是否有一种一次性转发所有方法的批发方式。我本能地想象像def __getattr__(attr): return default_thing.__getattr__(attr)这样的东西。
  • @Ray: 在模块上重载__getattr__ 我认为您需要将模块的类型定义为您自己的自定义类型。我认为,仅定义 __getattr__ 是行不通的,因为查找内置 __blah__ 挂钩的方式。我不确定这是否可行,但我想你不妨试试__getattr__,如果这不起作用,请研究指定模块具有自定义类型的方法,如果你得到一些有用的东西,请回答你自己的问题; -)
  • 非常感谢您的额外解释和见解!根据您的解释,我现在明白更简单的方法就是明确转发所有公共方法。我希望我能不止一次地为你投票。
猜你喜欢
  • 2013-12-20
  • 2011-10-03
  • 2021-04-19
  • 2020-06-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-31
  • 2016-04-05
相关资源
最近更新 更多