对我来说,似乎很奇怪,有关键字参数(或参数)可以传递给函数或类的__init__
方法。如何防止对您的代码不熟悉的用户犯错误?如何让用户立即(几乎本能地)熟悉您的代码,而无需编写错误或冗长的文档或许多试验和错误,以防止用户快速,轻松地使用您的代码或模块?
在python中我们很幸运,因为我们有help
和dir
函数,它们通常可以指导我们更好地理解一些函数参数。但有时写的__doc__
字符串写得不好,什么也没解释。
让我举几个例子来说明我的意思:
>>> help(str.lower)
Help on method_descriptor:
lower(...)
S.lower() -> string
Return a copy of the string S converted to lowercase
。 例如,这里我们有一些具有 ... 输入参数的函数。这个参数代表什么,对于完整的新手(就像我第一次潜入python时那样)这令人困惑,我常常跳过这一部分。
一些提供建议或教程的网站只是打印输出帮助功能文件或只是实现sed功能的许多功能之一。
1 functionality of example function
或直接来自python.org
str.lower()
返回字符串的副本,并将所有套接字符[4]转换为小写。
对于8位字符串,此方法取决于语言环境。
现在,对于那些刚开始编程并且不能(或者不能)潜入位和字节,地址等的人来说,这是一个只有主术士才能执行的古老咒语,不要甚至让我开始说明为什么这对非英语国家的人没有帮助。
对于这个特定的示例函数,我可以找出2-3个可以以不同方式完成其工作的其他示例,我还必须发现可以通过将字符串插入str.lower(here)
部分来使用此示例函数
这里的一个大问题(我当然看到它)是一个带有少量谷歌搜索的str本身可以描述,它的功能只能通过维基百科来逻辑地结束。
如果我总结一下问题很简单,有没有办法在用作参数时修改关键字以接受比我们定义更多的名字,这样用户就不必在介绍教程的第一步中撕掉他/她的头发?
现在我知道你们中的一些人会说些什么:"如果你不理解它,就不要这样做"或者"我不是妈妈教你的东西" ...我必须说"分享是关心"和#34;你有时也需要帮助编码,否则你不会在这个网站上#34;。
答案 0 :(得分:2)
我将此装饰器命名为kwargs。像魅力一样工作
def alias_param(param_name: str, param_alias: str) -> Callable:
"""
Decorator for aliasing a param in a function
Args:
param_name: name of param in function to alias
param_alias: alias that can be used for this param
Returns:
"""
def decorator(func: Callable):
@functools.wraps(func)
def wrapper(*args, **kwargs):
alias_param_value = kwargs.get(param_alias)
if alias_param_value:
kwargs[param_name] = alias_param_value
del kwargs[param_alias]
result = func(*args, **kwargs)
return result
return wrapper
然后可以像这样使用
@alias_param("param_1", alias='p1')
def function(param_1=None):
return param_1
function(p1='value')
答案 1 :(得分:1)
这是我的解决方案,如果有人有更好的请评论或纠正。
这个想法:
这个想法是每个类都可以有一个特定属性的别名列表,因此用户可以(基于类名逻辑:>点需要x,y,z,名称属性,狗需要品种,名称,年龄,性别属性等等) on)基于其自己的内部逻辑来调用属性,而无需确切知道sed属性具有什么属性名称。
逻辑:
如果 function 或 class 输入了一些关键字参数,那么我需要与sed参数关联的常用单词的最小列表。同义词和成语可以轻松搜索,但我会建议反对同义词的大名单,保持小2-3 +属性名称。然后我们需要的是将这些别名映射到原始属性,因为我们作为代码需要知道如何调用属性而不调用getattr(self,someattributestring)
<强>码强>:
按时间顺序,我们必须首先定义一个函数来生成别名。
# generate aliases for attributes
def generateAliases(*argListNames):
returningValues = [] # this could be omitted if user wants to make generator
la = returningValues.append # this could be omitted also
#dominated argListNames
argListNames = map( str, argListNames ) #for simplicity convert to strings
argListNames = map( str.lower , argListNames ) #for simplicity convert to lower string
argListNames = list(argListNames) # back to list
# small nameless lambda functions
getFirstChr = lambda element: element[0] # getting first character
conectedJoing= lambda connector,item,args: connecter.join([ item, args if not __isTL__(args) else connecter.join(args) ]) # generating joined string
# list of string convertors used to generate aliases
convertorList= [ lambda x: x , getFirstChr , str.title , str.upper , lambda x: getFirstChr( str.upper(x) ) ]
for item in argListNames:
## since we dont want alias to repeat itself
listNoitem = filter( lambda x: x!=item , argListNames )
listNoitem = list(listNoitem)
la(item) # if returningValues omitted use yield statement
for conversion in convertorList: ##1 keeping up with for loops
converted = conversion(item)
for connecter in "_,".split(","):
for listItem in listNoitem:
for cnvrt in convertorList: ##2 cnvrt is converted second stage : used to convert the whole list of items without current alias
cList = cnvrt(listItem)
la( conectedJoing(connecter,converted,cList) )# if returningValues omitted use yield statement
la( conectedJoing(connecter,converted,listNoitem) )# if returningValues omitted use yield statement
# if user wanted to make generator omit next lines
returningValues = [ x.replace("_","") if x.endswith("_") else x for x in returningValues ]
returningValues = sorted(set(returningValues))
return list( map(str,returningValues) )
现在我们需要在函数或类中映射和检查这些参数,所以我们需要一些参数解析器。
## **kwargs argument parser , no error
def argumentParser(AprovedSequence,**kwargs):
# AprovedSequence is suposed to be dictionary data type with {"original argument": generateAliases(originalArgumentName,somealias,somealias,...)
"""
phrases the keyword arguments,
for example : argumentParser(AprovedSequence,someArgument=somevalue,otherArgument=othervalue ... )
then it checks if someArgument is needed by checking in AprovedSequence if name "someArgument" is found in sequence:
If "someArgument" is found in AprovedSequence it stores returns dictionary of DefaultKeys : Values
for example: DefaultKey for someArgument: somevalue
input:
argumentParser(dict: AprovedSequence, kwargs)
returns:
dictionary of found attributes and their values
!!important!! kwargs are not case sensitive in this case , so go crazy as long as you get the apropriate keyword!!
if you dont know what kind of keywords are needed for class
just type className.errorAttributeNames()
for example point.errorAttributeNames()
"""
if isinstance(AprovedSequence,dict):
di = dict.items # dictionary.values(someDict)
dk = dict.keys # dictionary.keys(someDict)
# managing the kwargs and aprooved sequence data
toLowerStr = lambda el: str(el).lower() # conversion to lower string
asingKey = lambda el: [ key for key in dk(AprovedSequence) if toLowerStr(el) in AprovedSequence[key] ][0] # asigning key
return { asingKey(k):v for k,v in di(kwargs) } # dictionary comprehension
else:
raise TypeError("argumentPhraser function accepts only dictionary for a AprovedSequence aka first item")
return None
<强>实施强>
def somefunction(**kwargs):
aliases = {
"val1":generateAliases("first","1"),
"val2":generateAliases("second","2")
}
aproved = argumentParser(aliases,**kwargs)
if "val1" in aproved.keys(): val1 = aproved["val1"]
else: val1 = 0 # seting default value for val1
if "val2" in aproved.keys(): val2 = aproved["val2"]
else: val2 = 1 # seting default value for val2
#do something or your code here
return val1,val2
# for testing purposes
for x in [ {"first":1} , {"second":2,"first":3} , {"f1":4,"s2":5} , {"f_1":6,"2_s":7} ]:
# displaying imputed variables
form = ["passed "]
form += [ "{} as {} ".format(key,value) for key,value in x.items() ]
# implementing somefunciton
print( "".join(form), somefunction(**x) )
输出
python27 -m kwtest
Process started >>>
passed first as 1 (1, 1)
passed second as 2 first as 3 (3, 2)
passed f1 as 4 s2 as 5 (4, 5)
passed 2_s as 7 f_1 as 6 (6, 7)
<<< Process finished. (Exit code 0)
python35 -m kwtest
Process started >>>
passed first as 1 (1, 1)
passed first as 3 second as 2 (3, 2)
passed f1 as 4 s2 as 5 (4, 5)
passed f_1 as 6 2_s as 7 (6, 7)
<<< Process finished. (Exit code 0)
如果在类中实现,则__init__
中的流程类似,但__getitem__
,__setitem__
和__delitem__
必须进行编码,以便他们也可以在别名中搜索属性名称。此外,可以使用self.attributes = list( aliases.keys())
或类似名称生成属性名称。默认值可以存储在具有__kwdefaults__
或&#39; 默认值&#39;的类中。取决于您的功能使用的数据类型。
此代码已在py2.7和py3.5上进行了测试,如您所见。
如果需要进一步说明
您可以在类全局属性或__init__
内定义别名
进一步解释__getitem__
:
def __getitem__(self,item):
if item in self.aliases.keys():
return getattr(self,item)
if any( item in value for value in self.aliases.values() ):
item = [ key for key in self.aliases.keys() if item in self.aliases[key] ] [0]
return getattr(self,item)
if item in range( len( self.aliases.keys() ) ):
item = list( self.aliases.keys() )[item]
return getattr(self,item)
进一步解释__setitem__
:
def __setitem__(self,item,value):
item = self.__getitem__(self,item)
#? must have `__dict__` method or class needs to be instanced from object like class someclass(object)
item = [ key for key in vars(self).items() if self[key] == item] [0]
if item != None:
setattr(self,item,value)