Home > other >  How to Reinitialize Variables in a Class in Python
How to Reinitialize Variables in a Class in Python

Time:07-27

I created a test using Selenium with Python to use locators from a class.

Test:

def testPage(driver, url):
    login(driver, url)
    activateProfile(driver, url)

    driver.get(my_url)
    assert_true(driver, *Common.user_lbl)
    assert_true(driver, *Common.pass_lbl)
    .
    .

Class:

class Common():
    locator = namedtuple('Locator', ['elem_type', 'elem_target', 'elem_val'])

    user_lbl = locator("By.ID", "username", "username")
    pass_lbl = locator("By.ID", "password", "password")
    .
    .

I want to change the class so that the values 'user_lbl' and 'pass_lbl' can be determined by the value of global variable 'version'. However, that variable is determined after class Common has already been initialized. I want the class file to look more like this:

class Common():
    locator = namedtuple('Locator', ['elem_type', 'elem_target', 'elem_val'])

    if global.version == 5:
        user_lbl = locator("By.ID", "username", "username")
        pass_lbl = locator("By.ID", "password", "password")
    else:
        user_lbl = locator("By.CLASS_NAME", "user", "username")
        pass_lbl = locator("By.CLASS_NAME", "pass", "password")

    .
    .

... and I want those variables to be determined based upon my 'version' variable. In order to pull this off, I need a way to force Class Common to reinitialize the variables. How do I do this?

CodePudding user response:

There is the property decorator for cases like that :

# n°1
from collections import namedtuple  # stdlib

class Common:
    locator = namedtuple('Locator', ['elem_type', 'elem_target', 'elem_val'])

    @property
    def user_lbl(self):
        if VERSION == 5:
            return self.locator("By.ID", "username", "username")
        else:
            return self.locator("By.CLASS_NAME", "user", "username")

    @property
    def pass_lbl(self):
        if VERSION == 5:
            return self.locator("By.ID", "password", "password")
        else:
            return self.locator("By.CLASS_NAME", "pass", "password")

VERSION = 4  # default value
print(Common().user_lbl)  # Locator(elem_type='By.CLASS_NAME', elem_target='user', elem_val='username')
print(Common().pass_lbl)  # Locator(elem_type='By.CLASS_NAME', elem_target='pass', elem_val='password')
VERSION = 5
print(Common().user_lbl)  # Locator(elem_type='By.ID', elem_target='username', elem_val='username')
print(Common().pass_lbl)  # Locator(elem_type='By.ID', elem_target='password', elem_val='password')
#           ^^

It makes possible to call methods that looks like regular fields. But it requires an object, that is a class instance, in order to work. If you prefer not to have objects (and just a class definition that acts like a namespace), you could use the staticmethod decorator :

# n°2
from collections import namedtuple  # stdlib

class Common:
    locator = namedtuple('Locator', ['elem_type', 'elem_target', 'elem_val'])

    @staticmethod
    def user_lbl():
        if VERSION == 5:
            return Common.locator("By.ID", "username", "username")
        else:
            return Common.locator("By.CLASS_NAME", "user", "username")

    @staticmethod
    def pass_lbl():
        if VERSION == 5:
            return Common.locator("By.ID", "password", "password")
        else:
            return Common.locator("By.CLASS_NAME", "pass", "password")

VERSION = 4  # default value
print(Common.user_lbl())  # Locator(elem_type='By.CLASS_NAME', elem_target='user', elem_val='username')
print(Common.pass_lbl())  # Locator(elem_type='By.CLASS_NAME', elem_target='pass', elem_val='password')
VERSION = 5
print(Common.user_lbl())  # Locator(elem_type='By.ID', elem_target='username', elem_val='username')
print(Common.pass_lbl())  # Locator(elem_type='By.ID', elem_target='password', elem_val='password')
#                    ^^

If you really really, really, want to just have to write Common.user_lbl then you may need @staticmethod with @property but it makes things a bit more difficult to maintain.

Another way could be to instantiate an instance named Common from a class named differently, which declares its propertys :

# n°3
from collections import namedtuple  # stdlib

class _Common:  # or whatever
    locator = namedtuple('Locator', ['elem_type', 'elem_target', 'elem_val'])

    @property
    def user_lbl(self):
        if VERSION == 5:
            return self.locator("By.ID", "username", "username")
        else:
            return self.locator("By.CLASS_NAME", "user", "username")

    @property
    def pass_lbl(self):
        if VERSION == 5:
            return self.locator("By.ID", "password", "password")
        else:
            return self.locator("By.CLASS_NAME", "pass", "password")

Common = _Common()

VERSION = 4  # default value
print(Common.user_lbl)  # Locator(elem_type='By.CLASS_NAME', elem_target='user', elem_val='username')
print(Common.pass_lbl)  # Locator(elem_type='By.CLASS_NAME', elem_target='pass', elem_val='password')
VERSION = 5
print(Common.user_lbl)  # Locator(elem_type='By.ID', elem_target='username', elem_val='username')
print(Common.pass_lbl)  # Locator(elem_type='By.ID', elem_target='password', elem_val='password')

This works too, but I recommend against it because it deviates from PEP8 naming conventions (an instance name should be lowercase).

And finally, maybe you will have other configurations to add later, in which case it may be handy to :

# n°4
from collections import namedtuple  # stdlib

class Common:
    def __init__(self, version: int):
        self._version = version

    locator = namedtuple('Locator', ['elem_type', 'elem_target', 'elem_val'])

    @property
    def user_lbl(self):
        if self._version == 5:
            return self.locator("By.ID", "username", "username")
        else:
            return self.locator("By.CLASS_NAME", "user", "username")

    @property
    def pass_lbl(self):
        if self._version == 5:
            return self.locator("By.ID", "password", "password")
        else:
            return self.locator("By.CLASS_NAME", "pass", "password")

common = Common(4)
print(common.user_lbl)  # Locator(elem_type='By.CLASS_NAME', elem_target='user', elem_val='username')
print(common.pass_lbl)  # Locator(elem_type='By.CLASS_NAME', elem_target='pass', elem_val='password')
common = Common(5)
print(common.user_lbl)  # Locator(elem_type='By.ID', elem_target='username', elem_val='username')
print(common.pass_lbl)  # Locator(elem_type='By.ID', elem_target='password', elem_val='password')

In this case, you do not need a global variable.

My preferred solutions would be n°4 (Class instance with parameter) then n°2 (staticmethod). But make your own choice :)

  • Related