Python __repr__ and None
Asked Answered
U

6

5

I'm quite new to Python and currently I need to have a __repr__ for a SqlAlchemy class. I have an integer column that can accept Null value and SqlAlchemy converts it to None. For example:

class Stats(Base):
   __tablename__ = "stats"
   description = Column(String(2000))
   mystat = Column(Integer, nullable=True)

What is the correct way to represent the "mystat" field in the __repr__ function when SqlAlchemy returns None?

Uncanny answered 13/10, 2011 at 15:32 Comment(3)
The __repr__() just returns a string representation of the python object. I'm not sure that there is a "correct" way to implement it.Flunky
You do understand that in your code example you are creating class attributes, not instance attributes, right?Sympathizer
@KarlKnechtel Now that I searched online I know, thank you. That's why I wrote that I'm new to Python :)Uncanny
J
12

The __repr__ should return a string that describes the object. If possible, it should be a valid Python expression that evaluates to an equal object. This is true for built-in types like int or str:

>>> x = 'foo'
>>> eval(repr(x)) == x
True

If that's not possible, it should be a '<...>' string uniquely describing the object. The default __repr__ is an example of this:

>>> class Foo(object):
        pass
>>>
>>> repr(Foo())
'<__main__.Foo object at 0x02A74E50>'

It uses the object's address in memory to uniquely identify it. Of course address doesn't tell us much about the object so it's useful to override __repr__ and return a string describing the object's state.

The object's state is defined by other objects it contains so it makes sense to include their repr in yours. This is exactly what list or dict do:

>>> repr(['bar', Foo()])
"['bar', <__main__.Foo object at 0x02A74710>]"

In your case, the state is in your Column properties so you want to use their repr. You can use the %r formatting for this, it inserts a repr() of the argument:

def __repr__(self):
    return '<Stats: description=%r, mystat=%r>' % (self.description, self.mystat)

An equivalent using the new formatting:

def __repr__(self):
    return '<Stats: description={0.description!r}, mystat={0.mystat!r}>'.format(self)
Jerome answered 13/10, 2011 at 15:55 Comment(1)
Wow, thanks. I've missed the angle brackets (i.e. < ... >) convention for repr, if we cannot return "code which creates the object back". Also note that str falls back to repr.Bohman
U
8

I was trying to find a generic __repr__ method that could be used on any SQLAlchemy object and only found this page. So, I decided to write my own and here's what I've done:

from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()

if __debug__:
    # monkey-patch in useful repr() for all objects, but only in dev
    def tablerepr(self):
        return "<{}({})>".format(
            self.__class__.__name__,
            ', '.join(
                ["{}={}".format(k, repr(self.__dict__[k]))
                    for k in sorted(self.__dict__.keys())
                    if k[0] != '_']
            )
        )
    Base.__repr__ = tablerepr

This extends the Base class to print out the contents of the particular instance. So, now every object I create that extends the Base will have a __repr__ method that prints more than just the class name and the instance hash.

EDIT: I added an if __debug__ as this change may cause a leak of information you may not want leaked in a production environment. I also added a sorted so the display will be consistent.

Universalism answered 10/4, 2013 at 15:8 Comment(0)
H
4

Isn't the generic_repr decorator of sqlalchemy-utils providing a community based solution that suit your needs?

It leaves it as None.

Hayman answered 22/5, 2018 at 16:30 Comment(1)
This is the correct answer. All the other answers unfortunately pre-date this solution, but this is what people should be using now. However, it might be beneficial to wrap that decorator in something that checks for a dev environment to prevent leaking of data in production.Universalism
C
0

maybe repr(mystat) is what you want?

Cowbird answered 13/10, 2011 at 15:36 Comment(0)
P
0

'Null' if mystat is None else mystat

Provocation answered 13/10, 2011 at 15:40 Comment(0)
C
0

The previous answer showing how to override Base.__repr__ was exactly what I needed. Thank you! Here it is re-written with f-strings for Python 3.7+ and overriding the flask-sqlalchemy db.Model.

def override_default_repr(self):
    class_name = self.__class__.__name__
    attribs = ", ".join(
        [
            f"{k}={self.__dict__[k]!r}"
            for k in sorted(self.__dict__.keys())
            if k[0] != "_"
        ]
    )
    return f"<{class_name}({attribs})>"

db.Model.__repr__ = override_default_repr
Catcher answered 4/6, 2020 at 3:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.