python [Python 3.6]带类型管理的属性加载器。更多信息@ https://www.reddit.com/r/Python/comments/641dhn/attribute_loade

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python [Python 3.6]带类型管理的属性加载器。更多信息@ https://www.reddit.com/r/Python/comments/641dhn/attribute_loade相关的知识,希望对你有一定的参考价值。

from enum import Enum
import re



###########################################################################################################################
#/////         ////////////////////////////////////////////////////////////////////////////////////////////////////////////
#/////  Utils  ////////////////////////////////////////////////////////////////////////////////////////////////////////////
#/////         ////////////////////////////////////////////////////////////////////////////////////////////////////////////
###########################################################################################################################

def _quote_StringOnly(x):
  return f"'{x}'" if isinstance(x, str) else(x)



###########################################################################################################################
#/////              ///////////////////////////////////////////////////////////////////////////////////////////////////////
#/////  Properties  ///////////////////////////////////////////////////////////////////////////////////////////////////////
#/////              ///////////////////////////////////////////////////////////////////////////////////////////////////////
###########################################################################################################################

class PropertySet:

  def __init__(self, *properties):
    self._entries = set(properties)
    self._keys    = [x.key for x in properties]

  def __call__(self): return self._entries
  def get(self, key): return next((x for x in self._entries if(x.key == key)), None)
  def keys(self):     return self._keys

class Property:
  class _INIT(Enum): PROPERTY = 0
  INIT = _INIT.PROPERTY

  def __init__(self, type: object, key: str, default: object):
    self.type    = type
    self.key     = key
    self.default = default

  def __repr__(self):   return self._to_String()
  def __str__(self):    return self._to_String()
  def _to_String(self): return f"Property{{type={self.type.__name__}, key='{self.key}', default={self.default}}}"



###########################################################################################################################
#/////               //////////////////////////////////////////////////////////////////////////////////////////////////////
#/////  TypeManaged  //////////////////////////////////////////////////////////////////////////////////////////////////////
#/////               //////////////////////////////////////////////////////////////////////////////////////////////////////
###########################################################################################################################

class TypeManaged:
  _properties: PropertySet

  def __init__(self, **attributes):
    self._set_Attributes(attributes)
    self._validate_Attributes()

  def _set_Attributes(self, attributes):
    for key, value in attributes.items():
      _property = self._properties.get(key)
      if   (_property is None):                    raise Undefined_Property(self, key, value)
      elif not(isinstance(value, _property.type)): raise Invalid_Assignment(self, key, value)
      else:                                        self.__dict__[key] = value

  def _validate_Attributes(self):
    for _property in self._properties():
      validKey = re.compile(r"^[a-z_][a-z0-9_]*$", re.IGNORECASE)
      if   not(validKey.match(_property.key)):                 raise Invalid_PropertyKey(self, _property)
      if   hasattr(self, _property.key):                       continue
      if   (_property.default == Property.INIT):               raise Uninitialized_Property (self, _property)
      elif not(isinstance(_property.default, _property.type)): raise Invalid_PropertyDefault(self, _property)
      else:                                                    self.__dict__[_property.key] = _property.default

  def __setattr__(self, key, value):
    if key in self._properties.keys() and not isinstance(value, self._properties.get(key).type):
      raise Invalid_Assignment(self, key, value)
    object.__setattr__(self, key, value)



###########################################################################################################################
#/////             ////////////////////////////////////////////////////////////////////////////////////////////////////////
#/////  ErrorData  ////////////////////////////////////////////////////////////////////////////////////////////////////////
#/////             ////////////////////////////////////////////////////////////////////////////////////////////////////////
###########################################################################################################################

class ErrorData_From_SetAttr:
  def __init__(self, classInstance, key, value):
    properties = classInstance._properties
    self.key         = key
    self.value       = _quote_StringOnly(value)
    self.definedType = properties.get(key).type.__name__ if(key in properties.keys()) else(None)
    self.actualType  = type(value).__name__
    self.className   = classInstance.__class__.__name__

class ErrorData_From_ValidateAttr:
  def __init__(self, classInstance, _property):
    self.key          = _property.key
    self.defaultValue = _quote_StringOnly(_property.default)
    self.definedType  = _property.type.__name__
    self.actualType   = type(_property.default).__name__
    self.className    = classInstance.__class__.__name__



###########################################################################################################################
#/////              ///////////////////////////////////////////////////////////////////////////////////////////////////////
#/////  Exceptions  ///////////////////////////////////////////////////////////////////////////////////////////////////////
#/////              ///////////////////////////////////////////////////////////////////////////////////////////////////////
###########################################################################################################################

###########################
###  @ _set_Attributes  ###
###########################

class Undefined_Property(Exception):
  def __init__(self, classInstance, key, value):
    e = ErrorData_From_SetAttr(classInstance, key, value)
    message = (
      f"\n\t>>> {e.className}.{e.key} = ({e.actualType}) {e.value}"
      f"\n\t{e.className}.{e.key} is not a defined property"
    )
    super(Undefined_Property, self).__init__(message)

class Invalid_Assignment(Exception):
  def __init__(self, classInstance, key, value):
    e = ErrorData_From_SetAttr(classInstance, key, value)
    message = (
      f"\n\t>>> {e.className}.{e.key} = ({e.actualType}) {e.value}"
      f"\n\t{e.className}.{e.key} must be of ({e.definedType}) type"
    )
    super(Invalid_Assignment, self).__init__(message)

################################
###  @ _validate_Attributes  ###
################################

