I'm not sure what the proper way of doing this is but I have the following code:
class ToolKit(object):
def printname(self):
print self.name
class Test(ToolKit):
def __init__(self):
self.name = "Test"
b = Test()
b.printname()
My aim is to have an abstract base class of some sort that I can use as a toolkit for other classes. This toolkit class should not be instantiable. It should have abstract methods but other methods should be inherited and should not be implemented in child classes as they will be shared amongst the children. The following code is working. However, I'm using Pycharm and the "self.name" is causing this warning:
Unresolved attribute reference 'name' for class 'Toolkit'
I am wondering what the right way of doing this is. I've looked into ABC metaclass but haven't been able to make it work as I intend for two reasons. First, an abstract class can be instantiated if all the methods aren't abstract methods; I just want to make sure it can't be instantiated at all. Second, I'd like to have some methods that will be used as defaults (that don't need to be overwritten like printname) and I can't seem to figure out how to accomplish this.
Thanks in advance!
EDIT:
When I mean it works I mean it correctly prints "Test".
If you want to prevent your 'base' class to be instantiated while other classes can instantiate it and you don't want to use metaclasses, you can simply prevent it at the instance-creation level like:
class ToolKit(object):
def __new__(cls, *args, **kwargs):
assert cls is not ToolKit, "You cannot instantiate the base `ToolKit` class"
return super(ToolKit, cls).__new__(cls)
def printname(self):
print(self.name)
class Test(ToolKit):
def __init__(self):
self.name = "Test"
Now if you try to use it like:
b = Test()
b.printname()
Everything will be fine and it will print out Test
, but if you attempt to instantiate the ToolKit class, you'll get a different story:
a = ToolKit()
# AssertionError: You cannot instantiate the base `ToolKit` class
You can do a similar thing by forcing method to be implemented/overriden but it will quickly become hard to deal with so you might be better off to just use abc.ABCMeta
from the get go.
P.S. You might want to reconsider implementing patterns like these anyway. Instead of going out of your way to prevent your users from using your code in a way where you cannot guarantee its operation/correctness, you can just treat them as adults and write your intentions clearly in the documentation. That way if they decide to use your code a way it wasn't meant to, it would be their fault and you'd save a ton of time in the process.
UPDATE - If you want to enforce subclass definition of properties, there is a special @abc.abstractproperty
descriptor just for that - it's not ideal as it's not forcing subclasses to set a property but to override a property getter/setter, but you cannot have a descriptor around a variable.
You could at least enforce class-level variables (as in simple properties, without defined accessors) with something like:
class ToolKit(object):
__REQUIRED = ["id", "name"]
def __new__(cls, *args, **kwargs):
assert cls is not ToolKit, "You cannot instantiate the base `ToolKit` class"
for req in ToolKit.__REQUIRED:
assert hasattr(cls, req), "Missing a required property: `{}`".format(req)
return super(ToolKit, cls).__new__(cls)
def printname(self):
print("{} is alive!".format(self.name))
class Test1(ToolKit):
id = 1
name = "Test1"
class Test2(ToolKit):
name = "Test2"
class Test3(ToolKit):
id = 3
Now if you test instantiation of each of them:
for typ in [ToolKit, Test1, Test2, Test3]:
print("Attempting to instantiate `{}`...".format(typ.__name__))
try:
inst = typ()
inst.printname()
except AssertionError as e:
print("Instantiation failed: {}".format(e))
You'll get back:
Attempting to instantiate `ToolKit`... Instantiation failed: You cannot instantiate the base `ToolKit` class Attempting to instantiate `Test1`... Test1 is alive! Attempting to instantiate `Test2`... Instantiation failed: Missing a required property: `id` Attempting to instantiate `Test3`... Instantiation failed: Missing a required property: `name`
However, Python is a dynamic language so even if the instantiation-level check passes, the user can delete the property afterwards and it will cause printname
to raise an error due to a missing property. As I was saying, just treat the users of your code as adults and ask them to do the things you expect them to do in order for your code to function properly. It's much less of a hassle and you'd save a ton of time you can devote to improving the actual useful parts of the code instead of inventing ways to keep your users walled off from hurting themselves.