[英]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.