patternpythonMinor
Streamlining repetitive class definitions in python with a class_factory() function
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,
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:
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.
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 = _ChromeDoing 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 = _ChromeContext
StackExchange Code Review Q#29914, answer score: 2
Revisions (0)
No revisions yet.