[英]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
在循环中。
编辑:而不是直接运行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()
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.