class Invalid_PropertyKey(Exception):
  def __init__(self, classInstance, _property):
    e = ErrorData_From_ValidateAttr(classInstance, _property)
    message = (
      f"\n\t{e.className}.{e.key} is not a valid key."
      f"\n\tKeys must match the following RegEx pattern:"
      f"\n\t\t^[a-zA-Z_][a-zA-Z0-9_]*$"
    )
    super(Invalid_PropertyKey, self).__init__(message)

class Uninitialized_Property(Exception):
  def __init__(self, classInstance, _property):
    e = ErrorData_From_ValidateAttr(classInstance, _property)
    message = f"\n\t{e.className}.{e.key} must be instantiated"
    super(Uninitialized_Property, self).__init__(message)

class Invalid_PropertyDefault(Exception):
  def __init__(self, classInstance, _property):
    e = ErrorData_From_ValidateAttr(classInstance, _property)
    message = (
      f"\n\t{e.className}.{e.key} requires ({e.definedType}) default value"
      f"\n\t({e.actualType}) {e.defaultValue} is not a valid default"
    )
    super(Invalid_PropertyDefault, self).__init__(message)
from functools    import partial as F
from type_managed import TypeManaged, PropertySet, Property



#####################################################################################################
#/////         //////////////////////////////////////////////////////////////////////////////////////
#/////  Utils  //////////////////////////////////////////////////////////////////////////////////////
#/////         //////////////////////////////////////////////////////////////////////////////////////
#####################################################################################################

def test_Partials(*partials, dividerLength=51):

  def get_Partial_AsString(f):
    args = [str(x) for x in f.args]
    for key, value in f.keywords.items():
      if isinstance(value, str): args.append(f"{key}='" + value.replace("\n", "\\n") + "'")
      else:                      args.append(f"{key}={value}"                             )
    args_String = ", ".join(list(args))
    return f"{f.func.__name__}({args_String})"

  for i, f in enumerate(partials):
    print(">>> " + get_Partial_AsString(f))
    try:
      f()
    except Exception as E:
      message = str(E) if str(E).startswith("\n") else f"\n{E}"
      print(f"ERROR: {E.__class__.__name__}{message}")
    else:
      print("PASS")
    if(i < len(partials)-1):
      print(f"{'-'*dividerLength}")



#####################################################################################################
#/////        ///////////////////////////////////////////////////////////////////////////////////////
#/////  Test  ///////////////////////////////////////////////////////////////////////////////////////
#/////        ///////////////////////////////////////////////////////////////////////////////////////
#####################################################################################################

# Property(type, key, default)
#   - if Property.INIT is used as the 'default' argument
#     and a class is instantiated without an argument for that property,
#     the class will throw an Uninitialized_Property exception
#   - `object` can be used for the `type` argument
#     in order to avoid type checking

class ExampleClass_1(TypeManaged):
  _properties = PropertySet(
    Property(int,    "X", 0            ),
    Property(int,    "Y", Property.INIT),
    Property(object, "Z", True         ),
  )

class ExampleClass_2(TypeManaged):
  _properties = PropertySet(
    Property(int, "X", "one"),
  )

class ExampleClass_3(TypeManaged):
  _properties = PropertySet(
    Property(int, "X!", 0),
  )

test_Partials(
  F(ExampleClass_1, X=1, Y=2, Z=3      ),
  F(ExampleClass_1, X=1, Y=2, Z="three"),
  F(ExampleClass_1, X="one", Y=2, Z=3  ),
  F(ExampleClass_1, A=1, Y=2, Z=3      ),
  F(ExampleClass_1, X=1, Z=3           ),
  F(ExampleClass_2                     ),
  F(ExampleClass_3                     ),
)

###  RESULTS: ###
"""
>>> ExampleClass_1(X=1, Y=2, Z=3)
PASS
---------------------------------------------------
>>> ExampleClass_1(X=1, Y=2, Z='three')
PASS
---------------------------------------------------
>>> ExampleClass_1(X='one', Y=2, Z=3)
ERROR: Invalid_Assignment
  >>> ExampleClass_1.X = (str) 'one'
  ExampleClass_1.X must be of (int) type
---------------------------------------------------
>>> ExampleClass_1(A=1, Y=2, Z=3)
ERROR: Undefined_Property
  >>> ExampleClass_1.A = (int) 1
  ExampleClass_1.A is not a defined property
---------------------------------------------------
>>> ExampleClass_1(X=1, Z=3)
ERROR: Uninitialized_Property
  ExampleClass_1.Y must be instantiated
---------------------------------------------------
>>> ExampleClass_2()
ERROR: Invalid_PropertyDefault
  ExampleClass_2.X requires (int) default value
  (str) 'one' is not a valid default
---------------------------------------------------
>>> ExampleClass_3()
ERROR: Invalid_PropertyKey
  ExampleClass_3.X! is not a valid key.
  Keys must match the following RegEx pattern:
    ^[a-zA-Z_][a-zA-Z0-9_]*$
"""

以上是关于python [Python 3.6]带类型管理的属性加载器。更多信息@ https://www.reddit.com/r/Python/comments/641dhn/attribute_loade的主要内容,如果未能解决你的问题,请参考以下文章

anconda + python 3.6安装(以前的anconda)

Clarisse 3.6 python api

如何在 python 3.6 中使用类型提示?

更改列名称类型,Python 3.6

Python 3.6 泛型类型提示

“void”函数中的 NoReturn 与 None - Python 3.6 中的类型注释