运行for循环以在类内部,函数内部使用exec创建函数

时间:2020-02-21 04:55:35

标签: python

我知道这听起来很复杂,可能不可能,但我想还是应该尝试。

所以我正在使用Selenium进行一些Web抓取,并且每当我想在页面上运行一些jQuery而不是

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

我想整理我的代码,只是拥有

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

我正在尝试通过拥有一个类j来实现此目的,然后当您拥有类似parent之类的函数时,它只会返回该类的另一个实例。

在这些示例中,我只是打印了将要执行的字符串,以便可以更轻松地对其进行测试。而我拥有该类的方式是在函数内部。

因此,在此示例中,当所有类函数都正常定义时,一切正常,但是,如果取消注释for循环部分并用.parent()定义exec函数,则会得到错误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()

但是,如果将所有内容移至browser函数之外并运行所有内容,那么也就没有错误,也就是说,无论哪种方式定义函数。

我也尝试做exec(code, globals()),但这给了我一个不同的错误消息:AttributeError: 'j' object has no attribute 'parent'

是否可以通过这种方式用exec定义函数并执行我想做的事情?

编辑: 这是整个错误消息:

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

2 个答案:

答案 0 :(得分:2)

使用setattr和来自 Dynamic/runtime method creation (code generation) in Python的示例:

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('click:', output)
            return j(output)

        def fun(self, name):
            return j(f'{self.jq}.{name}()')

        def fun2(self, name):
            self.jq += f'.{name}()'
            return self

    # - after class -

    functions = 'parent, next, prev'
    for fn in functions.split(', '):
         #setattr(j, fn, lambda cls, fun=fn: j(f'{cls.jq}.{fun}()'))
         #setattr(j, fn, lambda cls, fun=fn: j.fun(cls, fun))
         setattr(j, fn, lambda cls, fun=fn: cls.fun(fun))

    print('test:', j('button').parent().next().click().fun2('a').fun('b').jq)

browser()

但这并不理想-它会创建类似next = fun("next")的东西,并且可能以next("x")的身份运行并添加.x()而不是.next()


我也测试了return self而不是创建新实例-它也可以工作。

因为lambdafor循环中使用,所以它需要fun=fn才能正确地从fn获取值。没有此功能,它将在执行时从fn获取值,然后所有函数从fn获得相同的值-循环中分配给fn的最后一个值。


编辑:我不是运行setattr来直接运行lambda,而是运行运行setattr并使用内部函数而不是lambda的函数-这无需第二个参数就可以创建函数的方式,因此现在.next("x")会引发错误。

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('click:', output)
            return j(output)

        def fun(self, name):
            return j(f'{self.jq}.{name}()')

        def fun2(self, name):
            self.jq += f'.{name}()'
            return self

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

    # - after class -

    functions = 'parent, next, prev'
    for fn in functions.split(', '):
         #setattr(j, fn, lambda cls, fun=fn: j(f'{cls.jq}.{fun}()'))
         #setattr(j, fn, lambda cls, fun=fn: j.fun(cls, fun))
         #setattr(j, fn, lambda cls, fun=fn: cls.fun(fun))
         #setattr(j, fn, lambda cls: cls.fun(x))
         j.add_function(fn)

    item = j('button').parent().next().click().fun2('a').fun('b')
    print('test:', item.jq)

    j.add_function('other')

    item = item.other()
    print('test:', item.jq)

    item = j('input').other().click()
    print('test:', item.jq)

    print('name:', j.next.__name__) # gives `next`, without `func.__name__ = name` it gives `func`

browser()

我也测试了__getattr__

def browser():

    class j:


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

        def click(self):
            return self.fun('click')
            #self.jq += f".click()"
            #return self

        def fun(self, name):
            self.jq += f'.{name}()'
            return self

        functions = ['parent', 'next', 'prev']

        #def __getattr__(self, name):
        #    '''acceptes every name'''
        #    return lambda: self.fun(name) # I use `lambda` because it needs function to use `()`

        #def __getattr__(self, name):
        #    '''___name__ doesn't change its name in error message and it shows `<lambda>`'''
        #    a = lambda: self.fun(name)
        #    a.__name__ = name
        #    return a

        def __getattr__(self, name):
            '''acceptes only name from list `functions`'''
            if name in self.functions:
                return lambda: self.fun(name) # I use `lambda` because it needs function to use `()`
            else:
                raise AttributeError(f"'j' object has no attribute '{name}'")

        #def __getattr__(self, name):
        #    '''test running without `fun()`'''
        #    self.jq += f'.{name}()'
        #    return lambda:self # I use `lambda` because it needs function to use `()`


    # - after class -

    item = j('button').parent().next().click().fun('a').fun('b')
    print('test:', item.jq)

    j.functions.append('other')
    item = item.other()
    print('test:', item.jq)

    item = j('input').other().click()
    print('test:', item.jq)

    #print('name:', j.next.__name__) # doesn't work

    print(j('a').tests().jq) 

browser()

答案 1 :(得分:2)

Furas的第二个回答使我大部分时间都到了那里,所以他值得投票。但是我设法对其进行了改进,并使其能够完成我一直在寻找的一切。

这是我的版本,可以接受参数并检查类型,并在字符串而不是数字周围加上引号。

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()