Python Descriptors -> 对数据进行校验

描述器的表现

  • 用到3个魔术方法 : _get_()、_set_()、_delete_()

  • 方法签名如下 :

    • object.get(self, instance, owner)
    • object.set(self, instance, value)
    • object.delete(self, instance)

self 指代当前实例, 调用者
instance 是owner的实例
owner 是属性的所属的类

用例子来看

class A:
    def __init__(self):
        self.a1 = 'a1'
        print('A.init')

class B:
    x = A()
    def __init__(self):
        print('B.init')

print(B.x.a1)

b = B()
print(b.x.a1)

运行结果

A.init
a1
B.init
a1

可以看出执行的先后顺序吧

  1. 类加载的时候, 类变量需要先生成而类B 的x 属性是类A 的实例, 所以打印A.init
  2. 然后执行到B.x.a1
  3. 接着实例化并初始化B的实例b
  4. 打印b.x.a1, 会查找类属性b.x, 指向A的实例, 所以返回A实例的属性a1的值

看懂执行流程后在看下面的程序, 对类A做一些改造, 如果在对类A中实现__get__方法, 看变化 …

class A:
    def __init__(self):
        self.a1 = 'a1'
        print('A.init')

    def __get__(self, instance, owner):
        print("A.__get__{}, {}, {}".format(self, instance, owner))

class B:
    x = A()
    def __init__(self):
        print('B.init')

print(B.x)
# print(B.x.a1)	# AttributeError: 'NoneType' object has no attribute 'a1'
b = B()
print(b.x)
# print(b.x.a1)	# AttributeError: 'NoneType' object has no attribute 'a1'

运行结果

A.init
A.__get__<__main__.A object at 0x00000157B0658278>, None, <class '__main__.B'>
None

B.init
A.__get__<__main__.A object at 0x00000157B0658278>, <__main__.B object at 0x00000157B0658898>, <class '__main__.B'>
None
  1. 因为定义了__get__ 方法, 类A 就是一个描述器, 对类B 或者类B 的实例的x 属性读取, 成为对类A 的 实例的访问, 就会调用__get__方法

  2. 如何解决上例中访问报错的问题, 问题来自__get__方法

  3. self, instance, owner 这三个参数是什么意思 ?

    – self 都是A 的实例
    – instance 都是B 类
    – owner 说明

<__main__.A object at 0x00000157B0658278>,  <__main__.B object at 0x00000157B0658898>,  <class '__main__.B'>

针对上面的问题 2, 使用返回值解决, 返回self, 就是A 的实例, 该实例有a1 属性, 返回正常.

class A:
    def __init__(self):
        self.a1 = 'a1'
        print('A.init')

    def __get__(self, instance, owner):
        print("A.__get__{}, {}, {}".format(self, instance, owner))
        return self     
    
class B:
    x = A()
    def __init__(self):
        print('B.init')

此处省略打印结果 …


那么类B 的实例属性也可以 ?

class A:
    def __init__(self):
        self.a1 = 'a1'
        print('A.init')

    def __get__(self, instance, owner):
        print("A.__get__{}, {}, {}".format(self, instance, owner))
        return self

class B:
    x = A()
    def __init__(self):
        print('B.init')
        self.b = A()    # 实例属性指向一个A 的实例

print(B.x)

print('-'*50)

print(B.x.a1)

print('-'*50)

b = B()
print(b.x)

print('-'*50)

print(b.x.a1)

print('-'*50)

print(b.b)  # 没有触发__get__


运行结果

A.init
A.__get__<__main__.A object at 0x0000020EB6998278>, None, <class '__main__.B'>
<__main__.A object at 0x0000020EB6998278>
--------------------------------------------------
A.__get__<__main__.A object at 0x0000020EB6998278>, None, <class '__main__.B'>
a1
--------------------------------------------------
B.init
A.init
A.__get__<__main__.A object at 0x0000020EB6998278>, <__main__.B object at 0x0000020EB6998940>, <class '__main__.B'>
<__main__.A object at 0x0000020EB6998278>
--------------------------------------------------
A.__get__<__main__.A object at 0x0000020EB6998278>, <__main__.B object at 0x0000020EB6998940>, <class '__main__.B'>
a1
--------------------------------------------------
<__main__.A object at 0x0000020EB6998AC8>
  • 从运行结果可以看出, 只有类属性是类的实例才行

