28

扩展现有 Python 模块的最佳实践是什么——在这种情况下,我想python-twitter通过向基础 API 类添加新方法来扩展包。

我看过tweepy,我也喜欢;我只是发现python-twitter更容易理解和扩展我想要的功能。

我已经编写了方法——我正在尝试找出最 Pythonic 且破坏性最小的方法来将它们添加到python-twitter包模块中,而无需更改此模块的核心。

4

6 回答 6

30

几种方法。

简单的方法:

不要扩展模块,扩展类。

exttwitter.py

import twitter

class Api(twitter.Api):
    pass 
    # override/add any functions here.

缺点:twitter 中的每个类都必须在 exttwitter.py 中,即使它只是一个存根(如上)

一种更难(可能不是pythonic)的方式:

将 * 从 python-twitter 导入一个模块,然后扩展。

例如 :

基本模块.py

 class Ball():
    def __init__(self,a):
        self.a=a
    def __repr__(self):
        return "Ball(%s)" % self.a

def makeBall(a):
    return Ball(a)

def override():
    print "OVERRIDE ONE"

def dontoverride():
    print "THIS WILL BE PRESERVED"

扩展模块.py

from basemodule import *
import basemodule

def makeBalls(a,b):
    foo = makeBall(a)
    bar = makeBall(b)
    print foo,bar

def override():
    print "OVERRIDE TWO"

def dontoverride():
    basemodule.dontoverride()
    print "THIS WAS PRESERVED"

运行脚本.py

import extmodule

#code is in extended module
print extmodule.makeBalls(1,2)
#returns Ball(1) Ball(2)

#code is in base module
print extmodule.makeBall(1)
#returns Ball(1)

#function from extended module overwrites base module
extmodule.override()
#returns OVERRIDE TWO

#function from extended module calls base module first
extmodule.dontoverride()
#returns THIS WILL BE PRESERVED\nTHIS WAS PRESERVED

我不确定 extmodule.py 中的双重导入是否是 pythonic - 你可以删除它,但是你不处理想要扩展 basemodule 命名空间中的函数的用例。

至于扩展类,只需创建一个新的 API(basemodule.API) 类来扩展 Twitter API 模块。

于 2010-04-24T20:33:41.000 回答
6

不要将它们添加到模块中。子类化你想要扩展的类并在你自己的模块中使用你的子类,根本不改变原来的东西。

于 2010-04-24T20:16:32.010 回答
6

这是在运行时直接操作模块列表的方法 -剧透警告:您从模块中获取模块类型types

from __future__ import print_function
import sys
import types
import typing as tx

def modulize(namespace: tx.Dict[str, tx.Any],
             modulename: str,
             moduledocs: tx.Optional[str] = None) -> types.ModuleType:

    """ Convert a dictionary mapping into a legit Python module """

    # Create a new module with a trivially namespaced name:
    namespacedname: str = f'__dynamic_modules__.{modulename}'
    module = types.ModuleType(namespacedname, moduledocs)
    module.__dict__.update(namespace)

    # Inspect the new module:
    name: str = module.__name__
    doc: tx.Optional[str] = module.__doc__
    contents: str = ", ".join(sorted(module.__dict__.keys()))
    print(f"Module name:      {name}")
    print(f"Module contents:  {contents}")
    if doc:
        print(f"Module docstring: {doc}")

    # Add to sys.modules, as per import machinery:
    sys.modules.update({ modulename : module })

    # Return the new module instance:
    return module

…然后你可以使用这样的函数:

ns = {
         'func' : lambda: print("Yo Dogg"), # these can also be normal non-lambda funcs
    'otherfunc' : lambda string=None: print(string or 'no dogg.'),
      '__all__' : ('func', 'otherfunc'),
      '__dir__' : lambda: ['func', 'otherfunc'] # usually this’d reference __all__
}

modulize(ns, 'wat', "WHAT THE HELL PEOPLE")
import wat

# Call module functions:
wat.func()
wat.otherfunc("Oh, Dogg!")

# Inspect module:
contents = ", ".join(sorted(wat.__dict__.keys()))
print(f"Imported module name:      {wat.__name__}")
print(f"Imported module contents:  {contents}")
print(f"Imported module docstring: {wat.__doc__}")

types.ModuleType... 当然,您也可以通过指定新声明的 的祖先来创建自己的模块子类class;我个人从未发现有必要这样做。

(另外,你不必从模块获取模块类型types——你总是可以ModuleType = type(os)在导入之后做一些事情os——我特别指出了这个类型的来源,因为它不是显而易见的;不像它的许多其他内置类型,Python 不提供对全局命名空间中模块类型的访问。)

