7

Ok so I need to create a bunch of utility classes in python. Normally I would just use a simple module for this but I need to be able to inherit in order to share common code between them. The common code needs to reference the state of the module using it so simple imports wouldn't work well. I don't like singletons, and classes that use the classmethod decorator do not have proper support for python properties.

One pattern I see used a lot is creating an internal python class prefixed with an underscore and creating a single instance which is then explicitly imported or set as the module itself. This is also used by fabric to create a common environment object (fabric.api.env).

I've realized another way to accomplish this would be with metaclasses. For example:

#util.py
class MetaFooBase(type):
    @property
    def file_path(cls):
        raise NotImplementedError

    def inherited_method(cls):
        print cls.file_path


#foo.py
from util import *
import env

class MetaFoo(MetaFooBase):
    @property
    def file_path(cls):
        return env.base_path + "relative/path"

    def another_class_method(cls):
        pass

class Foo(object): __metaclass__ = MetaFoo


#client.py
from foo import Foo

file_path = Foo.file_path

I like this approach better than the first pattern for a few reasons:

First, instantiating Foo would be meaningless as it has no attributes or methods, which insures this class acts like a true single interface utility, unlike the first pattern which relies on the underscore convention to dissuade client code from creating more instances of the internal class.

Second, sub-classing MetaFoo in a different module wouldn't be as awkward because I wouldn't be importing a class with an underscore which is inherently going against its private naming convention.

Third, this seems to be the closest approximation to a static class that exists in python, as all the meta code applies only to the class and not to its instances. This is shown by the common convention of using cls instead of self in the class methods. As well, the base class inherits from type instead of object which would prevent users from trying to use it as a base for other non-static classes. It's implementation as a static class is also apparent when using it by the naming convention Foo, as opposed to foo, which denotes a static class method is being used.

As much as I think this is a good fit, I feel that others might feel its not pythonic because its not a sanctioned use for metaclasses which should be avoided 99% of the time. I also find most python devs tend to shy away from metaclasses which might affect code reuse/maintainability. Is this code considered code smell in the python community? I ask because I'm creating a pypi package, and would like to do everything I can to increase adoption.

6
  • 6
    Why do you want a "utility class" to begin with? What's wrong with module-level functions and constants? I feel it is not idiomatic because it avoids a perfectly simple solution and adds a lot of complexity just to introduce a concept which reeks of another language, not because it uses metaclasses unconventionally (I do that myself).
    – user7043
    May 17, 2013 at 18:05
  • I think 99% of the time I agree with you on this, and I don't think this is a pattern I'd use often. I'm creating data file utility classes and I have the common code in a common separate module (load, save, etc). Load and save reference a class specific file path calculated at runtime and save/load the data in a class variable. I'm also using method decorators that automatically handles locking the file, loading it, calling the method, saving it, and unlocking it. This is very hard to do properly without classes. So its a bit motivated by convenience.
    – rsimp
    May 17, 2013 at 18:27
  • "classes that use the classmethod decorator do not have proper support for python properties": you can easily find a classproperty decorator on the web, or write one yourself. I use one very frequently.
    – shx2
    May 18, 2013 at 5:34
  • Yes this works well if you are only setting the getter for the property. It breaks down when you try to set the setter as shown here: stackoverflow.com/questions/128573/… . The best way to implement both the setter and getter for a class property is through the use of metaclasses which is actually what inspired this design. If you're going to go through the trouble to make a metaclass, you might as well put all of the class specific information inside of it.
    – rsimp
    May 18, 2013 at 6:23
  • 1
    It seems you want the functionality of singletons, but don't like singletons and therefore you use the most advanced Python features available to do the exact same thing as singletons do in a way that most people won't recognize as being a singleton. Jul 13, 2015 at 14:20

3 Answers 3

3

I think it is a bad idea.

Its a very odd use of meta classes which will contribute to confusion of anyone trying to follow your code. In order to make it worth using this technique, you need the advantages to outweigh that confusion.

As for your reasons:

  1. Creating an accesible Foo class that can be instantiated, but doesn't actually work like a typical object is much more prone to misuse than an internal class that you know you aren't supposed to call.
  2. Don't name the base class with an underscore. If its intended to be inherited by other modules, it shouldn't be named that way.
  3. There already is a method to implement static classes in python, staticmethod or classmethod. As noted, its not hard to implement a staticproperty

In general, there is a theme in your approach of trying to prevent client code from doing the wrong thing. That's not a pythonic attitude. The pythonic approach is to trust your client code. If they want to do something crazy, let them. Don't try to prevent client from instantiating your internal object, there may well be a good reason to do that.

I'm a bit of a purist and think that you shouldn't be storing state like this. So the very fact that you are asking the question means you're already doing it wrong. If you have state, I think it should be in an object. I think storing any sort of state at the module level should be avoided. I think its only a good idea to implement something like this if you have no changing state.

To close off, I'd like to plug the Code Review Stack Exchange, where I'm a moderator. If you post your code there you can get suggestions on how to make it better. I think you may get some helpful suggestions for how to restructure your code to avoid this question from even coming up.

0

Metaclasses are a powerful tool. Most of the time they are not needed, but it looks like you have solid reasons for doing so. I do not see your use of metaclasses, or lack thereof, affecting your package's adoption on PyPI.

0

If I really needed this level of configurability, I would build factory classes that paste the utility classes together and return them as objects.

The mechanism you first mention is quite good, apart from the assigning up to the module. You can just return them and store them in a normal namespace like a normal human. No need to bypass logical boundaries and make everyone search everywhere for your code.

So you can make code that looks like stereotypes of different aspects of your classes, and rely upon straightforward mix-in inheritance to compose your factory classes. This is more straightforward than metaclassing, which involves editing dictionaries of methods, etc, in ways that do not look like what they are -- code writing code inheriting from other code.

Metaclasses also get out of hand fast, in my experience.

And only one mechanism really gets to use them at a time in a whole code-base, unless everyone using them has been amazingly punctilious. So by using them here, you may be foreclosing a better, future use for them.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.