描述器定义

  • Python 中, 一个类实现了_get_、_set_、_delete_ 三个方法中任何一个方法就是描述器
  • 如果实现了__get__ 就是 非数据描述符 non-data descriptor
  • 实现了__get__、__set__就是 数据描述符 data descriptor
  • 如果一个类的雷属性设置为描述器, 那么它被称为 owner属主

属性访问顺序

继续用例子来看

class A:
    def __init__(self):
        self.a1 = 'a1'
        print('A.init')

    def __get__(self, instance, owner):
        print("A.__get__{}, {}, {}".format(self, instance, owner))
        return self

class B:
    x = A()
    def __init__(self):
        print('B.init')
        self.x = 'b.x'  # 增加实例属性 x

print(B.x)

print('-'*50)

print(B.x.a1)

print('-'*50)

b = B()
print(b.x)

print('-'*50)

# print(b.x.a1)   # AttributeError: 'str' object has no attribute 'a1'

运行结果

A.init
A.__get__<__main__.A object at 0x000001B1A9D28940>, None, <class '__main__.B'>
<__main__.A object at 0x000001B1A9D28940>
--------------------------------------------------
A.__get__<__main__.A object at 0x000001B1A9D28940>, None, <class '__main__.B'>
a1
--------------------------------------------------
B.init
b.x
--------------------------------------------------
  • b.x 访问到了实例的属性, 而不是描述器

继续修改代码, 为类A 增加__set__ 方法

class A:
    def __init__(self):
        self.a1 = 'a1'
        print('A.init')

    def __get__(self, instance, owner):
        print("A.__get__{}, {}, {}".format(self, instance, owner))
        return self

    def __set__(self, instance, value):
        print("A.__set__{}, {}, {}".format(self, instance, value))
        self.data = value

class B:
    x = A()
    def __init__(self):
        print('B.init')
        self.x = 'b.x'  # 增加实例属性 x

print(B.x)

print('-'*50)

print(B.x.a1)

print('-'*50)

b = B()
print(b.x)

print('-'*50)

运行结果

A.init
A.__get__<__main__.A object at 0x000001E1AA558940>, None, <class '__main__.B'>
<__main__.A object at 0x000001E1AA558940>
--------------------------------------------------
A.__get__<__main__.A object at 0x000001E1AA558940>, None, <class '__main__.B'>
a1
--------------------------------------------------
B.init
A.__set__<__main__.A object at 0x000001E1AA558940>, <__main__.B object at 0x000001E1AA558B00>, b.x
A.__get__<__main__.A object at 0x000001E1AA558940>, <__main__.B object at 0x000001E1AA558B00>, <class '__main__.B'>
<__main__.A object at 0x000001E1AA558940>
--------------------------------------------------
  • 返回变成了a1 , 访问到了描述器的数据

属性查找顺序

  • 实例的__dict__优先于 非数据描述器
  • 非数据描述器优先于实例的__dict__
  • _delete_ 方法有同样的效果, 有了这个方法就是 数据描述器

对实例数据校验

最初实现

class TypeCheck:
    def __init__(self, name, typ):
        self.name = name
        self.type = typ
    def __get__(self, instance, owner):
        print('get')
        if instance:
            return instance.__dict__[self.name]
        return self
    def __set__(self, instance, value):
        print('set')
        if not isinstance(value, self.type):
            raise TypeError
        instance.__dict__[self.name] = value
class Person:
    name = TypeCheck('name', str)   # 硬编码
    age = TypeCheck('age', int)     # 不优雅
    def __init__(self, name:str, age:int):
        self.name = name
        self.age = age


装饰器实现

import inspect

