¿Cómo se amplía un módulo de Python? Añadiendo nuevas funciones al paquete `python-twitter`
-
01-10-2019 - |
Pregunta
¿Cuáles son las mejores prácticas para ampliar un módulo de Python existente -. En este caso, quiero extender el paquete python-twitter
mediante la adición de nuevos métodos para la clase API
He mirado en tweepy
, y eso me gusta, así; Acabo de encontrar python-twitter
más fácil de entender y extender con la funcionalidad que quiero.
Tengo los métodos ya escritos - Estoy tratando de averiguar el más Pythonic y forma menos perjudicial para agregarlos en el módulo de paquete python-twitter
, sin cambiar núcleo estos módulos
Solución
Algunas maneras.
La forma más fácil:
No extienda el módulo, se extienden las clases.
exttwitter.py
import twitter
class Api(twitter.Api):
pass
# override/add any functions here.
Lo malo: Cada clase en Twitter debe estar en exttwitter.py, incluso si es sólo un talón (como el anterior)
A más difícil manera (posiblemente un-Pythonic):
Importar * desde pitón-twitter en un módulo que se extienden entonces.
Por ejemplo:
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
No estoy seguro de si el doble de importaciones en extmodule.py es Pythonic - podría eliminarlo, pero entonces no manejan el caso de uso de querer extender una función que estaba en el espacio de nombres de basemodule.
En cuanto a las clases extendidas, solo crea una nueva API de la clase (basemodule.API) para extender el módulo API de Twitter.
Otros consejos
No añadir al módulo. Subclase las clases que desea ampliar y utilizar sus subclases en su propio módulo, sin cambiar el material original en absoluto.
Así es como se puede manipular directamente la lista de módulos en tiempo de ejecución - alerta de spoiler: que presentamos lo mejor el tipo de módulo del módulo 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
... A continuación, puede utilizar una función de este 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__}")
... Se podría también crear su propia subclase módulo, especificando types.ModuleType
como el antepasado de su class
recién declarado, por supuesto; Nunca he encontrado personalmente este necesario hacerlo.
(También, no lo hace Tienes para obtener el tipo de módulo del módulo types
- siempre se puede simplemente hacer algo como ModuleType = type(os)
después de importar os
- me señaló específicamente la fuente única del tipo porque no es obvia;. a diferencia de muchos de sus otros tipos internos, Python no ofrece el acceso al tipo de módulo en el espacio de nombres global)
La verdadera acción está en el dict sys.modules
, donde (si está adecuadamente intrépido) puede reemplazar módulos existentes, así como la adición de los nuevos.
Supongamos que tiene un módulo más antiguo llamado mod
que utilice la siguiente manera:
import mod
obj = mod.Object()
obj.method()
mod.function()
# and so on...
Y desea extenderlo, sin reemplazarlo para sus usuarios. Fácil de hacer. Usted puede dar a su nuevo módulo de un nombre diferente, newmod.py
o colocarlo por mismo nombre en un camino más profundo y mantener el mismo nombre, por ejemplo, /path/to/mod.py
. A continuación, los usuarios pueden importar en cualquiera de estas maneras:
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
En su módulo, tendrá que hacer todos los viejos nombres disponibles:
from mod import *
o nombrar explícitamente todos los nombres de importar:
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
Creo que el import *
será más fácil de mantener para este caso de uso - si el módulo base se amplía la funcionalidad, se le mantienen al día sin problemas (aunque es posible que la sombra nuevos objetos con el mismo nombre).
Si el mod
está extendiendo tiene una __all__
decente, restringirá los nombres importados.
También debe declarar un __all__
y ampliarlo con __all__
del módulo extendido.
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)
A continuación, extender los objetos y funcionalidad como lo haría normalmente.
class NewObject(object):
def newmethod(self):
"""this method extends Object"""
def newfunction():
"""this function builds on mod's functionality"""
Si los nuevos objetos proporcionan funcionalidad tiene la intención de reemplazar (o tal vez usted está backporting la nueva funcionalidad en una base de código antiguo) puede sobrescribir los nombres
¿Puedo sugerir que no reinventar la rueda? Estoy construyendo una> 6k línea de cliente de Twitter hace 2 mes, en un primer momento he comprobado python-twitter también, pero está quedando mucho detrás de los recientes cambios en la API de Desarrollo ,, no parece ser que sea activa, también hubo (al menos la última vez que revisé) no hay soporte para OAuth / XAUTH).
Así que después de buscar en torno a un poco más descubrí tweepy:
http://github.com/joshthecoder/tweepy
Pros:. El desarrollo activo, OAauth / XAUTH y hasta la fecha con la API
Hay muchas posibilidades de que lo que necesita ya está ahí.
Así que le sugiero ir con eso, está funcionando para mí, lo único que tenía que añadir era XAUTH (que volver fusión tiene que tweepy:)
Oh un tapón de una desvergonzada, si es necesario analizar los Tweets y / o darles formato HTML a utilizar mi versión pitón de las bibliotecas * twitter-texto-:
http://github.com/BonsaiDen/twitter-text-python
Esta cosa es un unittestetd garantizado a los Tweets de análisis sintáctico al igual que lo hace Twitter.com.
Definir una nueva clase, y en vez de heredan de la clase que desea que se extienda desde el módulo original, añadir una instancia de la clase original como un atributo a su nueva clase. Y aquí viene el truco: intercepción de todas las llamadas inexistentes método en su nueva clase y tratar de llamarlo en la instancia de la clase de edad. En su NewClass acaba de definir los métodos nuevos o cambiados a su gusto:
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")
En mi caso he utilizado esta solución cuando la herencia sencilla de la clase de edad no fue posible, debido a una instancia tuvo que ser creada no por su constructor, pero con un guión de inicio de una otra clase / módulo. (Es la originalmodule.create_oldclass_instance en el ejemplo anterior.)