6

Is it possible to add properties and special methods to modules? I want to define a module such that importing it acts like a class instance, and the body acts as a class definition. Essentially, it's to avoid ugly syntax like this:

import game
if game.Game().paused:
    print("The game is paused")

E.g. the game module would look like this:

_Speed = 1

@property
def paused():
    return _Speed == 0

And the file using it:

import game
if game.paused:
    print("The game is paused")

Also, is it possible to define special methods (such as __call__)?

To be clear, I do not differentiate between class/instance methods, since I'm using game.Game as a singleton/borg class.

I have tested using @property and defining __bool__, but neither acts as I hoped.

Edit (information on why I want to use a property):

I have a property game.speed, a function game.paused() and a function game.pause(bool). Essentially, I have a temporary variable used to store the game speed when the game is paused. There is a private speed variable that is set to zero when the game is paused. I never want the user to see the speed as being zero, and be able to modify the speed while the game is paused, so that when the game is resumed, it uses the new speed.

Casey Kuball
  • 7,717
  • 5
  • 38
  • 70
  • 2
    Why not just use `from game import Game`? – Adam Mihalcin Mar 30 '12 at 05:09
  • How would the properties work? A metaclass? How would the metaclass refer to the Game class? – Casey Kuball Mar 30 '12 at 05:11
  • 2
    I'm saying that if you use `from ... import` syntax, you *don't need* properties on the module. People who use your module won't expect there to be properties on it, so if you drop the properties from the module and simply use `form ... import` in your own code, you get the same readability but your API will better conform to other programmers' expectations, which is a Good Thing. – Adam Mihalcin Mar 30 '12 at 05:14
  • I updated my post with more information. I have values that are calculated, but should act as properties (as opposed to a function). – Casey Kuball Mar 30 '12 at 05:30
  • 2
    Related: http://stackoverflow.com/q/880530/936083 -- Might this work if I add methods directly to the module object? – Casey Kuball Mar 30 '12 at 05:33

5 Answers5

13

Python doesn't care that what's in sys.modules is actually a module. So you can just:

# game.py
class Game(object):
    pass

import sys
sys.modules["game"] = Game()

Now other modules that import game will get the Game instance, not the original module.

I'm not sure I recommend it, but it'll do what you want.

kindall
  • 178,883
  • 35
  • 278
  • 309
  • 5
    Whoa. That's some black magic. – bukzor Mar 30 '12 at 06:09
  • 1
    It makes a certain kind of sense. A module, after all, is itself an instance of a class (`types.ModuleType`). You could actually write your `Game` class as a subclass of the module type, instantiate it, and then you'd have something that is basically a module. But as I said, Python doesn't actually check the type. – kindall Mar 30 '12 at 06:17
3

It seems you want to avoid accessing items via the module. That's fairly easy to do.

These two are equivalent:

import game
if game.Game().paused:
    print("The game is paused")

from game import Game
if Game().paused:
    print("The game is paused")

Ok then, how about this:

# game.py

class Game(object):
    @property
    def paused():
        return True

game = Game()

# from your module
from game import game
game.paused
Josh Smeaton
  • 47,939
  • 24
  • 129
  • 164
  • This syntax is ugly. It requires me to write a class to use a singleton/borg pattern, for the methods to refer to members as either `self.*` or `Game.*`, and (for borg pattern) to use an initializer hack (to avoid re-setting members every call to `__init__`). – Casey Kuball Mar 30 '12 at 05:28
  • In response to your second one, it was something I was trying to do at first, but turned out to be problematic when I had game require one module, and that module then require the class instance from game. It gives you an error like so: `ImportError: cannot import name game` – Casey Kuball Mar 30 '12 at 05:50
  • @Darthfett circular dependency? Or am I misunderstanding you? game imports X and X imports game? – Josh Smeaton Mar 30 '12 at 05:53
  • In Python we "are consenting adults". `Game` isn't part of your interface here; the `game` instance is, so in the Pythonic view, there's no need for instantiation-control "patterns". – Karl Knechtel Mar 30 '12 at 05:55
  • @Josh Smeaton yup, I couldn't remember that name. At the time I actually use stuff from game, everything should already be fully defined, so it's just a problem if I want to import a global. – Casey Kuball Mar 30 '12 at 05:56
  • @Karl Knechtel my first comment was only written about the first part. The second was added in an edit. – Casey Kuball Mar 30 '12 at 05:57
  • That second example is great. Basically the same benefits of the module having a property and no messing with Python internals. If you really want to indicate to users of the module that the `game` variable is the only thing they should import, you can prefix other items with an underscore; that will also filter them from `*` imports. – jpmc26 Feb 13 '14 at 07:28
  • @jpmc26 you could also just define the `__all__` property in the module, so that only `game` is available. – Josh Smeaton Feb 14 '14 at 10:11
