是否可以在Python中实现类似于.NET的属性?
我是.NET的属性的粉丝 - 预定义和用户定义的属性。属性是从Attribute
继承的类。 .NET中的所有东西(类,方法,成员(属性,字段,枚举值))都可以被“装饰”/装备属性。这些属性可以通过例如回读。编译器提取编译器提示或由用户提供的元编程。是否可以在Python中实现类似于.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文档):
- Writing Custom Attributes
- Retrieving Information Stored in Attributes
我也是一个大Python和装饰者的粉丝。在装饰器的帮助下,是否有可能在Python中实现类似于.NET的属性? Python中看起来像什么?
@Serializable
class SampleClass():
# Objects of this type can be serialized.
另一个用例可能是Python库。可以注册回调函数,如果输入包含正确的子命令,则由子解析器调用回调函数。定义这种命令行参数语法的更自然的方式可以是装饰器的使用。
这个问题不是关于序列化 - 它只是一个用法示例。
我玩过一些基于类的装饰器和直到我可以告诉它可以在Python中实现类似于.NET的属性。
那么首先让我们开发一个有意义的使用情况:
大多数人都知道了Python命令行参数解析器。此解析器可以处理子命令,如git commit -m "message"
,其中提交是子命令,-m <message>
是此子命令解析器的参数。可以为每个子命令解析器分配一个回调函数。
对于Windows的Python 3.4.2在处理回调函数时有一个错误。它在3.5.0中已修复(我没有测试过其他3.4.x版本)。
下面是一个经典例如:
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 - 定义一个子命令并将装饰函数注册为回调函数。
- 参数属性 - 将参数添加到子命令解析器。
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步 - 建立一个辅助的混合类来处理方法的属性
为了缓和与我实现了一个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
。这个类有一个普通的构造函数,它调用了两个基类构造函数。它还向主分析器添加了2个参数,用于详细的和调试。所有回调处理程序都用新的属性进行装饰。
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
辅助类。
该类使用提供的属性数据构造基于的解析器。解析过程由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库。
不错的例子,但如果它是用更pythonic风格编写的话会更好: - DONT name这个“属性” - 在Python中,“属性”有一个明确定义和完全不同的含义 - 属性(Python的意思)名称应该在all_lower - 使用泛型函数而不是实现'__magic_methods__',即'setattr(obj,name,attr )',而不是'obj .__ setattr __(name,attr)',并且避免直接访问'obj .__ dict__',除非您有足够的理由这样做 –
https://pypi.python.org/pypi/plac是' argparse'前端。它可以通过装饰功能来创建子分析器。子分析器的参数是函数的参数。它也可以使用Python3'函数注释'。几年前我开始使用'plac',但现在直接使用'argparse'更加自然。 – hpaulj
鉴于此Serializable
属性,您可能想要一个简单的解决方案。工作
class C:
b = 2
def __init__(self):
self.a = 1
def f(self):
pass
>>> c = C()
>>> c.__dict__
{'a': 1}
80%在__dict__
魔法属性,它是适用于所有对象已经完成。您可能希望拥有可序列化成员的类级别列表,并使用魔法方法来修改__dict__
属性将为您的班级返回的内容。
这同样适用于要移植的C#属性的其余部分。我不认为有一种通用的方法可以在不编写大量代码的情况下将随机属性移植到装饰器语法中。所以为了简单起见,我的建议并不是坚持装饰者,而是寻找简单而简单的方法。
这个问题不是关于序列化 - 它只是一个.NET使用属性的例子。另一个是['FlagAttribute'](https://msdn.microsoft.com/en-us/library/system.flagsattribute(v = vs.110).aspx)将枚举标记为位字段(one-热编码),否则所有成员将具有递增的整数值。 – Paebbels
不是所有的对象都有'__dict__',并且并非所有对象的属性都存储在'__dict__'中。 –
@Paebbels答案不是关于序列化。这是关于我的意见,不坚持装修。这是可能的,但不建议。 – Vovanrock2002
如果您希望从python社区获得答案,您可能想要解释“.NET属性”是什么以及您的C#和VB代码片段具有什么功能。 –
是的,可以装饰一个类,但更多的Pythonic方法是定义一个'Serializable' [抽象基类](https://docs.python.org/2/library/abc.html)并实现继承它的类中的适当方法。 – jonrsharpe
@brunodesthuilliers我用一个简短的解释扩展了我的问题。我希望我能解释.NET中属性的主要意图。我会添加进一步阅读的原始文档的链接以及一个很好的例子。 (这是关于这个问题的重要主题:) – Paebbels