131

With python properties, I can make it such that

obj.y 

calls a function rather than just returning a value.

Is there a way to do this with modules? I have a case where I want

module.y 

to call a function, rather than just returning the value stored there.

martineau
  • 119,623
  • 25
  • 170
  • 301
Josh Gibson
  • 21,808
  • 28
  • 67
  • 63
  • 6
    See [`__getattr__` on a module](https://stackoverflow.com/q/2447353/674039) for a more modern solution. – wim May 17 '19 at 17:12

8 Answers8

75

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

demo:

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

EDIT

My answer cannot have setters and deleters. If you need, adopt kxr's answer.

Create a subclass of <class 'module'>, define properties in that class, then change module class into that class.

file: mymodule.py

import sys

class This(sys.__class__):  # sys.__class__ is <class 'module'>
    _y = 3

    @property
    def y(self):          # do the property things in this class
        return self._y

    @y.setter
    def y(self, value):   # setter is also OK
        self._y = value

other = 4

sys.modules[__name__].__class__ = This  # change module class into This

demo:

>>> import mymodule
>>> mymodule.y
3
>>> mymodule.other
4
>>> mymodule.y = 5
>>> mymodule.y
5
>>> mymodule._y
5    # to prove that setter works

I am too novice to know why it works. So the credit should go to kxr.

John Lin
  • 1,130
  • 10
  • 17
  • 2
    Based on this, I added another answer: https://stackoverflow.com/a/58526852/2124834 –  Oct 23 '19 at 15:59
  • 3
    This is only half of a property. No setter. – wim Feb 12 '20 at 02:50
  • 1
    Unfortunately it doesn't seems hard to make tooling aware of such attributes(?) (__getattr__ is only invoked if no regular member is found) – olejorgenb Feb 27 '20 at 08:51
  • @olejorgenb Try adding a typehint without initializing the variable. y: int (no =3 or anything like that). satisfies mypy in my project – sinback Oct 05 '21 at 17:14
62

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()
mari.mts
  • 683
  • 5
  • 9
Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • I would never have thought of doing that. I admit I'm unlikely to use it, but you never know. Always happy to learn about unexplored avenues in Python... thanks. – Jarret Hardie May 19 '09 at 01:14
  • 2
    Did anybody else try this? When I put this code in one file x.py and import it from another, then calling x.y results in AttributeError: 'NoneType' object has no attribute 'c', since _M somehow has value None... – Stephan202 May 19 '09 at 01:35
  • You must be mistyping something. Check an interactive session: $ python ... >>> import sys >>> class _M(object): ... c = 0 ... def afunction(self): ... _M.c += 1 ... return _M.c ... y = property(afunction) ... >>> sys.modules['bowwow'] = _M() >>> import bowwow >>> bowwow.y 1 >>> bowwow.y 2 >>> ...exactly as expected, of course. So, what's different in what you exactly are doing? – Alex Martelli May 19 '09 at 17:29
  • 3
    Indeed the code works on the interpreter. But when I put it in a file (say, bowwow.py) and I import it from another file (otherfile.py), then it no longer works... – Stephan202 May 19 '09 at 18:47
  • You're right -- I had coded it sloppily causing an unwonted dependence on globals (the code accessed _M, implicitly, from globals); now fixed, and thanks for pointing this out. – Alex Martelli May 20 '09 at 13:46
  • 6
    Q: Would there be any particular advantage(s) to deriving the instance's class from `types.ModuleType` as shown in @Unknown's otherwise very similar answer? – martineau May 24 '11 at 08:53
  • @AlexMartelli Can you help me understand the motivation behind this? I just found this answer by getting a reply [to my other question](http://stackoverflow.com/questions/7374748/whats-the-difference-between-a-python-property-and-attribute), and I'd love to understand why this is the case. It seem so hackish! – mvanveen Aug 01 '12 at 04:55
  • 16
    *Only instances of new-style classes can have properties.* This isn't the reason: modules *are* instances of new-style classes, in that they are instances of `builtins.module`, which itself is an instance of `type` (which is the definition of new-style class). The problem is that properties must be on the class, not the instance: if you do `f = Foo()`, `f.some_property = property(...)`, it'll fail in the same way as if you naïvely put it in a module. The solution is to put it in the class, but since you don't want all modules having the property, you subclass (see Unknown's answer). – Thanatos Apr 14 '14 at 18:58
  • 1
    @Thanatos: When I use either solution (Alex's or Unknown's), I am unable to refer to the class from within the class. For instance in afunction(self), "assert _M is not None" will raise an AssertionError. Why? – Joe Apr 15 '15 at 20:54
  • 3
    @Joe, the alteration of the `globals()` (keeping the keys intact but resetting the values to `None`) upon the name's rebinding in `sys.modules` is a Python 2 issue -- Python 3.4 works as intended. If you need access to the class object in Py2, add e.g `_M._cls = _M` right after the `class` statement (or stash it equivalently in some other namespace) and access it as `self._cls` in the methods requiring it (`type(self)` might be OK but not if you also do any subclassing of `_M`). – Alex Martelli Apr 17 '15 at 10:59
  • 1
    What if the original module is a package and I want to access modules underneath the original module? – kawing-chiu Jan 18 '18 at 02:55
  • 1
    @Stephan202: (I know this is a very belated.) I think `_M` got set to the value `None` because of a change made to the Python 2.7 interpreter. See the accepted answer to the related question [Why is the value of `__name__` changing after assignment to `sys.modules[__name__]`?](https://stackoverflow.com/questions/5365562/why-is-the-value-of-name-changing-after-assignment-to-sys-modules-name). – martineau May 01 '19 at 17:29
60

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
ankostis
  • 8,579
  • 3
  • 47
  • 61
Unknown
  • 45,913
  • 27
  • 138
  • 182
  • 1
    This seems to work only for the simplest of cases. Possible issues are: (1) some import helpers may also expect other attributes like `__file__` which have to be manually defined, (2) imports made in the module containing the class will not be "visible" during run time, etc... – tutuDajuju Nov 01 '16 at 09:41
  • 1
    It's not necessary to derive a subclass from `types.ModuleType`, _any_ (new-style) class will do. Exactly what special module attributes where you hoping to inherit? – martineau Nov 16 '16 at 23:43
  • What if the original module is a package and I want to access modules underneath the original module? – kawing-chiu Jan 18 '18 at 02:55
  • 2
    @martineau You'll have a module repr, you can specify the module name when `__init__` an instance, and you'll get correct behaviour when using `isinstance`. – wim May 17 '19 at 18:42
  • @wim: Points taken, although frankly none seem to be all that important IMO. – martineau Feb 12 '20 at 02:02
17

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.

  • Great! Compared with class property `@property def x(self): return self._x` I think `def thing()` without underscore is more conventional. And can you create a "module property setter" decorator in your answer as well? – John Lin Nov 15 '19 at 02:11
  • 3
    @JohnLin, I attempted to implement your `def thing()` suggestion. Problem is that `__getattr__` only gets called for [missing attributes](https://stackoverflow.com/questions/3278077). But after `@module_property def thing(): …` runs, `the_module.thing` is defined, so __getattr__ will never be called. We need to somehow register `thing` in the decorator and then delete it from the module's namespace. I tried returning `None` from the decorator, but then `thing` is defined as `None`. One could do `@module_property def thing(): … del thing` but I find that worse than using `thing()` as a function – Ben Mares Feb 07 '20 at 17:56
  • OK I see there's no "module property setter", nor "module `__getattribute__`". Thank you. – John Lin Feb 26 '20 at 07:40
8

Update Python 3

In Python 3, since 3.7 at least, the class of modules can be changed to a sub class, so real module properties (or descriptors) are now easy to implement - more solid and powerful than a PEP 562 module __getattr__ .

# mymodule.py

class ThisMod(sys.modules[__name__].__class__):
    y = property(lambda self: "Hi this is module %s." % __name__)
    const = property(lambda self: _const)  # block setting
sys.modules[__name__].__class__ = ThisMod

_const = 77

# rest of module code ...

Python 2 compatible

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

kxr
  • 4,841
  • 1
  • 49
  • 32
1

A short answer: use proxy_tools

The proxy_tools package attempts to provide @module_property functionality.

It installs with

pip install proxy_tools

Using a slight modification of @Marein's example, in the_module.py we put

from proxy_tools import module_property

@module_property
def thing():
    print(". ", end='')  # Prints ". " on each invocation
    return 'hello'

Now from another script, I can do

import the_module

print(the_module.thing)
# . hello

Unexpected behavior

This solution is not without caveats. Namely, the_module.thing is not a string! It is a proxy_tools.Proxy object whose special methods have been overridden so that it mimicks a string. Here are some basic tests which illustrate the 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']

Internally, the original function is stored to the_module.thing._Proxy__local:

print(res._Proxy__local)
# <function thing at 0x7f729c3bf680>

Further thoughts

Honestly, I'm baffled about why modules don't have this functionality built in. I think the crux of the matter is that the_module is an instance of the types.ModuleType class. Setting a "module property" amounts to setting a property on an instance of this class, rather than on the types.ModuleType class itself. For more details, see this answer.

We can actually implement properties on types.ModuleType as follows, although the results are not great. We can't directly modify built-in types, but we can curse them:

# 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__}'))

This gives us a property which exists over all modules. It's a bit unwieldly, since we break the setting behavior across all modules:

import sys

print(sys.thing2)
# hi from sys

sys.thing2 = 5
# AttributeError: can't set attribute
Ben Mares
  • 1,764
  • 1
  • 15
  • 26
  • 1
    How is this better than just making the module an instance of a real class as shown in @Alex Martelli's answer? – martineau Feb 11 '20 at 21:16
  • Good question @martineau. I personally don't find that answer helpful since module code must be drastically transformed to fit into a class due to the different scoping constructs. Each module has its own global namespace, while for classes you need `self` in order to access attributes. I actually [discuss it quite a lot here](https://stackoverflow.com/a/58841531/10155767). – Ben Mares Feb 11 '20 at 22:32
  • Ben: Sorry, I don't find what you said in that linked answer compelling because it can be done using @Alex's technique. See [this snippet](https://pastebin.com/fcVDPvJX) the illustrates this. In a nutshell, if you put the class in a separate module and make an instance of the class **be** the module when it's imported, then that instance can refer to scope of whatever else is in the module it's defined in, as well as what's in the class itself, plus _it can have properties just like any other class instance_. – martineau Feb 12 '20 at 01:41
  • My goal is to effortlessly and elegantly add module properties. Having to manually patch `sys.modules` doesn't meet that standard. After all, I'm just trying to save the pair of parens at the end of `my_module.some_global()`. In other words, for an answer to qualify as allowing properties, it should implement a `@module_property` decorator. Am I making sense? If we could implement Alex's solution as a decorator, that would be awesome. My answer above leaves much to be desired (as I pointed out in "unexpected behavior") – Ben Mares Feb 12 '20 at 07:46
  • martineau: more to the point, modules have many advantages over classes for the type of code that I write. I would effectively like to use equivalents of @property and @cached_property at the module level. Sure I can wrap a class around my module, but then I have the complication of dealing with two different namespaces which are not properly integrated. For instance in your snippet, at the end of `foobar.py`, I cannot even do `z = y + 1`. Instead I must write `z = _Foobar.y + 1`. But it's more concise to give up on properties, define y() directly in the module, and then do `z = y() + 1`. – Ben Mares Feb 13 '20 at 11:39
  • Sorry, I just realized that the example of my last comment technically doesn't make sense, because properties are *defined* in terms of attributes, and `z = y + 1` involves no attributes. So admittedly, a part of my frustration has nothing to do with properties. – Ben Mares Feb 13 '20 at 12:14
  • 1
    You've said something else that doesn't make sense to me. Take this business about having a `@module_property` decorator. Generally speaking, the built-in `@property` decorator is used when a class is defined, not after an instance of it has been created, so I would assume the same would be true for a module property and it is with Alex's answer — recall this question asks "Can modules have properties the same way that objects can?". However it _is_ possible to add them afterwards and I've modified my earlier [snippet](https://pastebin.com/fcVDPvJX) to illustrate a way that can be done. – martineau Feb 13 '20 at 16:14
  • martineau: Thanks for pushing me to make my idea more precise. I understand your point about adding decorators, but I mean something different by a "module-level equivalent" of the @property decorator. Sorry that I haven‘t yet articulated it properly, it‘s hard for me to fully explain myself in these character-restricted comments. Here is [a concrete example of what I have in mind](https://pastebin.com/GcrGJyJ5), with some explanation. Thanks for all the feedback! – Ben Mares Feb 14 '20 at 13:07
  • 1
    Ben: After looking over the code in your concrete example, I think I understand what you're getting at now. I also think I recently stumbled upon a technique to implement something akin to module properties that doesn't require the module to be replaced with a class instance like in Alex's answer, although I'm not sure at this point if there's a way of doing it via a decorator — will get back to you if I make any progress. – martineau Feb 15 '20 at 22:52
  • martineau: Thanks for your patience on this. I'm very glad that you understand! I would be very curious to know the rough idea of what you have in mind, even if you're not sure it will work. Based on your comments I'd like to rewrite parts of my answer. I'd also like to publish a GitHub repo with @module_property implementations (also including the non-decorator solution if it works out). Then I could write some tests and show all the ways the various implementations fail. – Ben Mares Feb 15 '20 at 23:27
  • 1
    OK, here's a link to an [answer](https://stackoverflow.com/questions/2447353/getattr-on-a-module/48916205#48916205) to another question which contains the core idea. – martineau Feb 15 '20 at 23:37
  • Sounds similar to the approach here of John Lin & Marein. I was experimenting along those lines, but got stuck because `__getattr__` only triggers for undefined attributes. I'm very interested to hear about anything you find. :) – Ben Mares Feb 16 '20 at 00:00
  • 1
    Well, at least in the case of a `cached_module_property`, the fact that `__getattr__()` will no longer be called if the attribute gets defined is helpful. (similar to what `functools.cached_property` accomplishes). – martineau Feb 16 '20 at 00:21
  • The one major advantage to this `proxy_tools` approach in my answer is that I can access the wrapped function as a module variable. (The seemingly impossible feature which I want.) Unfortunately it leads to the screwy lazy-type evaluation which I complained about. – Ben Mares Feb 16 '20 at 00:27
  • I don't think you understood what one can do with respect to using `__getattr__` that I was alluding to in my last comment. While I could try to explain things more, now is not a good time and this is not a good place for an extended discussion. Maybe you should ask your own question regarding this topic. – martineau Feb 16 '20 at 01:36
0

based on user2124834's answer:

import sys
class AttrGeter:
    def __new__(cls, gt):
        if isinstance(gt, cls):
            return gt
        else:
            o = super().__new__(cls)
            o.oldgetattr = gt
            o.funcmap = {}
            return o

    def __call__(self, name):
        name2 = "_" + name
        if name2 in self.funcmap:
            return self.funcmap[name2]()
        else:
            return self.oldgetattr(name)

    def add(self, func):
        self.funcmap[func.__name__] = func


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}'")
    ag = AttrGeter(getattr(module, '__getattr__', base_getattr))
    module.__getattr__ = ag
    ag.add(func)
    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'

I use a dict instead of nested function in original solution. That may be more efficient when use the decorator many times in one module.

guoyongzhi
  • 121
  • 1
  • 3
  • Suggest you rename the class `AttrGetter` which is closer to an English spelling. Regardless, have to prefix certain references with the underscore means the user of the class has to know which ones are properties and which ones aren't — which at least partially defeats a very important aspect of properties. – martineau Apr 10 '21 at 20:43
0

Google led me to this gist which provided a pair of decorators (mod_property, cached_mod_property) with simple implementations. I tried them and they worked for me.

I took the code from that gist, and some of the code from dickens, and combined it all into a single utility module with full demo here.

Manifest:

  • cache (for functions)
  • cached_property (for instance methods)
  • module_property
  • cached_module_property
  • class_property
  • cached_class_property
odigity
  • 7,568
  • 4
  • 37
  • 51