class TypeCheck:
    def __init__(self, name, typ):
        self.name = name
        self.type = typ
    def __get__(self, instance, owner):
        print('get')
        if instance:
            return instance.__dict__[self.name]
        return self
    def __set__(self, instance, value):
        print('set')
        if not isinstance(value, self.type):
            raise TypeError
        instance.__dict__[self.name] = value

def typeinject(cls):
    sig = inspect.signature(cls)
    params = sig.parameters
    for name, param in params.items():
        print(name, param)
        if param.annotation != param.empty:
            setattr(cls, name, TypeCheck(name, param.annotation))
    return cls

@typeinject
class Person:
    # name = TypeCheck('name', str)   # 硬编码
    # age = TypeCheck('age', int)     # 不优雅
    def __init__(self, name:str, age:int):
        self.name = name
        self.age = age



描述器实现

import inspect

class TypeCheck:
    def __init__(self, name, typ):
        self.name = name
        self.type = typ

    def __get__(self, instance, owner):
        print('get')
        if instance:
            return instance.__dict__[self.name]
        return self

    def __set__(self, instance, value):
        print('set')
        if not isinstance(value, self.type):
            raise TypeError
        instance.__dict__[self.name] = value

class TypeInject:
    def __init__(self, cls):
        self.cls = cls
        sig = inspect.signature(cls)
        params = sig.parameters
        for name, param in params.items():
            print(name, param)
            if param.annotation != param.empty:     # 注入类属性
                setattr(cls, name, TypeCheck(name, param.annotation))

    def __call__(self, *args, **kwargs):
        return self.cls(*args, **kwargs)    # 新建一个Person对象

@TypeInject
class Person:  # Person =  TypeInject(Person)

    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age


p1 = Person('tom', 18)
p2 = Person('tom', 20)

运行结果

name name:str
age age:int
set
set
set
set

  • 运行结果没有报错, 且可以看到执行顺序
这段代码有一些问题。首先,在函数签名中,你声明了两个参数 `shot1` 和 `shot2`,但是在函数中并没有使用它们。其次,你使用了一个未定义的变量 `scene_descriptors`。最后,你需要将 `squaredDistances` 变量的类型修改为 `hammingDistances`,因为你在使用汉明距离进行匹配。 如果你想使用布尔型描述子进行匹配,你需要对代码进行一些修改。下面是一个可能的实现方式: ``` void find_match_shot(pcl::PointCloud<pcl::BINARY>::Ptr model_descriptors, pcl::PointCloud<pcl::BINARY>::Ptr scene_descriptors, pcl::CorrespondencesPtr model_scene_corrs) { clock_t start = clock(); // Create a KdTreeFLANN object and set the input cloud pcl::KdTreeFLANN<pcl::BINARY> matching; matching.setInputCloud(model_descriptors); // Loop through all scene descriptors and find the best match in the model descriptors for (size_t i = 0; i < scene_descriptors->size(); ++i) { std::vector<int> neighbors(1); std::vector<int> hammingDistances(1); if (pcl_isfinite(scene_descriptors->at(i).descriptor[0])) { int neighborCount = matching.nearestKSearch(scene_descriptors->at(i), 1, neighbors, hammingDistances); if (neighborCount == 1 && hammingDistances[0] < 0.05f) { pcl::Correspondence correspondence(neighbors[0], static_cast<int>(i), hammingDistances[0]); model_scene_corrs->push_back(correspondence); } } } } ``` 在这个实现中,我假设 `model_descriptors` 和 `scene_descriptors` 是 `pcl::PointCloud<pcl::BINARY>::Ptr` 类型的指针,而 `model_scene_corrs` 是 `pcl::CorrespondencesPtr` 类型的指针。然后,我创建了一个 `KdTreeFLANN` 对象,并将模型描述子作为输入。接下来,我循环遍历所有场景描述子,并在模型描述子中查找最佳匹配。如果找到了匹配,则将对应关系添加到 `model_scene_corrs` 变量中。在这个实现中,我使用了一个硬编码的阈值 `0.05f` 来过滤掉不好的匹配。在实际应用中,你需要根据实际数据特征来设置正确的阈值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值