是否可以在Python中实现类似于.NET的属性?

是否可以在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​​库。可以注册回调函数,如果输入包含正确的子命令,则由子解析器调用回调函数。定义这种命令行参数语法的更自然的方式可以是装饰器的使用。

这个问题不是关于序列化 - 它只是一个用法示例。

+5

如果您希望从python社区获得答案,您可能想要解释“.NET属性”是什么以及您的C#和VB代码片段具有什么功能。 –

+0

是的,可以装饰一个类,但更多的Pythonic方法是定义一个'Serializable' [抽象基类](https://docs.python.org/2/library/abc.html)并实现继承它的类中的适当方法。 – jonrsharpe

+0

@brunodesthuilliers我用一个简短的解释扩展了我的问题。我希望我能解释.NET中属性的主要意图。我会添加进一步阅读的原始文档的链接以及一个很好的例子。 (这是关于这个问题的重要主题:) – Paebbels

我玩过一些基于类的装饰器和直到我可以告诉它可以在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步 - 建立一个应用程序类

现在是时候建立,从MyBaseArgParseMixin继承的应用程序类。稍后我会讨论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库。

+2

不错的例子,但如果它是用更pythonic风格编写的话会更好: - DONT name这个“属性” - 在Python中,“属性”有一个明确定义和完全不同的含义 - 属性(Python的意思)名称应该在all_lower - 使用泛型函数而不是实现'__magic_methods__',即'setattr(obj,name,attr )',而不是'obj .__ setattr __(name,attr)',并且避免直接访问'obj .__ dict__',除非您有足够的理由这样做 –

+2

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#属性的其余部分。我不认为有一种通用的方法可以在不编写大量代码的情况下将随机属性移植到装饰器语法中。所以为了简单起见,我的建议并不是坚持装饰者,而是寻找简单而简单的方法。

+0

这个问题不是关于序列化 - 它只是一个.NET使用属性的例子。另一个是['FlagAttribute'](https://msdn.microsoft.com/en-us/library/system.flagsattribute(v = vs.110).aspx)将枚举​​标记为位字段(one-热编码),否则所有成员将具有递增的整数值。 – Paebbels

+1

不是所有的对象都有'__dict__',并且并非所有对象的属性都存储在'__dict__'中。 –

+0

@Paebbels答案不是关于序列化。这是关于我的意见,不坚持装修。这是可能的,但不建议。 – Vovanrock2002