是否有可能在Python中实现类.net属性?
本文关键字:net 属性 实现 有可能 Python 是否 | 更新日期: 2023-09-27 18:17:59
我是。net属性的忠实粉丝——无论是预定义的还是用户定义的。属性是从Attribute
继承的类。. net中的大部分内容(类、方法、成员(属性、字段、枚举值))都可以"装饰"/配备属性。这些属性可以被回读,例如编译器提取编译器提示,或者由用户作为一种元编程。
c#示例:
[System.Serializable]
public class SampleClass {
// Objects of this type can be serialized.
}
VB的例子:<System.Serializable()>
Public Class SampleClass
' Objects of this type can be serialized.
End Class
在我的例子中,Serializable
标记了一个要序列化的类。序列化程序现在可以检索该类实例的所有成员,并将实例数据组装为序列化对象。也可以将单个字段标记为序列化或不序列化。
用户可以通过反射从类中获得定义的属性:System.Attribute.GetCustomAttributes(...)
进一步阅读(MSDN文档):
-书写自定义属性
-检索存储在Attributes
我也是Python和decorator的忠实粉丝。有可能在装饰器的帮助下在Python中实现。net类属性吗?在Python中会是什么样子呢?
@Serializable
class SampleClass():
# Objects of this type can be serialized.
另一个用例可能是Python argparse
库。可以注册回调函数,如果输入包含正确的子命令,子解析器将调用回调函数。定义这种命令行参数语法的一种更自然的方法是使用修饰符。
这个问题不是关于序列化的,它只是一个用法示例。
我已经使用了一些基于类的装饰器和
所以首先让我们开发一个有意义的用例:
我们大多数人都知道Python argparse
命令行参数解析器。这个解析器可以处理像git commit -m "message"
这样的子命令,其中commit是子命令,-m <message>
是这个子命令解析器的一个参数。可以为每个子命令解析器分配一个回调函数。
Python 3.4.2 for Windows有一个处理回调函数的错误。它在3.5.0中修复(我还没有测试其他3.4)。x版本)。
下面是一个经典的argparse
示例:
class MyProg():
def Run(self):
# create a commandline argument parser
MainParser = argparse.ArgumentParser(
description = textwrap.dedent('''This is the User Service Tool.'''),
formatter_class = argparse.RawDescriptionHelpFormatter,
add_help=False)
MainParser.add_argument('-v', '--verbose', dest="verbose", help='print out detailed messages', action='store_const', const=True, default=False)
MainParser.add_argument('-d', '--debug', dest="debug", help='enable debug mode', action='store_const', const=True, default=False)
MainParser.set_defaults(func=self.HandleDefault)
subParsers = MainParser.add_subparsers(help='sub-command help')
# UserManagement commads
# create the sub-parser for the "create-user" command
CreateUserParser = subParsers.add_parser('create-user', help='create-user help')
CreateUserParser.add_argument(metavar='<Username>', dest="Users", type=str, nargs='+', help='todo help')
CreateUserParser.set_defaults(func=self.HandleCreateUser)
# create the sub-parser for the "remove-user" command
RemoveUserParser = subParsers.add_parser('remove-user', help='remove-user help')
RemoveUserParser.add_argument(metavar='<UserID>', dest="UserIDs", type=str, nargs='+', help='todo help')
RemoveUserParser.set_defaults(func=self.HandleRemoveUser)
def HandleDefault(self, args):
print("HandleDefault:")
def HandleCreateUser(self, args):
print("HandleCreateUser: {0}".format(str(args.Users)))
def HandleRemoveUser(self, args):
print("HandleRemoveUser: {0}".format(str(args.UserIDs)))
my = MyProg()
my.Run()
一个更好的、更具描述性的解决方案可能是这样的:
class MyProg():
def __init__(self):
self.BuildParser()
# ...
def BuiltParser(self):
# 1. search self for methods (potential handlers)
# 2. search this methods for attributes
# 3. extract Command and Argument attributes
# 4. create the parser with that provided metadata
# UserManagement commads
@CommandAttribute('create-user', help="create-user help")
@ArgumentAttribute(metavar='<Username>', dest="Users", type=str, nargs='+', help='todo help')
def HandleCreateUser(self, args):
print("HandleCreateUser: {0}".format(str(args.Users)))
@CommandAttribute('remove-user',help="remove-user help")
@ArgumentAttribute(metavar='<UserID>', dest="UserIDs", type=str, nargs='+', help='todo help')
def HandleRemoveUser(self, args):
print("HandleRemoveUser: {0}".format(str(args.UserIDs)))
步骤1—通用Attribute
类
Attribute
类,它也是一个基于类的装饰器。这个装饰器将自己添加到一个名为__attributes__
的列表中,该列表注册在要装饰的函数上。
class Attribute():
AttributesMemberName = "__attributes__"
_debug = False
def __call__(self, func):
# inherit attributes and append myself or create a new attributes list
if (func.__dict__.__contains__(Attribute.AttributesMemberName)):
func.__dict__[Attribute.AttributesMemberName].append(self)
else:
func.__setattr__(Attribute.AttributesMemberName, [self])
return func
def __str__(self):
return self.__name__
@classmethod
def GetAttributes(self, method):
if method.__dict__.__contains__(Attribute.AttributesMemberName):
attributes = method.__dict__[Attribute.AttributesMemberName]
if isinstance(attributes, list):
return [attribute for attribute in attributes if isinstance(attribute, self)]
return list()
步骤2 -用户自定义属性
现在我们可以创建自定义属性,继承Attribute
的基本装饰功能。我将声明3个属性:
- DefaultAttribute -如果没有子命令解析器识别命令,此修饰方法将作为备用处理程序。
- CommandAttribute -定义一个子命令并将修饰函数注册为回调函数
- ArgumentAttribute -添加参数到子命令解析器
class DefaultAttribute(Attribute):
__handler = None
def __call__(self, func):
self.__handler = func
return super().__call__(func)
@property
def Handler(self):
return self.__handler
class CommandAttribute(Attribute):
__command = ""
__handler = None
__kwargs = None
def __init__(self, command, **kwargs):
super().__init__()
self.__command = command
self.__kwargs = kwargs
def __call__(self, func):
self.__handler = func
return super().__call__(func)
@property
def Command(self):
return self.__command
@property
def Handler(self):
return self.__handler
@property
def KWArgs(self):
return self.__kwargs
class ArgumentAttribute(Attribute):
__args = None
__kwargs = None
def __init__(self, *args, **kwargs):
super().__init__()
self.__args = args
self.__kwargs = kwargs
@property
def Args(self):
return self.__args
@property
def KWArgs(self):
return self.__kwargs
步骤3 -构建一个helper mixin类来处理方法
的属性为了简化属性工作,我实现了一个AttributeHelperMixin
类,它可以:
- 检索类的所有方法
- 检查方法是否有属性和
- 返回给定方法的属性列表。
class AttributeHelperMixin():
def GetMethods(self):
return {funcname: func
for funcname, func in self.__class__.__dict__.items()
if hasattr(func, '__dict__')
}.items()
def HasAttribute(self, method):
if method.__dict__.__contains__(Attribute.AttributesMemberName):
attributeList = method.__dict__[Attribute.AttributesMemberName]
return (isinstance(attributeList, list) and (len(attributeList) != 0))
else:
return False
def GetAttributes(self, method):
if method.__dict__.__contains__(Attribute.AttributesMemberName):
attributeList = method.__dict__[Attribute.AttributesMemberName]
if isinstance(attributeList, list):
return attributeList
return list()
步骤4 -构建应用程序类
现在是时候构建一个继承MyBase
和ArgParseMixin
的应用程序类了。稍后我将讨论ArgParseMixin
。类有一个普通构造函数,它调用两个基类构造函数。它还向主解析器添加了verbose和debug两个参数。所有回调处理程序都用新的Attributes来装饰。
class MyBase():
def __init__(self):
pass
class prog(MyBase, ArgParseMixin):
def __init__(self):
import argparse
import textwrap
# call constructor of the main interitance tree
MyBase.__init__(self)
# Call the constructor of the ArgParseMixin
ArgParseMixin.__init__(self,
description = textwrap.dedent(''''
This is the Admin Service Tool.
'''),
formatter_class = argparse.RawDescriptionHelpFormatter,
add_help=False)
self.MainParser.add_argument('-v', '--verbose', dest="verbose", help='print out detailed messages', action='store_const', const=True, default=False)
self.MainParser.add_argument('-d', '--debug', dest="debug", help='enable debug mode', action='store_const', const=True, default=False)
def Run(self):
ArgParseMixin.Run(self)
@DefaultAttribute()
def HandleDefault(self, args):
print("DefaultHandler: verbose={0} debug={1}".format(str(args.verbose), str(args.debug)))
@CommandAttribute("create-user", help="my new command")
@ArgumentAttribute(metavar='<Username>', dest="Users", type=str, help='todo help')
def HandleCreateUser(self, args):
print("HandleCreateUser: {0}".format(str(args.Users)))
@CommandAttribute("remove-user", help="my new command")
@ArgumentAttribute(metavar='<UserID>', dest="UserIDs", type=str, help='todo help')
def HandleRemoveUser(self, args):
print("HandleRemoveUser: {0}".format(str(args.UserIDs)))
p = prog()
p.Run()
步骤5 - ArgParseMixin
助手类
该类使用属性提供的数据构造基于argparse
的解析器。解析过程由Run()
调用。
class ArgParseMixin(AttributeHelperMixin):
__mainParser = None
__subParser = None
__subParsers = {}
def __init__(self, **kwargs):
super().__init__()
# create a commandline argument parser
import argparse
self.__mainParser = argparse.ArgumentParser(**kwargs)
self.__subParser = self.__mainParser.add_subparsers(help='sub-command help')
for funcname,func in self.GetMethods():
defAttributes = DefaultAttribute.GetAttributes(func)
if (len(defAttributes) != 0):
defAttribute = defAttributes[0]
self.__mainParser.set_defaults(func=defAttribute.Handler)
continue
cmdAttributes = CommandAttribute.GetAttributes(func)
if (len(cmdAttributes) != 0):
cmdAttribute = cmdAttributes[0]
subParser = self.__subParser.add_parser(cmdAttribute.Command, **(cmdAttribute.KWArgs))
subParser.set_defaults(func=cmdAttribute.Handler)
for argAttribute in ArgumentAttribute.GetAttributes(func):
subParser.add_argument(*(argAttribute.Args), **(argAttribute.KWArgs))
self.__subParsers[cmdAttribute.Command] = subParser
continue
def Run(self):
# parse command line options and process splitted arguments in callback functions
args = self.__mainParser.parse_args()
# because func is a function (unbound to an object), it MUST be called with self as a first parameter
args.func(self, args)
@property
def MainParser(self):
return self.__mainParser
@property
def SubParsers(self):
return self.__subParsers
我将在GitHub上提供我的代码和示例作为pyAttribute存储库。
给定这个Serializable
属性,您可能希望有一个简单的解决方案。
class C:
b = 2
def __init__(self):
self.a = 1
def f(self):
pass
>>> c = C()
>>> c.__dict__
{'a': 1}
80%的工作已经在__dict__
魔术属性中完成,该属性可用于每个对象。您可能希望拥有一个类级别的可序列化成员列表,并使用__getattribute__
魔术方法来修改您的__dict__
属性将为您的类返回的内容。
同样适用于你想要移植的其他c#属性。我认为没有一种通用的方法可以在不编写大量代码的情况下将随机属性移植到装饰器语法中。因此,为了简单起见,我的建议是不要坚持使用装饰器,而是寻找简短而简单的方法。