3

If all you are looking for is the syntax as you mentioned, then you can define a class and use class level attributes

a1.py

class Game(object):
    paused = True


>>> from a1 import Game
>>> Game.paused
True
>>> Game.paused = False
>>> Game.paused
False

Well, as you asked about the Properties on Class, then you can do something with property decorator and classmethod. Soemthing like this

class ClassProperty(property):
    def __get__(self, cls, owner):
        return self.fget.__get__(None, owner)()

class Game(object):
    stage = True
    @ClassProperty
    @classmethod
    def paused(cls):
        return Game.stage == True

>>> from a1 import Game
>>> Game.paused
True
Community
  • 1
  • 1
Senthil Kumaran
  • 54,681
  • 14
  • 94
  • 131
  • The problem lies in using a property. I want `paused` to be calculated, as it is never stored. – Casey Kuball Mar 30 '12 at 05:23
  • 1
    According to the answer you linked, it doesn't work for setters. I tried to give an [SSCCE](http://sscce.org/) which is why I didn't mention this, but I do also need setters for other members of game. – Casey Kuball Mar 30 '12 at 05:43
  • Is there a reason you can't define the class with instance properties, define an instance of it as a global variable in the module, and `from... import` *that*? – Karl Knechtel Mar 30 '12 at 05:54
  • Yeah, setters may not be possible in that way.How about using the Class variable for setting the state? Ofcourse, it is not symmetric and I would say your method of instantiating would be cleaner. – Senthil Kumaran Mar 30 '12 at 05:55
  • @Karl Knechtel: as Josh Smeaton mentioned in the comments of his answer, I have circular dependencies. `game` is sort of a glue class, where it calls other modules' methods which in return need information from `game`. – Casey Kuball Mar 30 '12 at 06:00
0

good answer here:

http://jtushman.github.io/blog/2014/05/02/module-properties/

Use of proxy pattern to implement module wide property. Elegant.

class Proxy(object):
    def __init__(self, local):
        self.local = local
    def __getattr__(self, name):
        return getattr(self.local(), name)

# aliasing for better syntax        
module_property = Proxy

class User(object):
    """Contrived User Object"""
    def __init__(self, **kwargs):
        self.name = kwargs.get('name', 'billy')
    def speak(self):
        print("Well hello there!")
    def say_hi(self, to_whom):
        print("Hi there {}".format(to_whom))

@module_property
def current_user():
  return User()

Then it can be used as property:

from login_manager import current_user, User

if __name__ == '__main__':
    print current_user.name
    current_user.speak()
    current_user.say_hi('lauren')

Article also mentions installable package (werkzerg project) extending this concept.

A. Dupre
  • 11
  • 1
0

I don't think this is going to work. You'll need to import the module more than once and Python will execute its contents more than once. And that will reset the speed variable again. I think you're better off using a single Game class that stores the game's state. It will also keep your code a lot clearer and easier to follow.

Simeon Visser
  • 118,920
  • 18
  • 185
  • 180
  • I want the module to be executed once, the problem I'm having is creating a property interface for `paused`. – Casey Kuball Mar 30 '12 at 05:24
  • That's why your approach is not going to work. You'll need a class with properties to store or compute the state – Simeon Visser Mar 30 '12 at 05:37
  • I'm not against having a class, if I don't have to use it as a singleton/borg, and create an instance every time I want to use a value. – Casey Kuball Mar 30 '12 at 05:52