109 votes

Les modules peuvent-ils avoir des propriétés de la même manière que les objets ?

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 faire cela 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.

3 votes

Ver __getattr__ sur un module pour une solution plus moderne.

60voto

John Lin Points 133

Como PEP 562 a été implémenté dans Python >= 3.7, maintenant nous pouvons faire ceci

fichier : module.py

def __getattr__(name):
    if name == 'y':
        return 3
    raise AttributeError(f"module '{__name__}' has no attribute '{name}'")

other = 4

l'usage :

>>> 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 l'option raise AttributeError en el __getattr__ cela signifie que la fonction se termine par return None alors le module.nosuch obtiendra une valeur de None .

60voto

Alex Martelli Points 330805

Seules les instances des classes de style nouveau peuvent avoir des propriétés. Vous pouvez faire croire à Python qu'une telle instance est un module en la plaçant dans le répertoire sys.modules[thename] = theinstance . Ainsi, par exemple, votre fichier 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()

0 votes

Je n'aurais jamais pensé à faire ça. J'admets qu'il est peu probable que je l'utilise, mais on ne sait jamais. Je suis toujours heureux d'apprendre des choses inexplorées en Python... merci.

2 votes

Quelqu'un d'autre a essayé ? Lorsque je place ce code dans un fichier x.py et que je l'importe d'un autre, l'appel de x.y donne lieu à AttributeError : 'NoneType' object has no attribute 'c', since _M somehow has value None...

0 votes

Vous devez faire une erreur de frappe. Vérifiez une session interactive : $ python ... >>> import sys >>> class _M(object) : ... c = 0 ... def afunction(self) : ... _M.c += 1 ... return _M.c ... y = propriété(afunction) ... >>> sys.modules['bowwow'] = _M() >>> import bowwow >>> bowwow.y 1 >>> bowwow.y 2 >>> ...exactement comme prévu, bien sûr. Alors, qu'est-ce qui est différent dans ce que vous faites exactement ?

58voto

Unknown Points 22789

Je ferais cela 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 ensuite vous pouvez insérer ceci dans sys.modules :

sys.modules[__name__] = MyModule(__name__)  # remember to instantiate the class

0 votes

Cela ne semble fonctionner que dans les cas les plus simples. Les problèmes possibles sont les suivants : (1) certaines aides à l'importation peuvent également s'attendre à d'autres attributs tels que __file__ qui doivent être définis manuellement, (2) les importations effectuées dans le module contenant la classe ne seront pas "visibles" pendant l'exécution, etc...

1 votes

Il n'est pas nécessaire de dériver une sous-classe à partir de types.ModuleType , tout (nouveau style) fera l'affaire. Quels sont exactement les attributs spéciaux du module dont vous espérez hériter ?

0 votes

Que se passe-t-il si le module d'origine est un paquet et que je souhaite accéder aux modules situés sous le module d'origine ?

14voto

Sur la base de La 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 trait de soulignement en tête), en the_module.py :

@module_property
def _thing():
    return 'hello'

Ensuite :

import the_module

print(the_module.thing)  # prints 'hello'

Le trait de soulignement est nécessaire pour différencier la fonction de propriété de la fonction d'origine. Je n'ai pas trouvé de moyen de réaffecter l'identifiant, car au moment 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.

5voto

kxr Points 31

Un cas d'utilisation typique est l'enrichissement d'un (énorme) module existant avec quelques (rares) attributs dynamiques - sans transformer tout le contenu du module en une classe. Malheureusement, un patch de classe de module très 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 permet de le faire sans les crochets d'importation Python, simplement 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)

Utilisation :

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

Note : Quelque chose comme from propertymodule import dynval produira une copie gelée de cours - correspondant à dynval = someobject.dynval

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X