Reputation: 129
I have the following class hierarchy:
class A(object):
def __init__(self, filename, x):
self.x = x
# initialize A from filename
# rest of A's methods
class B(A):
def __init__(self, filename):
super(B, self).__init__(filename, 10)
# rest of B's methods
Both classes are passed the name of a file in their __init__
methods. The file contents are then used to initialize some data in the objects.
To facilitate testing I'd like to be able to construct an A
or B
instance by passing it the data directly, rather than having the object read from a file. I thought that a pair of factory functions, each implemented using a classmethod
might work, but I've been unable to get the versions in B
to work at all. Here's what I have so far:
class A(object):
def __init__(self, x):
self.x = x
@classmethod
def construct(cls, filename, x):
a = cls(x)
# initialize a from filename
return a
@classmethod
def test_construct(cls, data, x):
a = cls(x)
# initialize a from data
return a
class B(A):
def __init__(self):
super(B, self).__init__(10)
@classmethod
def construct(cls, filename):
# should construct B from filename
@classmethod
def test_construct(cls, data):
# should construct B from data
Upvotes: 0
Views: 1166
Reputation: 95652
For the class methods in B to call their superclass class methods, you just use super()
as though you were calling an instance in a base class:
class B(A):
def __init__(self):
super(B, self).__init__(10)
@classmethod
def construct(cls, filename):
# should construct B from filename
return super(B, cls).construct(filename, 10)
Edit:
As you point out in your comment, there is then a problem because you've added an argument to the base class constructor. You should avoid making incompatible changes in method signatures between base and sub-classes: a B
instance is an A
instance so it should accept any method call you can make to the A
instance.
One option:
class B(A):
def __init__(self, x=10):
# if you're paranoid insert `assert x==10` here
super(B, self).__init__(x)
@classmethod
def construct(cls, filename):
# should construct B from filename
return super(B, cls).construct(filename, 10)
and now it works again, but you have to trust yourself not to pass 'x' to directly constructed instances of B
. Probably better would be to lose the x
entirely from __init__
which it looks like you could do by making it a class attribute or by setting it separately after construction.
Upvotes: 2
Reputation: 29302
the data at __init__
.
You can make that optional and use filename
in your program and data
in your test:
class A(object):
def __init__(self, x, filename=None, data=None):
if not any((filename, data)):
raise TypeError('either filename or data needs to be provided')
if all((filename, data)):
raise TypeError("both filename and data can't be provided")
self.x = x
if filename:
with open(filename, 'r') as f:
data = f.read() # just an example
Edit: Anyway if you want use special constructors method, this is how I would it:
class A(object):
def __init__(self, data, x):
self.x = x
self.data = data
@classmethod
def construct(cls, filename, x):
with open(filename, 'r') as f:
data = f.read()
return cls(data, x)
class B(A):
def __init__(self, data):
super(B, self).__init__(data, 10)
@classmethod
def construct(cls, filename):
with open(filename, 'r') as f:
data = f.read()
# modify data as you wish
return cls(data)
Where you call construct
in your program and __init__
in your test.
Upvotes: 1