Come posso estendere un modulo Python? L'aggiunta di nuove funzionalità per il pacchetto `python-twitter`

StackOverflow https://stackoverflow.com/questions/2705964

Domanda

Quali sono le migliori pratiche per l'estensione di un modulo Python esistente -. In questo caso, voglio estendere il pacchetto python-twitter con l'aggiunta di nuovi metodi per la classe di base API

Ho guardato tweepy, e questo mi piace pure; Ho appena trovato python-twitter più facile da capire ed estendere con la funzionalità che voglio.

Non ho i metodi scritti già - Sto cercando di capire il più Pythonic e modo meno dirompente per aggiungerli nel modulo pacchetto python-twitter, senza cambiare anima questo moduli

.
È stato utile?

Soluzione

A pochi modi.

Il modo più semplice:

Non estendere il modulo, estendere le classi.

exttwitter.py

import twitter

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

Downside: Ogni classe twitter deve essere exttwitter.py, anche se è solo uno stub (come sopra)

Un duro modo (possibilmente non-divinatorio):

Importa * da python-twitter in un modulo che poi estendere.

Per esempio:

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

Non sono sicuro se il doppio di importazione in extmodule.py è divinatorio - si potrebbe rimuoverlo, ma poi non si gestisce il caso d'uso di voler estendere una funzione che era nello spazio dei nomi di basemodule.

Per quanto riguarda le classi estese, basta creare una nuova API (basemodule.API) Classe di estendere il modulo API di Twitter.

Altri suggerimenti

Do non li aggiungere al modulo. Sottoclasse le classi che si desidera estendere e utilizzare le sottoclassi nel proprio modulo, non cambiando la roba originale a tutti.

Ecco come si può manipolare direttamente l'elenco dei moduli in fase di esecuzione - spoiler alert: si ottiene il tipo di modulo dal modulo 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

... È quindi possibile utilizzare tale funzione in questo modo:

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__}")

... Si potrebbe anche creare un proprio modulo sottoclasse, specificando types.ModuleType come l'antenato del vostro class appena dichiarato, naturalmente; Non ho mai trovato personalmente questo bisogna fare.

(Inoltre, non è necessario sono per ottenere il tipo di modulo dal modulo types - si può sempre e solo fare qualcosa di simile ModuleType = type(os) dopo l'importazione os - ho espressamente sottolineato questa una fonte di tipo perché non è ovvio;. a differenza di molti dei suoi altri tipi built-in, Python non offre l'accesso al tipo di modulo nel namespace globale)

L'azione reale è in dict sys.modules, dove (se siete in modo appropriato intrepido) è possibile sostituire i moduli esistenti e aggiungendo i nuovi.

Diciamo che hanno un modulo più vecchio chiamato mod che si utilizza in questo modo:

import mod

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

E si desidera estendere, senza sostituirlo per gli utenti. Fatto facilmente. Potete dare il vostro nuovo modulo un nome diverso, newmod.py o metterlo da stesso nome a un percorso più profondo e mantenere lo stesso nome, ad esempio /path/to/mod.py. Quindi gli utenti possono importare in uno dei seguenti modi:

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

o

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

Nel modulo, ti consigliamo di fare tutti i vecchi nomi disponibili:

from mod import *

o un nome in modo esplicito ogni nome si importa:

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

Penso che il import * sarà più gestibile per questo caso d'uso - se il modulo base si espande le funzionalità, avrete senza tenere il passo (anche se si potrebbe ombra nuovi oggetti con lo stesso nome).

Se il mod si sta estendendo ha un __all__ decente, limiterà i nomi importati.

Si dovrebbe anche dichiarare un __all__ ed estenderlo con __all__ del modulo estesa.

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)

Poi estendere gli oggetti e le funzionalità come si farebbe normalmente.

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

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

Se i nuovi oggetti forniscono funzionalità che si intende sostituire (o forse si sta backport la nuova funzionalità in una base di codice più vecchio) è possibile sovrascrivere i nomi

Mi permetto di suggerire di non reinventare la ruota qui? Sto costruendo una> 6k linea di Twitter client per due mesi ormai, in un primo momento ho controllato python-twitter troppo, ma è in ritardo molto dietro le recenti modifiche API ,, lo sviluppo non sembra essere che attiva sia, inoltre c'era (almeno quando ho controllato) alcun supporto per OAuth / xAuth).

Così, dopo aver cercato in giro un po 'più ho scoperto tweepy:
http://github.com/joshthecoder/tweepy

Pro:. Sviluppo attivo, OAauth / xauth e al passo con l'API
Le probabilità sono alte che quello che vi serve è già lì.

Quindi suggerisco di andare con questo, si sta lavorando per me, l'unica cosa che ho dovuto aggiungere era XAuth (che di nuovo si fondono avuto modo di tweepy:)

Oh una spina di una spudorata, se avete bisogno di analizzare i tweet e / o formattare loro di HTML usare la mia versione di Python delle librerie twitter-testo- *:
http://github.com/BonsaiDen/twitter-text-python

Questa cosa è unittestetd un garantito ai Tweet parse proprio come Twitter.com fa.

definire una nuova classe, e invece di ereditare dalla classe si desidera estendere dal modulo originale, aggiungere un'istanza della classe originale come un attributo alla nuova classe. E qui arriva il trucco: intercetta tutte le chiamate non-esistente metodo sulla vostra nuova classe e provare a chiamare sul istanza della vecchia classe. Nella tua NewClass basta definire metodi nuovi o ignorati come ti piace:

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")

Nel mio caso ho usato questa soluzione quando semplice eredità dalla vecchia classe non era possibile, perché un'istanza doveva essere creato non dal suo costruttore, ma con un init script da un altro / modulo di classe. (È l'originalmodule.create_oldclass_instance nell'esempio precedente.)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top