I can do this:
class Person:
def __init__(self, firstName, lastName):
self.firstName = firstName
self.lastName = lastName
def __str__(self):
print self.firstName
bob = Person('Robert', 'Smith')
print bob
>>> Robert
But I want to do it more generically rather than overriding __str__
for every class manually. I've tried using decorators and metaclasses, but I am confounded. I think that it should be possible to do something similar to this, but I don't know how to make the decorator or metaclass accept an argument and still create a properly instantiable object:
class strAs:
def __init__(self, prop):
self.prop = prop
def __call__(self, cls):
decoratorSelf = self
def wrapper(*args, **kwargs):
def __str__(s):
return getattr(s, decoratorSelf.prop)
c = cls(*args, **kwargs)
c.__str__ = __str__
return c
return wrapper
@strAs(prop='firstName')
class Person:
def __init__(self, firstName, lastName):
self.firstName = firstName
self.lastName = lastName
@strAs(prop='name')
class Dog:
def __init__(self, name):
self.name = name
bob = Person('Robert', 'Smith')
fido = Dog('Fido')
print bob
print fido
>>> Robert
Fido
But this fails with:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/tmp/py6495xVN", line 33, in <module>
print bob
TypeError: __str__() takes exactly 1 argument (0 given)
I can't figure out why __str__()
is not getting self
passed to it. I noticed that the bob
instance of the Person
class seems to be missing some typical object methods, so I tried I subclassing object
like this (and added two print statements):
class strAs(object):
def __init__(self, prop):
self.prop = prop
def __call__(self, cls):
decoratorSelf = self
def wrapper(*args, **kwargs):
print 'in wrapper'
def __newstr__(s):
print 'in newstr'
return getattr(s, decoratorSelf.prop)
c = cls(*args, **kwargs)
c.__str__ = __newstr__
return c
return wrapper
@strAs(prop='firstName')
class Person(object):
def __init__(self, firstName, lastName):
self.firstName = firstName
self.lastName = lastName
@strAs(prop='name')
class Dog(object):
def __init__(self, name):
self.name = name
bob = Person('Robert', 'Smith')
fido = Dog('Fido')
print bob
print fido
And now I get some output with __str__
apparently being called correctly:
>>> in wrapper
in wrapper
<__main__.Person object at 0x7f179389e450>
<__main__.Dog object at 0x7f179389e510>
But it's not printing the property as it should be, and it doesn't appear to even be using the __newstr__
function, because it's not printing in newstr
. However, if I check bob.__str__
, I see that it is indeed <function __newstr__ at 0x7f17951d78c0>
.
I must be missing something obvious, but I feel like this is over my head right now. :/