真正的行动在sys.modulesdict 中,在那里(如果你足够勇敢的话)你可以替换现有的模块以及添加你的新模块。

于 2018-09-05T11:20:08.370 回答
3

假设您有一个旧模块mod,您可以像这样使用它:

import mod

obj = mod.Object()
obj.method()
mod.function()
# and so on...

而且您想扩展它,而不是为您的用户替换它。轻松搞定。你可以给你的新模块一个不同的名字,newmod.py或者用相同的名字把它放在更深的路径上并保持相同的名字,例如/path/to/mod.py。然后您的用户可以通过以下任一方式导入它:

import newmod as mod       # e.g. import unittest2 as unittest idiom from Python 2.6

或者

from path.to import mod    # useful in a large code-base

在您的模块中,您需要使所有旧名称可用:

from mod import *

或明确命名您导入的每个名称:

from mod import Object, function, name2, name3, name4, name5, name6, name7, name8, name9, name10, name11, name12, name13, name14, name15, name16, name17, name18, name19, name20, name21, name22, name23, name24, name25, name26, name27, name28, name29, name30, name31, name32, name33, name34, name35, name36, name37, name38, name39

我认为import *这个用例将更易于维护 - 如果基本模块扩展功能,您将无缝跟上(尽管您可能会使用相同的名称遮蔽新对象)。

如果mod您正在扩展的 有一个不错的__all__,它将限制导入的名称。

您还应该声明一个__all__并使用扩展模块的__all__.

import mod
__all__ = ['NewObject', 'newfunction']
__all__ += mod.__all__   
# if it doesn't have an __all__, maybe it's not good enough to extend
# but it could be relying on the convention of import * not importing
# names prefixed with underscores, (_like _this)

然后像往常一样扩展对象和功能。

class NewObject(object):
    def newmethod(self):
        """this method extends Object"""

def newfunction():
    """this function builds on mod's functionality"""

如果新对象提供了您打算替换的功能(或者您可能正在将新功能反向移植到旧代码库中),您可以覆盖名称

于 2015-10-26T17:55:43.297 回答
2

我可以建议不要在这里重新发明轮子吗?我正在构建一个超过 6k 行的 Twitter 客户端 2 个月,起初我也检查了 python-twitter,但它落后于最近的 API 更改很多,开发似乎也没有那么活跃,也有(至少在我上次检查时)不支持 OAuth/xAuth)。

所以在搜索了更多之后,我发现了 tweepy:http:
//github.com/joshthecoder/tweepy

优点:积极开发、OAauth/xAuth 和最新的 API。
您需要的东西很有可能已经在那里了。

所以我建议继续这样做,它对我有用,我唯一需要添加的是 xAuth(合并回 tweepy :)

哦,一个无耻的插件,如果您需要解析推文和/或将它们格式化为 HTML,请使用我的 twitter-text-* 库的 python 版本:http:
//github.com/BonsaiDen/twitter-text-python

这个东西是 unittestetd 保证解析推文就像 Twitter.com 做的那样。

于 2010-04-24T21:08:48.860 回答
2

定义一个新类,而不是从要从原始模块扩展的类继承它,而是将原始类的实例作为属性添加到新类。诀窍来了:拦截新类上所有不存在的方法调用,并尝试在旧类的实例上调用它。在您的 NewClass 中,只需根据需要定义新的或覆盖的方法:

import originalmodule

class NewClass:
    def __init__(self, *args, **kwargs):
        self.old_class_instance = originalmodule.create_oldclass_instance(*args, **kwargs)

    def __getattr__(self, methodname):
        """This is a wrapper for the original OldClass class.

        If the called method is not part of this NewClass class,
        the call will be intercepted and replaced by the method
        in the original OldClass instance.
        """
        def wrapper(*args, **kwargs):
            return getattr(self.old_class_instance, methodname)(*args, **kwargs)
        return wrapper

    def new_method(self, arg1):
        """Does stuff with the OldClass instance"""
        thing = self.old_class_instance.get_somelist(arg1)
        # returns the first element only
        return thing[0]

    def overridden_method(self):
        """Overrides an existing method, if OldClass has a method with the same name"""
        print("This message is coming from the NewClass and not from the OldClass")

在我的情况下,当无法从旧类进行简单继承时,我使用了这个解决方案,因为实例必须不是由其构造函数创建,而是使用来自其他类/模块的初始化脚本。(它是上面示例中的 originalmodule.create_oldclass_instance。)

于 2019-07-03T09:16:03.780 回答