zoukankan      html  css  js  c++  java
  • python 描述符专项

    1. Clean Code in Python中的描述符类学习
    # 具有当前城市的旅行者在程序运行期间跟踪用户访问过的所有城市
    class HistoryTracedAttribute:
        def __init__(self, trace_attribute_name) :
            self.trace_attribute_name = trace_attribute_name  # 需要跟踪的属性 # cities_visited
            self._name = None
    
        def __set_name__(self, owner, name):
            # print(owner, name)  # class Traveller , current_city  # 初始化才调用
            self._name = name
    
        def __get__(self, instance, owner):
            if instance is None:
                return self
            # print('__get__',instance.__dict__,self._name)   # {'name': 'the traveller name', 'cities_visited': ['a', 'b', 'd'], 'current_city': 'd'} current_city
            return instance.__dict__[self._name]
    
        def __set__(self, instance, value):
            print('set start')
            # print('__set__',instance,value)  # <__main__.Traveller object at 0x00000289A33634C8> a  # b # d
            self._track_change_in_value_for_instance(instance, value)
            instance.__dict__[self._name] = value    # current_city = a # b # d
            print('4')
            print(instance.__dict__)
            print('set end')
    
        def _track_change_in_value_for_instance(self, instance, value):
            self._set_default(instance)  # 设置需要跟踪的属性的默认值 'cities_visited': []
            if self._needs_to_track_change(instance, value):
                instance.__dict__[self.trace_attribute_name].append(value)  # 'cities_visited': [] 列表append
                print('3')
                print('_track_change_in_value_for_instance:',instance.__dict__)
    
        def _needs_to_track_change(self, instance, value) -> bool:  # 很巧妙,初始化的时候,字典对应的key current_city不存在:直接添加
            try:
                # print('_needs_to_track_change:', instance.__dict__)
                current_value = instance.__dict__[self._name]
            except KeyError:
                print('2')
                print('_needs_to_track_change:', instance.__dict__)
                return True
            return value != current_value
    
        def _set_default(self, instance):
            # print(self.trace_attribute_name)  # cities_visited
            instance.__dict__.setdefault(self.trace_attribute_name, [])  # 设置默认值 'cities_visited': []
            # print(instance.__dict__)
    
    
    
    class Traveller:    # 所有者类
        # 调用 __set_name__ , 这里current_city是一个类的实例
        # t.current_city = 'b' 后current_city就是字符串了
        current_city = HistoryTracedAttribute("cities_visited")  # 是类的属性,20210420
    
        def __init__(self, name, current_city):
            self.name = name
            print('1')
            # print(self.current_city)  # 报错,__get__ 中 instance.__dict__[self._name],KeyError: 'current_city'
            self.current_city = current_city     # 调用 __set__  #注意这里就算不设置self.current_city,外面也是可以t.current_city访问的
            print('5')
    
    
    t = Traveller('the traveller name','a')
    
    t.current_city = 'b'
    t.current_city = 'd'
    t.current_city = 'b'  # ['a', 'b', 'd', 'b']
    t.current_city = 'b'  # 不增加
    
    
    # HistoryTracedAttribute 类给Traveller对象添加的
    print(t.cities_visited)  # ['a', 'b', 'd']   # 不会调用 __get__
    # print(t.__dict__)
    
    
    # Traveller类自带的
    print(t.name)           # the traveller name  # 不会调用 __get__
    print(t.current_city)   # d  #访问的时候会调用 __get__ ,只有访问描述符才会调用
    
    
    1. robot中testsuite相关代码中,描述符类装饰器比较难以理解,简化源码方便理解如下
    
    #  描述符类, 描述符是作为类的属性而不是实例属性存在的
    #  将一个类中的方法装饰成类中的属性
    class setter(object):
    
        def __init__(self, method):
            self.method = method
            self.attr_name = '_setter__' + method.__name__
            self.__doc__ = method.__doc__
            print('setter: __init__:' ,method)
    
        def __get__(self, instance, owner):  # instance:TestSuite object ;owner:class TestSuite
            if instance is None:
                return self
            print('setter: __get__:', instance, owner)
            return getattr(instance, self.attr_name)    # 返回的是TestSuite的attr_name
            # try:
            #     print('setter: __get__:', instance, owner)
            #     return getattr(instance, self.attr_name)
            # except AttributeError:
            #     raise AttributeError(self.method.__name__)
    
        def __set__(self, instance, value):
            if instance is None:
                return
            print('setter: __set__:', instance,value )
            # 相当于TestSuite 执行 test(self, Keyword)
            setattr(instance, self.attr_name, self.method(instance, value))
    
    
    
    class SetterAwareType(type):
    
        def __new__(cls, name, bases, dct):
            slots = dct.get('__slots__')
            if slots is not None:
                for item in dct.values():
                    if isinstance(item, setter):
                        slots.append(item.attr_name)
            return type.__new__(cls, name, bases, dct)
    
    
    
    def with_metaclass(meta, *bases):
        """Create a base class with a metaclass."""
        # This requires a bit of explanation: the basic idea is to make a
        # dummy metaclass for one level of class instantiation that replaces
        # itself with the actual metaclass.
        class metaclass(type):
            def __new__(cls, name, this_bases, d):
                return meta(name, bases, d)
        return type.__new__(metaclass, 'temporary_class', (), {})
    
    
    class ModelObject(with_metaclass(SetterAwareType, object)):
    
        __slots__ = []    # __slots__变量,来限制该class实例能添加的属性
    
        def copy(self, **attributes):
            pass
    
    
    class Keyword(ModelObject):
    
        @setter
        def parent(self, parent):
            """Parent test suite, test case or keyword."""
            if parent and parent is not self.parent:
                self._sort_key = getattr(parent, '_child_sort_key', -1)
            return parent
    
        @setter
        def tags(self, tags):
            """Keyword tags as a :class:`~.model.tags.Tags` object."""
            return tags
    
    
    class TestSuite(ModelObject):
    
        def __init__(self, name='', doc='', metadata=None, source=None):
            self.parent = None  #: Parent suite. ``None`` with the root suite.
            self._name = name
    
        # test = setter(test) , setter是描述符类,描述符是作为类的属性而不是实例属性存在的
        @setter
        def test(self, Keyword):
            print('TestSuite: test: 赋值的时候调用',Keyword)
            return Keyword
    
        # _setter__test = 'amize'
    
    
        # 源码中都是如下的suites,tests,keywords等
        # @setter
        # def suites(self, suites):
        #     """Child suites as a :class:`~.TestSuites` object."""
        #     return TestSuites(self.__class__, self, suites)
        #
        # @setter
        # def tests(self, tests):
        #     """Tests as a :class:`~.TestCases` object."""
        #     return TestCases(self.test_class, self, tests)
        #
        # @setter
        # def keywords(self, keywords):
        #     """Suite setup and teardown as a :class:`~.Keywords` object."""
        #     return Keywords(self.keyword_class, self, keywords)
    
    
    ''' 以下不实例化也会打印
    setter: __init__: <function Keyword.parent at 0x0000020B824D7E58>
    setter: __init__: <function Keyword.tags at 0x0000020B824D7EE8>
    setter: __init__: <function TestSuite.test at 0x0000020B82520048>'''
    
    
    # 不执行下面的语句也会打印上面的
    suite = TestSuite('name')
    
    keyword = Keyword()
    
    #  @setter 保证suite.test必须赋值后才能运用,因为__get__ 返回的是__init__里面的值
    #  这里会报错:'TestSuite' object has no attribute '_setter__test'
    # print(suite.test)
    # 同上,报错:'TestSuite' object has no attribute '_setter__test'
    # print(suite._setter__test)
    
    
    suite.test = keyword  # 【1. setter: __set__:函数】 ,【2. TestSuite: test: 函数被调用】
    
    
    # 已经赋值了 suite.test = keyword ,不能看class setter中的 __init__了,这里会报错:'Keyword' object has no attribute 'method'
    # print(type(suite.test.method))
    
    
    # _setter__test == suite.test ,区别是 print(suite.test)还要调用__get__
    print(suite._setter__test)
    print(suite.test)        # 【1. setter: __get__ 调用】
    
    print(suite.__dict__)      # {'parent': None, '_name': 'name', '_setter__test': <__main__.Keyword object at 0x0000026458EF11C8>}
    print(dir(suite))
    
    
    
    
    
    1. robot中testsuite相关代码中,描述符类装饰器比较难以理解,简化源码2方便理解如下
    #  将一个类中的方法装饰成类中的属性
    
    import copy
    
    class setter(object):
    
        def __init__(self, method):
            self.method = method
            self.attr_name = '_setter__' + method.__name__
            self.__doc__ = method.__doc__
    
        def __get__(self, instance, owner):
            if instance is None:
                return self
            try:
                return getattr(instance, self.attr_name)
            except AttributeError:
                raise AttributeError(self.method.__name__)
    
        def __set__(self, instance, value):
            if instance is None:
                return
            setattr(instance, self.attr_name, self.method(instance, value))
    
    
    def with_metaclass(meta, *bases):   # 虚拟元类,参数元类meta,返回元类metaclass
        """Create a base class with a metaclass."""
        # This requires a bit of explanation: the basic idea is to make a
        # dummy metaclass for one level of class instantiation that replaces
        # itself with the actual metaclass.
        class metaclass(type):
            def __new__(cls, name, this_bases, d):
                return meta(name, bases, d)  # 常规的return type(name, bases, attr)
        return type.__new__(metaclass, 'temporary_class', (), {})  # return __new__的情况 , 返回【类】,【参数:当前准备创建的类的对象,类的名字,类继承的父类集合,类的方法集合】
    
    
    class SetterAwareType(type):    # 元类
    
        def __new__(cls, name, bases, dct):
            slots = dct.get('__slots__')
            if slots is not None:
                for item in dct.values():
                    if isinstance(item, setter):
                        slots.append(item.attr_name)
            return type.__new__(cls, name, bases, dct)
    
    
    class ModelObject(with_metaclass(SetterAwareType, object)):  # 还是元类
    
        def copy(self, **attributes):
            copied = copy.copy(self)
            for name in attributes:
                setattr(copied, name, attributes[name])
            return copied
    
    
    
    class Message(ModelObject):
        """A message created during the test execution.
    
        Can be a log message triggered by a keyword, or a warning or an error
        that occurred during parsing or test execution.
        """
    
        @setter
        def parent(self, parent):
            if parent and parent is not getattr(self, 'parent', None):
                self._sort_key = getattr(parent, '_child_sort_key', -1)
            return parent
    
  • 相关阅读:
    Delete 语句带有子查询的sql优化
    标量子查询SQL改写
    自定义函数导致的sql性能问题
    Oracle 11G RAC For ASM 利用RMAN COPY进行存储迁移
    WPF 如何控制右键菜单ContextMenu的弹出
    将字符串以用二进制流的形式读入XML文件
    WPF 将数据源绑定到TreeView控件出现界面卡死的情况
    WPF如何实现TreeView节点重命名
    Azure一个Cloud Service支持多个公网地址
    Azure上部署Barracuda WAF集群 --- 2
  • 原文地址:https://www.cnblogs.com/amize/p/14601229.html
Copyright © 2011-2022 走看看