Search code examples
pythonstringpython-3.xstring-formatting

Left truncate using python 3.5 str.format?


Q: Is is possible to create a format string using Python 3.5's string formatting syntax to left truncate?

Basically what I want to do is take a git SHA:

"c1e33f6717b9d0125b53688d315aff9cf8dd9977"

And using only a format string, get the display only the right 8 chars:

"f8dd9977"

Things Ive tried:

Invalid Syntax

>>> "{foo[-8:]}".format(foo="c1e33f6717b9d0125b53688d315aff9cf8dd9977")
>>> "{foo[-8]}".format(foo="c1e33f6717b9d0125b53688d315aff9cf8dd9977")  
>>> "{:8.-8}".format("c1e33f6717b9d0125b53688d315aff9cf8dd9977")

Wrong Result

### Results in first 8 not last 8. 
>>> "{:8.8}".format("c1e33f6717b9d0125b53688d315aff9cf8dd9977")

Works but inflexible and cumbersome

### solution requires that bar is always length of 40.
>>> bar="c1e33f6717b9d0125b53688d315aff9cf8dd9977"
>>> "{foo[32]}{foo[33]}{foo[34]}{foo[35]}{foo[36]}{foo[37]}{foo[38]}{foo[39]}".format(foo=bar)

A similar question was asked, but never answered. However mine differs in that I am limited to using only format string, I don't have the ability to change the range of the input param. This means that the following is an unacceptable solution:

>>> bar="c1e33f6717b9d0125b53688d315aff9cf8dd9977"
>>> "{0}".format(bar[-8:])

One more aspect I should clarify... the above explains the simplest form of the problem. In actual context, the problem is expressed more correctly as:

>>> import os
>>> "foo {git_sha}".format(**os.environ)

Where I want to left_truncate "git_sha" environment variable. Admittedly this is a tad more complex than simplest form, but if I can solve the simplest - I can find a way to solve the more complex.


Solution

  • Subclassing str and overriding the __format__ method is an option:

    class CustomStr(str):
        def __format__(self, spec):
            if spec == 'trunc_left':
                return self[-8:]
            else:
                return super().__format__(spec)
    
    git_sha = 'c1e33f6717b9d0125b53688d315aff9cf8dd9977'
    s = CustomStr(git_sha)
    
    print('{:trunc_left}'.format(s))
    

    Better though, you can create a custom Formatter which inherits from string.Formatter and will provide a format method. By doing this, you can override a number of methods used in the process of formatting strings. In your case, you want to override format_field:

    from string import Formatter
    
    class CustomFormatter(Formatter):
            
         def format_field(self, value, format_spec):
             if format_spec.startswith('trunc_left.'):
                 char_number = int(format_spec[len('trunc_left.'):])
                 return value[-char_number:]
             return super().format_field(value, format_spec)
    
    environ = {'git_sha': 'c1e33f6717b9d0125b53688d315aff9cf8dd9977'}
    fmt = CustomFormatter()
    print(fmt.format('{git_sha:trunc_left.8}', **environ))
    

    Depending on the usage, you could put this in a context manager and temporarily shadow the builtin format function:

    from string import Formatter
    
    class CustomFormat:
        
        class CustomFormatter(Formatter):
            
            def format_field(self, value, format_spec):
                if format_spec.startswith('trunc_left.'):
                    char_number = int(format_spec[len('trunc_left.'):])
                    return value[-char_number:]
                return super().format_field(value, format_spec)
                
        def __init__(self):
            self.custom_formatter = self.CustomFormatter()
                
        def __enter__(self):
            self.builtin_format = format
            return self.custom_formatter.format
            
        def __exit__(self, exc_type, exc_value, traceback):
            # make sure global format is set back to the original
            global format
            format = self.builtin_format
    
        
    environ = {'git_sha': 'c1e33f6717b9d0125b53688d315aff9cf8dd9977'}
    
    with CustomFormat() as format:
        # Inside this context, format is our custom formatter's method
        print(format('{git_sha:trunc_left.8}', **environ))
    
    print(format)  # checking that format is now the builtin function