Class to help dealing with files, with lazy properties

I have the following class to help me deal with files:

File class:

# -*- coding: utf-8 -*-

"""
file
~~~~~~~~~~~~~~~~
"""

from mimetypes    import MimeTypes
from os           import path
from lazyproperty import lazyproperty

class File(object):
"""Class to help dealing with files.
"""

def __init__(self, path):
"""The constructor function.
"""
self.path = path

@lazyproperty
def basename(self):
"""Return last portion of the file.
"""
return path.basename(self.path)

@lazyproperty
def extension(self):
"""The extension name of the file, from the last occurrence of the
. (period) character to end of string.
"""
remaining, extension = path.splitext(self.path)
return extension

@lazyproperty
def encoding(self):
"""The encoding of the file, if no encoding is found, None will be
the default.
"""
return self.__mime_type_and_encoding[1]

@lazyproperty
def abspath(self):
"""The absolute path of the file.
"""
return path.abspath(self.path)

@lazyproperty
def mime_type(self):
"""The mime type associated with the file, if no mime type is found,
None will be the default.
"""
return self.__mime_type_and_encoding[0]

# @private
@lazyproperty
def __mime_type_and_encoding(self):
"""Guess the type of a file based on its filename or URL, given by url.
The return value is a tuple (type, encoding) where type is None if the
type can’t be guessed (missing or unknown suffix) or a string of the
form 'type/subtype', usable for a MIME content-type header.
"""
return MimeTypes().guess_type(self.abspath)


# -*- coding: utf-8 -*-

"""
lazyproperty
~~~~~~~~~~~~~~~~
"""

class lazyproperty(object):
"""Class to create lazy properties.
"""

def __init__(self, func):
"""The constructor function.
"""
self.func = func

def __get__(self, instance, cls):
if instance is None: return self
value = self.func(instance)
setattr(instance, self.func.__name__, value)
return value


I would like to improve the lazyproperties mime_type and encoding, this two parts:

return self.__mime_type_and_encoding[1]
return self.__mime_type_and_encoding[0]


Are making my sad, this magic numbers are very bad for maintenance. What i'm doing:

I'm using python 2.5 because i'm working in this legacy code, and the only way to get the mime type of file in this version is using the MimeTypes class:

mime_type = MimeTypes().guess_type("path/to/file")


Returns a tuple, -> ("mime_type", "encoding")

Which doesn't make any sense, since i'm asking for the mime type, not the encoding... any way... so, i created the __mime_type_and_encoding lazyproperty to avoid calling the MimeTypes() two times, in the encoding and mime_type.

Is it possible to avoid the magic numbers, and still do what i want?

1. In Python it's conventional to use a single underscore to indicate that a method is only for internal use (see PEP8). The double underscore is intended to solve a particular problem, namely avoiding conflict with a method of the same name in another class, when the classes are combined via inheritance. Since you don't have to worry about that problem here, you don't need the double underscore.

2. In Python 2.6 or later, a simple way to avoid magic numbers would be to make a collections.namedtuple:

from collections import namedtuple

MimeTypeAndEncoding = namedtuple('MimeTypeAndEncoding', 'type encoding')


and then in _mime_type_and_encoding write:

return MimeTypeAndEncoding(*MimeTypes().guess_type(self.abspath))


and in mime_type:

return self._mime_type_and_encoding.type


But this won't work for you because you are stuck on 2.5. But you could do much the same thing (if a bit more long-windedly) using a class:

class MimeTypeAndEncoding(object):
def __init__(self, type, encoding):
self.type = type
self.encoding = encoding

3. In Python 3.2 or later you could implement lazy_property like this, using functools.lru_cache:

from functools import lru_cache

def lazy_property(method):
"Decorator for a property that caches the result of the first call."
return property(lru_cache(1)(method))


(This won't work for you because you're stuck on Python 2.5, but I think it's neat.)