Module property provides the default value for a module. Multiple process properties can source their value from a single module property. Module properties are defined at the module level and can be referenced by various resources that are defined as a part of the module.
In Python, modules are objects (values) and are handled like other objects. Thus, you can pass a module as an argument in a call to a function. Similarly, a function can return a module as the result of a call.
In programming, function refers to a segment that groups code to perform a specific task. A module is a software component or part of a program that contains one or more routines. That means, functions are groups of code, and modules are groups of classes and functions.
Modules can import each other cyclically, but there's a catch. In the simple case, it should work by moving the import statements to the bottom of the file or not using the from syntax.
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()
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
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
.
Based on John Lin's answer:
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
Usage (note the leading underscore), in the_module.py
:
@module_property
def _thing():
return 'hello'
Then:
import the_module
print(the_module.thing) # prints 'hello'
The leading underscore is necessary to differentiate the property-ized function from the original function. I couldn't think of a way to reassign the identifier, since during the time of the decorator execution, it has not been assigned yet.
Note that IDEs won't know that the property exists and will show red wavies.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With