1

I started off learning programming/OOP in PHP. To the best of my knowledge of best practices in PHP, you can instantiate a class without parenthesis if it does not take any arguments.

Such as

$class = new Class;

As opposed to:

$class = new Class();

I am starting to expand my skills into python and wasted about 5 hours yesterday trying to figure out why a function wouldn't pass an argument even though it was ridiculously simple. My Code:

class MainViewWidgets(MainViewContainer):

    def __init__(self):
        # instantiating like this prevents MainViewController.getHeaderItems from returning the arg passed to it, however the code still "works" in some sense
        self.controller = MainViewController 

        #this works
        self.controller = MainViewController()

    def createHeaderOptionCheckbox(self, pane):
        self.header_string = StringVar()
        header_checkbox = ttk.Checkbutton(pane, text='Data Contains Headers', variable=self.header_string, onvalue='headers', offvalue='keys')
        self.header_string.trace('w', self.headerOptionCheckboxChanged)
        return header_checkbox

    def headerOptionCheckboxChanged(self, *args):
        print(self.header_string.get())
        #will print "headers" or "keys" on checkbox toggle
        print(self.controller.getHeaderItems(self.header_string.get()))
        #prints "default"

class MainViewController:

    def __init__(self):
        self.CheckFile = CheckFile()
        get_config = GetConfiguration('config.ini')
        self.config_file = get_config.getProperty('directory', 'input_file')
        self.csv = CSVReader(self.config_file)
        self.chosen_index = None

    def getHeaderItems(self, header='default'):
        return header

Can someone please help me understand why in Python you need to instantiate a class with parenthesis even if there are no constructor arguments other than self. Also, why did the MainViewController still kind of work, but it did not behave as I wanted it to? As in it was loaded, and the functions "did things", but it would not seem to accept arguments. Is there any advantages of instantiating a class without its parenthesis?

Please note, I do not need help getting this code to work, I just want to understand why this happens.

BartoszKP
  • 34,786
  • 15
  • 102
  • 130
Tom Ford
  • 176
  • 10

2 Answers2

5

Can someone please help me understand why in Python you need to instantiate a class with parenthesis even if there are no constructor arguments other than self.

The reason is simple: when you instantiate an object, you are actually calling its class (which is itself an object), and you call objects using ().

In python, everything is a first-class object, even classes (and functions!) themselves. In order for a class to be a first class object, it follows that the class needs its own class (metaclass) to define its behavior. We call the class of a class "metaclass" so as to avoid confusion when talking about classes and classes of classes.

To answer the second part of your question: "things" were happening when you used MainViewController instead of MainViewController() because MainViewController is a full-fledged object, just like any other object.

So you might ask: what is the class - actually the metaclass - of the MainViewController object?


As you know, you can create a class like this:

class MyClass: 
    pass

When you do this, you are in actuality creating a new instance of the metaclass known as type.

Note that you can create the same class this way; there is literally no difference between the below and the above:

MyClass = type('MyClass', (object,), {}) 

The type metaclass is the base metaclass of all classes. All python "new style classes" (not so "new" anymore since they were implemented in python 2.1, I believe) are of the class type:

print(type(MyClass)) # type
print(type(list)) # type
print(type(int)) # type
# Note that above, type is being used as a "function" (it's really just a callable)

Interestingly enough, type is even its own metaclass:

print(type(type)) # type

So to reiterate: the class MyClass is actually an instantiation of type. It follows, then, that calling the class results in running the __call__ method of its metaclass.

When you do:

obj = MyClass()

...you are calling MyClass, which results (in the background) in running the method type.__call__().

This is the case with all user defined classes, btw; if you include the __call__ method in your class, your class is callable, and the __call__ method is executed when you call class instances:

class MyCallable():
    def __call__(self):
        print("You rang?") 

my_instance = MyCallable()
my_instance() # You rang?

You can see this in action. If you create your own metaclass by subclassing type, you can cause things to happen when an instance of the class based on your custom metaclass is created. For example:

class MyMeta(type):
    def __call__(self, *args, **kwargs):
        print "call: {} {} {}".format(self, args, kwargs)
        return super().__call__(*args, **kwargs)

# Python 3: 

class MyClass(metaclass = MyMeta):
    pass

# Python 2: 

class MyClass():
    __metaclass__ = MyMeta
    pass

Now when you do MyClass(), you can see that the __call__ method of MyMeta happens before anything else (including before __new__ AND before __init__).

Community
  • 1
  • 1
Rick
  • 43,029
  • 15
  • 76
  • 119
2

Because function calls require (). When you do MyClass(), you are calling MyClass. The expression MyClass evaluates to the class itself, which is an object.

Marcin
  • 48,559
  • 18
  • 128
  • 201