Avec les propriétés python, je peux faire en sorte que
obj.y
appelle une fonction plutôt que de simplement renvoyer une valeur.
Existe-t-il un moyen de le faire avec des modules? J'ai un cas où je veux
module.y
pour appeler une fonction, plutôt que de simplement renvoyer la valeur qui y est stockée.
Seules les instances de classes de nouveau style peuvent avoir des propriétés. Vous pouvez faire croire à Python qu'une telle instance est un module en la stockant dans sys.modules[thename] = theinstance
. Ainsi, par exemple, votre fichier de module m.py pourrait être:
import sys
class _M(object):
def __init__(self):
self.c = 0
def afunction(self):
self.c += 1
return self.c
y = property(afunction)
sys.modules[__name__] = _M()
Je le ferais afin d'hériter correctement de tous les attributs d'un module et d'être correctement identifié par isinstance ()
import types
class MyModule(types.ModuleType):
@property
def y(self):
return 5
>>> a=MyModule("test")
>>> a
<module 'test' (built-in)>
>>> a.y
5
Et puis vous pouvez insérer ceci dans sys.modules:
sys.modules[__name__] = MyModule(__name__) # remember to instantiate the class
Comme PEP 562 a été implémenté dans Python> = 3.7, nous pouvons maintenant le faire
fichier: module.py
def __getattr__(name):
if name == 'y':
return 3
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
other = 4
utilisation:
>>> import module
>>> module.y
3
>>> module.other
4
>>> module.nosuch
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "module.py", line 4, in __getattr__
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
AttributeError: module 'module' has no attribute 'nosuch'
Notez que si vous omettez le raise AttributeError
dans le __getattr__
fonction, cela signifie que la fonction se termine par return None
, puis le module.nosuch
obtiendra une valeur de None
.
Un cas d'utilisation typique est: l'enrichissement d'un (énorme) module existant avec quelques (quelques) attributs dynamiques - sans transformer tous les éléments du module en une disposition de classe. Malheureusement, un correctif de classe de module le plus simple comme sys.modules[__name__].__class__ = MyPropertyModule
échoue avec TypeError: __class__ assignment: only for heap types
. La création de modules doit donc être recâblée.
Cette approche le fait sans Python hooks d'importation, juste en ayant un prologue au-dessus du code du module:
# propertymodule.py
""" Module property example """
if '__orgmod__' not in globals():
# constant prolog for having module properties / supports reload()
print "PropertyModule stub execution", __name__
import sys, types
class PropertyModule(types.ModuleType):
def __str__(self):
return "<PropertyModule %r from %r>" % (self.__name__, self.__file__)
modnew = PropertyModule(__name__, __doc__)
modnew.__modclass__ = PropertyModule
modnew.__file__ = __file__
modnew.__orgmod__ = sys.modules[__name__]
sys.modules[__name__] = modnew
exec sys._getframe().f_code in modnew.__dict__
else:
# normal module code (usually vast) ..
print "regular module execution"
a = 7
def get_dynval(module):
return "property function returns %s in module %r" % (a * 4, module.__name__)
__modclass__.dynval = property(get_dynval)
Usage:
>>> import propertymodule
PropertyModule stub execution propertymodule
regular module execution
>>> propertymodule.dynval
"property function returns 28 in module 'propertymodule'"
>>> reload(propertymodule) # AFTER EDITS
regular module execution
<module 'propertymodule' from 'propertymodule.pyc'>
>>> propertymodule.dynval
"property function returns 36 in module 'propertymodule'"
Remarque: quelque chose comme from propertymodule import dynval
produira bien sûr une copie figée - correspondant à dynval = someobject.dynval
Basé sur réponse de John Lin :
def module_property(func):
"""Decorator to turn module functions into properties.
Function names must be prefixed with an underscore."""
module = sys.modules[func.__module__]
def base_getattr(name):
raise AttributeError(
f"module '{module.__name__}' has no attribute '{name}'")
old_getattr = getattr(module, '__getattr__', base_getattr)
def new_getattr(name):
if f'_{name}' == func.__name__:
return func()
else:
return old_getattr(name)
module.__getattr__ = new_getattr
return func
Utilisation (notez le tiret bas), dans the_module.py
:
@module_property
def _thing():
return 'hello'
Ensuite:
import the_module
print(the_module.thing) # prints 'hello'
Le trait de soulignement de tête est nécessaire pour différencier la fonction de propriété de la fonction d'origine. Je ne pouvais pas penser à un moyen de réaffecter l'identifiant, car pendant le temps de l'exécution du décorateur, il n'a pas encore été attribué.
Notez que les IDE ne sauront pas que la propriété existe et afficheront des ondulations rouges.
proxy_tools
Le proxy_tools
package tente de fournir @module_property
Fonctionnalité.
Il s'installe avec
pip install proxy_tools
En utilisant une légère modification de l'exemple de @ Marein, dans the_module.py
nous mettons
from proxy_tools import module_property
@module_property
def thing():
print(". ", end='') # Prints ". " on each invocation
return 'hello'
Maintenant, à partir d'un autre script, je peux faire
import the_module
print(the_module.thing)
# . hello
Cette solution n'est pas sans réserves. À savoir, the_module.thing
n'est pas une chaîne ! C'est un proxy_tools.Proxy
objet dont les méthodes spéciales ont été remplacées de façon à imiter une chaîne. Voici quelques tests de base qui illustrent le point:
res = the_module.thing
# [No output!!! Evaluation doesn't occur yet.]
print(type(res))
# <class 'proxy_tools.Proxy'>
print(isinstance(res, str))
# False
print(res)
# . hello
print(res + " there")
# . hello there
print(isinstance(res + "", str))
# . True
print(res.split('e'))
# . ['h', 'llo']
En interne, la fonction d'origine est stockée dans the_module.thing._Proxy__local
:
print(res._Proxy__local)
# <function thing at 0x7f729c3bf680>
Honnêtement, je ne comprends pas pourquoi les modules n'ont pas cette fonctionnalité intégrée. Je pense que le nœud du problème est que the_module
est une instance de types.ModuleType
classe. Définir une "propriété de module" revient à définir une propriété sur une instance de cette classe, plutôt que sur la types.ModuleType
classe elle-même. Pour plus de détails, voir cette réponse .
Nous pouvons réellement implémenter des propriétés sur types.ModuleType
comme suit, bien que les résultats ne soient pas excellents. Nous ne pouvons pas modifier directement les types intégrés, mais nous pouvons maudire eux:
# python -m pip install forbiddenfruit
from forbiddenfruit import curse
from types import ModuleType
# curse has the same signature as setattr.
curse(ModuleType, "thing2", property(lambda module: f'hi from {module.__name__}'))
Cela nous donne une propriété qui existe sur tous les modules. C'est un peu compliqué, car nous cassons le comportement de réglage sur tous les modules:
import sys
print(sys.thing2)
# hi from sys
sys.thing2 = 5
# AttributeError: can't set attribute