Python: Running a for loop to create functions with exec, inside a class, inside a function

Rob Kwasowski :

I know it sounds complicated and it may not be possible, but I thought I'd try anyway.

So I'm doing some web scraping with Selenium, and any time that I want to run some jQuery on a page, instead of having

driver.execute_script("$('button').parent().click()")

I want to tidy up my code and just have

j('button').parent().click()

I'm attempting to accomplish this by having a class j and then when you have a function like parent it just returns another instance of that class.

In these examples I just have it printing the string that will get executed so that it can be tested more easily. And the way I have it the class exists inside a function.

So in this example everything works fine when all the class functions are defined normally, but if you uncomment the for loop section and define the .parent() function with exec then you get the error NameError: name 'j' is not defined:

def browser():

    class j:

        def __init__(self, str):
            if str[0] == '$':
                self.jq = str
            else:
                self.jq = f"$('{str}')"

        def click(self):
            output = f"{self.jq}.click()"
            print(output)
            return j(output)

        def parent(self):
            return j(f'{self.jq}.parent()')

        # functions = 'parent, next, prev'
        # for fn in functions.split(', '):
        #     exec(
        #         f"def {fn} (self):\n" +
        #         f"    return j(f'{{self.jq}}.{fn}()')"
        #     )

    j('button').parent().click()

browser()

However, if you move everything outside of the browser function and run everything then there's no error that way also, and that's if you define the functions either way.

I also tried doing exec(code, globals()) but that just gave me a different error message instead: AttributeError: 'j' object has no attribute 'parent'

Is there some way to define functions with exec in this way and do what I'm try to do?

Edit: This is the entire error message:

Traceback (most recent call last):
  File "C:\Users\Rob\Desktop\site\mysite\py\z.py", line 30, in <module>
    browser()
  File "C:\Users\Rob\Desktop\site\mysite\py\z.py", line 28, in browser
    j('button').parent().click()
  File "<string>", line 2, in parent
NameError: name 'j' is not defined
Rob Kwasowski :

Furas's second answer got me most of the way there so he deserves the upvotes. But I managed to improve on it and get it to do everything I was looking for.

So here is my version that can take arguments and checks type and puts quotes around strings but not numbers.

def browser():

    class j:

        def __init__(self, str):
            self.jq = f"$('{str}')"

        def click(self):
            self.jq += ".click()"
            print(self.jq)
            return self

        @classmethod
        def quotes(cls, arg):
            q = "'" * (type(arg) == str and len(arg) > 0)
            output = f'{q}{arg}{q}'
            return output

        @classmethod
        def add_function(cls, name):
            def func(cls, arg=''):
                cls.jq += f'.{name}({j.quotes(arg)})'
                return cls
            func.__name__ = name
            setattr(j, name, func)

    j_functions = 'closest, eq, find, next, parent, parents, prev'
    for fn in j_functions.split(', '):
        j.add_function(fn)

    j('button').eq(0).next('br').prev().click()

browser()


>> $('button').eq(0).next('br').prev().click()

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=3819&siteId=1