是否有可能在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中实现类.net属性?

我已经使用了一些基于类的装饰器和

,就我所知,可以在Python中实现类。net属性。

所以首先让我们开发一个有意义的用例:
我们大多数人都知道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 -构建应用程序类

现在是时候构建一个继承MyBaseArgParseMixin的应用程序类了。稍后我将讨论ArgParseMixin。类有一个普通构造函数,它调用两个基类构造函数。它还向主解析器添加了verbosedebug两个参数。所有回调处理程序都用新的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#属性。我认为没有一种通用的方法可以在不编写大量代码的情况下将随机属性移植到装饰器语法中。因此,为了简单起见,我的建议是不要坚持使用装饰器,而是寻找简短而简单的方法。