HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Streamlining repetitive class definitions in python with a class_factory() function

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
withfunctionstreamliningrepetitivepythonclass_factoryclassdefinitions

Problem

I forked this repo to be more concise. The code is here. I'll paste it below since that seems to be the style. I removed the class definitions at the bottom that I didn't change -- the edit I'm concerned with is the use of the "class_factory" function at the bottom.

Is this good? Pythonic?

```
from selenium.webdriver import DesiredCapabilities
from selenium.webdriver.firefox.webdriver import WebDriver as _Firefox
from selenium.webdriver.chrome.webdriver import WebDriver as _Chrome
from selenium.webdriver.ie.webdriver import WebDriver as _Ie
from selenium.webdriver.remote.webdriver import WebDriver as _Remote
from selenium.webdriver.phantomjs.webdriver import WebDriver as _PhantomJS

from webdriverplus.utils import _download
from webdriverplus.webdriver import WebDriverDecorator
from webdriverplus.webelement import WebElement

import atexit
import os
import socket
import subprocess
import time

try:
from urllib2 import URLError
except ImportError:
from urllib.error import URLError

VERSION = (0, 2, 0)

def get_version():
return '%d.%d.%d' % (VERSION[0], VERSION[1], VERSION[2])

class WebDriver(WebDriverDecorator):
_pool = {} # name -> (instance, signature)
_quit_on_exit = set() # set of instances
_selenium_server = None # Popen object
_default_browser_name = 'firefox'

@classmethod
def _at_exit(cls):
"""
Gets registered to run on system exit.
"""
if cls._selenium_server:
cls._selenium_server.kill()

for driver in cls._quit_on_exit:
try:
driver.quit(force=True)
except URLError:
pass

@classmethod
def _clear(cls):
cls._pool.clear()

@classmethod
def _get_from_pool(cls, browser):
"""Returns (instance, (args, kwargs))"""
return cls._pool.get(browser, (None, (None, None)))

def __new__(cls, browser=None, *args, **kwargs):
browsers = {'firefox':Firefox,

Solution

It's not unknown: python is really good at this, although the more common approach would be to use a metaclass. The immediate drawbacks are

1) it introduces a state-changing dependency to the import statement. If code that is using this code gets imported in a non-standard way you may get confusing errors because types will or will not appear depending on when this module gets run. It's not a major issue if this code will be imported directly but it's potentially problematic if there is more magic going on elsewhere.

2) less importantly, it's going to play hell with IDE's that try to do autocomplete for you :)

I'm guessing that the super().init idiom is a python 3 replacement for type('Name', (),{}) by it's form. If it's not - that is the old way to create a runtime type and it avoids creating and renaming the Class_ class, which seems messy to me. Examples of the 'old way' here

Lastly: this seems like classes that differ only in data, or to be more precise in composition. In cases like that I've always found it more maintainable to do it declaratively with class-level variables and appropriate indirections:

class Browser(object):
   BROWSER = 'browser'
   DRIVER = None

   @property
   def name(self):
       return self.BROWSER

   def do_something(self):
       self.DRIVER.do_something()

class Firefox(Browser):
    BROWSER = 'Firefox'
    DRIVER = _Firefox

class Chrome (Browser):
    BROWSER = 'Chrome'
    DRIVER = _Chrome


Doing this allows you to do subclassing and overrides as appropriate, which is much hairier with types that have to be created before they can be changed.

Code Snippets

class Browser(object):
   BROWSER = 'browser'
   DRIVER = None

   @property
   def name(self):
       return self.BROWSER

   def do_something(self):
       self.DRIVER.do_something()


class Firefox(Browser):
    BROWSER = 'Firefox'
    DRIVER = _Firefox

class Chrome (Browser):
    BROWSER = 'Chrome'
    DRIVER = _Chrome

Context

StackExchange Code Review Q#29914, answer score: 2

Revisions (0)

No revisions yet.