Python 中的关键字参数别名

Keyword argument aliases in Python

我正在为一些新编码员从头开始创建自定义 3D space,我担心我的文档可能对初学者不友好。这是一个例子:

def make_point(**kwargs):
   X, Y, Z = 0, 0, 0;
   if "Xcoord" in kwargs.keys(): X = kwargs["Xcoord"];
   if "Ycoord" in kwargs.keys(): Y = kwargs["Ycoord"];
   if "Zcoord" in kwargs.keys(): Z = kwargs["Zcoord"];
   return tuple(X, Y, Z);

但是我命名keyword arguments的方式不太适合对linear algebra有一定了解的新程序员,但是对于我跟踪哪个变量是什么。所以以这种方式我有 vXcoord 向量 x 坐标, pXcoord 点等

有没有办法让 keyword arguments 对用户更友好,所以如果用户键入 vXvectorX 或任何看起来更合乎逻辑的内容,它仍然会映射到 vXcoord?

思路:

想法是每个 class 都可以有一个特定属性的别名列表,这样用户就可以(基于 class 名称逻辑:> 点需要 x、y、z、名称属性, dog 需要 breed, name, age, sex 等属性)根据它自己的内部逻辑来调用属性,而不需要确切知道 sed 属性有什么属性名。

逻辑:

如果 functionclass 有一些关键字参数的输入,那么我需要最少的相关常用词列表使用 sed 参数。同义词和成语可以很容易地用谷歌搜索,但我建议不要使用大列表的同义词,将其保持在 2-3 + 属性名称的小范围内。然后我们只需要 map 那些别名到原始属性,因为我们作为代码需要知道如何在不调用 getattr(self, someattributestring)

的情况下调用属性

代码:

按时间顺序我们首先要定义一个函数来生成别名

# Generate aliases for attributes
def generateAliases(*argListNames):

    returningValues = [] # This could be omitted if the user wants to make a 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 don't want an alias to repeat itself
        listNoitem = filter(lambda x: x != item, argListNames)
        listNoitem = list(listNoitem)

        la(item) # If returningValues omitted, use the '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 the current alias
                        cList = cnvrt(listItem)

                        la(conectedJoing(connecter, converted, cList)) # If returningValues is omitted, use the 'yield' statement


                la(conectedJoing(connecter, converted, listNoitem)) # if returningValues is omitted, use the 'yield' statement

    # If the user wanted to make a generator, omit the next lines
    returningValues = [ x.replace("_", "") if x.endswith("_") else x for x in returningValues ]
    returningValues = sorted(set(returningValues))
    return list(map(str, returningValues))

现在我们需要在 函数class 中映射和检查这些参数,所以我们需要一些参数解析器.

## **kwargs argument parser, no error
def argumentParser(ApprovedSequence, **kwargs):

    # ApprovedSequence is supposed to be a dictionary data type with {"original argument": generateAliases(originalArgumentName, somealias, somealias, ...)

    """
        Phrases the keyword arguments,
            for example:     argumentParser(ApprovedSequence, someArgument=somevalue, otherArgument=othervalue ...)
        Then it checks if someArgument is needed by checking in ApprovedSequence if name "someArgument" is found in the sequence:
        If "someArgument" is found in ApprovedSequence, it stores returns the dictionary of DefaultKeys: Values,
            for example: DefaultKey for someArgument: somevalue

        input:
            argumentParser(dict: ApprovedSequence, 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 appropriate keyword!!
        If you don't know what kind of keywords are needed for class,
            just type className.errorAttributeNames().
            For example, point.errorAttributeNames()

    """
    if isinstance(ApprovedSequence, dict):

        di = dict.items # dictionary.values(someDict)
        dk = dict.keys  # dictionary.keys(someDict)

        # Managing the kwargs and approved sequence data
        toLowerStr = lambda el: str(el).lower() # Conversion to lower string
        asingKey = lambda el: [ key for key in dk(ApprovedSequence) if toLowerStr(el) in ApprovedSequence[key] ][0] # Assigning key

        return { asingKey(k):v for k,v in di(kwargs) } # Dictionary comprehension
    else:
        raise TypeError("argumentPhraser function accepts only a dictionary for a ApprovedSequence, aka first item")
        return None

实施

def somefunction(**kwargs):
    aliases = {
        "val1": generateAliases("first", "1"),
        "val2": generateAliases("second", "2")
    }
    approved = argumentParser(aliases, **kwargs)

    if "val1" in approved.keys(): val1 = approved["val1"]
    else: val1 = 0 # Setting default value for val1

    if "val2" in approved.keys(): val2 = approved["val2"]
    else: val2 = 1 # Setting 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 inputed variables
    form = ["passed "]
    form += [ "{} as {} ".format(key, value) for key, value in x.items() ]
    # Implementing somefunction
    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)

如果在 classes 中实现,过程在 __init__ 中类似,但必须对 __getitem____setitem____delitem__ 进行编码,以便它们也可以在别名中搜索属性名称。此外,属性名称可以用 self.attributes = list(aliases.keys()) 或类似的东西生成。默认值可以存储在 classes 和 __kwdefaults__ 或 'defaults' 中,具体取决于您的函数使用的数据类型。

如您所见,此代码已在 Python 2.7 和 Python 3.5 上进行了测试。

如果需要进一步解释

您可以在 class 全局属性或 __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 a `__dict__` method or the class needs to be instanced from an 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)