One common thing I've used a few times is using str.format
to create formatting metapatterns -- essentially a format pattern that becomes a format pattern when formatted. I'm not necessarily advocating for using this everywhere, but it's handy sometimes.
Let me provide two examples:
Arbitrary Padding
Part of a Python learning group gives homework, one of the "assignments" was to pad an input to an arbitrary amount with an arbitrary character. The "cannonical" example given was to pad a given number to five characters with zeroes. Formatting ahoy!
Keep in mind how closures work: When we call padder
we're actually creating an "instance" of pad
where the pattern is bound at the time of creation. The way this metapattern works is that {{}}
will actually output {}
when passed through formatting, so assuming defaults, the metapattern ends up looking like: {:0>5}
.
def padder(padwith=0, direction='>', length=5):
pattern = "{{:{}{}{}}}".format(padwith, direction, length)
def pad(input):
return pattern.format(input)
return pad
p = padder()
g = padder(padwith='*', direction='^', length='7')
print(p(11))
print(g('g'))
Generic repr
A more useful example is creating a generic __repr__
Mixin:
class ReprMixin:
'''Provides a string representible form for objects.'''
# Most models will have both an id and name fields
# This can be overwritten if needed
__repr_fields__ = ['id', 'name']
def __repr__(self):
fields = {f:getattr(self, f, '<BLANK>') for f in self.__repr_fields__}
# constructs a dictionary compatible pattern for formatting
# {{{0}}} becomes {id} for example
pattern = ['{0}={{{0}}}'.format(f) for f in self.__repr_fields__]
pattern = ' '.join(pattern)
pattern = pattern.format(**fields)
return '<{} {}>'.format(self.__class__.__name__, pattern)
I've been using this in OpenWebAmp's (nee FlaskAmp) models for easy recognition of model instances in the command line shell. Basically, all of my main objects have a id
and name
fields, but some don't and I need more information from others. Creating a generic __repr__
method isn't very intuitive given how much attributes in objects can vary. Essentially what this does is look for __repr_fields__
in an object (using sane defaults if not present) and then iterates through that creating a list of patterns like ['id={id}', 'name={name}']
and then joins them into a single string, then formats it and then formats that into another pattern. What pops out the other end is something like this: <Artist id=1 name=The Foo Bars>
.
But you can also override __repr_fields__
in a class to pull more or different information out of it.
class Artist(ReprMixin):
def __init__(self, id, name):
self.id = id
self.name = name
class Member(ReprMixin):
def __init__(self, id, name):
self.id = id
self.name = name
class Tag(ReprMixin):
def __init__(self, id, name):
self.id = id
self.name = name
class MemberTaggedArtist(ReprMixin):
__repr_fields__ = ['id', 'member', 'tag', 'artist']
# pretend there's relational bindings here
def __init__(self, id, member, tag, artist):
self.id = id
self.artist = artist
self.member = member
self.tag = tag
mta = MemberTaggedArtist(
id=4,
member=Member(id=1, name='anr'),
tag=Tag(id=1, name='Rock'),
artist=Artist(id=1, name='The Foo Bars')
)
repr(mta)
So metapatterns. They make your patterns a little unfun to read, but they're really useful. Don't stick 'em everywhere because they're awful to look at and terrible to debug if you screw something up.
No comments:
Post a Comment