Can modules have properties the same way that objects can?

╄→尐↘猪︶ㄣ 提交于 2019-11-26 15:49:41
Alex Martelli

Only instances of new-style classes can have properties. You can make Python believe such an instance is a module by stashing it in sys.modules[thename] = theinstance. So, for example, your m.py module file could be:

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()
Unknown

I would do this in order to properly inherit all the attributes of a module, and be correctly identified by isinstance()

import types

class MyModule(types.ModuleType):
    @property
    def y(self):
        return 5


>>> a=MyModule("test")
>>> a
<module 'test' (built-in)>
>>> a.y
5

And then you can insert this into sys.modules:

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

A typical use case is: enriching a (huge) existing module with some (few) dynamic attributes - without turning all module stuff into a class layout. Unfortunately a most simple module class patch like sys.modules[__name__].__class__ = MyPropertyModule fails with TypeError: __class__ assignment: only for heap types. So module creation needs to be rewired.

This approach does it without Python import hooks, just by having some prolog on top of the module code:

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

Note: Something like from propertymodule import dynval will produce a frozen copy of course - corresponding to dynval = someobject.dynval

As PEP 562 has been implemented in Python >= 3.7, now we can do this

file: module.py

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

other = 4

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'

Note that if you omit the raise AttributeError in the __getattr__ function, it means the function ends with return None, then the module.nosuch will get a value of None.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!