繁体   English   中英

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

[英]Running a for loop to create functions with exec, inside a class, inside a function

我知道这听起来很复杂,而且可能不可能,但我想我还是会尝试一下。

所以我正在用 Selenium 做一些网页抓取,并且任何时候我想在页面上运行一些 jQuery,而不是

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

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

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

我试图通过拥有一个类j来实现这一点,然后当你有一个像parent这样的函数时,它只返回该类的另一个实例。

在这些示例中,我只是让它打印将被执行的字符串,以便可以更轻松地对其进行测试。 我的方式是类存在于函数中。

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

在 Python 中使用setattr和来自动态/运行时方法创建(代码生成)的示例我做了:

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而不是创建新实例 - 它也可以工作。

因为lambda for循环for所以它需要fun=fn才能从fn正确获取值。 如果没有这个,它将在执行时从fn获取值,然后所有函数从fn获取相同的值 - 最后一个值分配给fn在循环中。


编辑:而不是直接运行setattrlambda我运行它运行功能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()

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM