Как расширить модуль Python? Добавление новой функциональности в пакет "python-twitter"

Каковы лучшие практики для расширения существующего модуля Python - в этом случае я хочу расширить пакет python-twitter, добавив новые методы в базовый класс API.

Я смотрел на tweepy, и мне это тоже нравится; Я просто нахожу python-twitter проще для понимания и дополнения той функциональностью, которая мне нужна.

У меня уже есть написанные методы - я пытаюсь найти наиболее Pythonic и наименее разрушительный способ добавить их в модуль пакета python-twitter, не меняя ядро этого модуля.

Ответы

Ответ 1

Несколько способов.

Простой способ:

Не расширяйте модуль, расширяйте классы.

exttwitter.py

import twitter

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

Даунсайд: каждый класс в твиттере должен находиться в exttwitter.py, даже если это просто заглушка (как указано выше)

Более сложный (возможно, непифонный) способ:

Импортировать * из python-twitter в модуль, который затем расширяется.

Например:

basemodule.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"

extmodule.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"

runscript.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 - вы можете удалить его, но тогда вы не обрабатываете usecase необходимости расширения функции, которая была в пространстве имен basemodule.

Что касается расширенных классов, просто создайте новый класс API (basemodule.API) для расширения модуля API Twitter.

Ответ 2

Не добавляйте их в модуль. Подкласс классов, которые вы хотите расширить, и использовать свои подклассы в своем собственном модуле, не изменяя исходный материал вообще.

Ответ 3

Вот как вы можете напрямую манипулировать списком модулей во время выполнения - предупреждение о спойлере: вы получаете тип модуля из модуля 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 thisd 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.modules, где (если вы соответственно отважны) вы можете заменить существующие модули, а также добавить свои новые.

Ответ 4

Скажем, у вас есть более старый модуль под названием 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 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 functionality"""

Если новые объекты предоставляют функциональность, которую вы собираетесь заменить (или, возможно, вы передаете новую функциональность в более раннюю базу кода), вы можете перезаписать имена

Ответ 5

Могу ли я предложить не изобретать колесо здесь? Я создаю клиентский клиент > 6k line Twitter уже 2 месяца, сначала я также проверил python-twitter, но он сильно отстает от последних изменений API. Разработка тоже не так активна, также есть (по крайней мере, когда я последний раз проверял) нет поддержки OAuth/xAuth).

Итак, после поиска немного больше, я обнаружил tweepy:
http://github.com/joshthecoder/tweepy

Плюсы: Активная разработка, OAauth/xAuth и обновление API.
Шансы высоки, что то, что вам нужно, уже там.

Итак, я предлагаю, чтобы это сработало для меня, единственное, что я должен был добавить, это xAuth (который снова слился в tweepy:)

О, бесстыдный плагин, если вам нужно разобрать Tweets и/или отформатировать их в HTML, используйте мою версию python библиотек twitter-text- *:
http://github.com/BonsaiDen/twitter-text-python

Эта вещь является unittestetd гарантированной для разбора твитов, как это делает Twitter.com.

Ответ 6

Определите новый класс и вместо того, чтобы наследовать его от класса, который вы хотите расширить из исходного модуля, добавьте экземпляр исходного класса в качестве атрибута в ваш новый класс. И тут возникает хитрость: перехватывать все несуществующие вызовы методов вашего нового класса и пытаться вызвать его на экземпляре старого класса. В вашем 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")

В моем случае я использовал это решение, когда простое наследование от старого класса было невозможно, потому что экземпляр должен был быть создан не его конструктором, а с помощью сценария инициализации из другого класса/модуля. (Это исходный модуль .create_oldclass_instance в приведенном выше примере.)