我知道这听起来很复杂,可能不可能,但我想还是应该尝试。
所以我正在使用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
答案 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
而不是创建新实例-它也可以工作。
因为lambda
在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()
答案 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()