CuriousGuy CuriousGuy - 2 months ago 17
Python Question

Parent constructor called by default?

I have the following example from python

page_object
docs:

from page_objects import PageObject, PageElement
from selenium import webdriver

class LoginPage(PageObject):
username = PageElement(id_='username')
password = PageElement(name='password')
login = PageElement(css='input[type="submit"]')

driver = webdriver.PhantomJS()
driver.get("http://example.com")
page = LoginPage(driver)
page.username = 'secret'
page.password = 'squirrel'
assert page.username.text == 'secret'
page.login.click()


What bothers me is that we create a
LoginPage
with providing a
driver
to it's constructor, but we haven't define a
__init__
method in
LoginPage
class.

Does that mean that the parent class
PageObject
's constructor is called with
driver
parameter? I thought that python doesn't implicitly call parent's constructors?

Answer

The __init__ method is just a method and as such python performs the same kind of lookup for it as other methods. If class B does not define a method/attribute x then python looks up it's base class A and so on, until it either finds the attribute/method or fails.

A simple example:

>>> class A:
...     def method(self):
...         print('A')
... 
>>> class B(A): pass
... 
>>> class C(B):
...     def method(self):
...         print('C')
... 
>>> a = A()
>>> b = B()
>>> c = C()
>>> a.method()
A
>>> b.method()  # doesn't find B.method, and so uses A.method
A
>>> c.method()
C

The same is with __init__: since LoginPage does not define __init__ python looks up the PageObject class and finds its definition there.

What is meant when we say that "python doesn't implicitly call parent class constructors" is that if you define an __init__ method the interpreter will just call that method and not call all the parent class __init__s, and as such if you want to call the parent class constructor you have to do so explicitly.

Note the difference among these classes:

>>> class A:
...     def __init__(self):
...         print('A')
... 
>>> class B(A):
...     pass
... 
>>> class B2(A):
...     def __init__(self):
...         print('B')
... 
>>> class B3(A):
...     def __init__(self):
...         print('B3')
...         super().__init__()
... 
>>> A()
A
<__main__.A object at 0x7f5193267eb8>
>>> B()  # B.__init__ does not exists, uses A.__init__
A
<__main__.B object at 0x7f5193267ef0>
>>> B2()  # B2.__init__ exists, no call to A.__init__
B
<__main__.B2 object at 0x7f5193267eb8>
>>> B3()  # B3.__init__exists, and calls to A.__init__ too
B3
A
<__main__.B3 object at 0x7f5193267ef0>
Comments