3

What is the most "pythonic" way to create a class member which is an instance of that class? I.e. something like this:

class MyClass:

    # error! (and this is to be expected as MyClass is not yet fully defined)
    instance_as_member = MyClass()

    def __init__(self):
        print("MyClass instance created!")

One can solve this by adding a member after the class definition:

class MyClass:
    ...
MyClass.instance_as_member = MyClass()

but this seems a little wrong; naturally, class members should be defined in that class.

Is there a better way to do this?

Pika Supports Ukraine
  • 3,612
  • 10
  • 26
  • 42
RobertR
  • 745
  • 9
  • 27
  • 6
    Well, since the class *doesn't exist yet* inside the class body, you have to find a point **after** the class body has completed. – Martijn Pieters Jan 04 '19 at 19:34
  • Makes sense. So the proposed solution is the only way to do it I suppose. – RobertR Jan 04 '19 at 19:36
  • [Related](https://stackoverflow.com/questions/2035423/python-how-to-refer-to-the-class-from-within-it-like-the-recursive-function) – glibdud Jan 04 '19 at 19:37
  • 1
    @RobertR: there are [other](https://docs.python.org/3/reference/datamodel.html#object.__init_subclass__) [hooks](https://docs.python.org/3/reference/datamodel.html#metaclasses) [available](https://www.python.org/dev/peps/pep-3129/), but sometimes it is just simpler to just add it after the class has been created. – Martijn Pieters Jan 04 '19 at 19:41

3 Answers3

2

That is not "A little wrong" - it is the simple and obvious way to do that. One line of code, readable by everyone and obvious "what it is".

Still it does not "feel" elegant. So, if you don't want to do that just for the looks, and perhaps, if there are many classes like this, not repeating code, it could be done with a class decorator:

def instance_as_member(attr_name='instance_as_member', *init_args, **init_kwargs):
    def decorator(cls):
        instance = cls(*init_args, **init_kwargs)
        setattr(cls, attr_name, instance)
        return cls
    return decorator

@instance_as_member()
class MyClass:
    ...

Decorators are ok for that, as they get the cls to be modified as a parmater after it is fully created. With metaclasses, that would be tricky because some steps of class instantiating, like calling __init_subclass__ are performed after any explicit metaclass methods that can be overriden, so, one could have problems trying to create an instance inside a metaclass initialization method (__init__, __new__, or meta-meta-class' __call__)

jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • Neat idea using the decorator, but I think your first comment is accurate: it is probably better to just do the assignment after the class definition for sake of readability. – RobertR Jan 04 '19 at 21:08
0

It appears that you want a setUp type of functionality. Here's a "slightly dirty" way to do it from within the class: set a class-variable flag to denote the first use of the class. Check the flag on instantiation; if this is the first access, then clear the flag and run the set-up method.

class MyClass:

    first_touch = True

    def __init__(self, id=0):

        if MyClass.first_touch:
            MyClass.setUp(id)
        print("MyClass instance created!", id)

    @classmethod
    def setUp(self, id):
        MyClass.first_touch = False
        MyClass.instance_as_member = MyClass(-999)


print("Start")
local_obj = MyClass(1)
local_obj = MyClass(2)
local_obj = MyClass(3)
Prune
  • 76,765
  • 14
  • 60
  • 81
  • Same problem with match's answer: this would only work if the member instance is only ever accessed after another instance is created. Thanks for the suggestion though! – RobertR Jan 04 '19 at 21:04
0

While the class isn't defined within the body, it is defined when inside __init__

However, there's a problem that instantiating the class inside __init__ is recursive, so you need to track whether instance_as_member has been made yet.

Something like the following works:

class Foo:
    member_added = False
    instance_as_member = None

    def __init__(self):
        if not Foo.member_added:
            Foo.member_added = True
            Foo.instance_as_member = Foo()

print(Foo.instance_as_member)
Foo()
print(Foo.instance_as_member)

This results in:

None
<__main__.Foo object at 0x7fcd9f68f3c8>

(As to whether this is a good idea is an entirely different question!)

match
  • 10,388
  • 3
  • 23
  • 41
  • Yeah I thought about this too, but then the member instance is only available after another instance has been created. – RobertR Jan 04 '19 at 21:02