美文网首页
python进阶——5. 实例

python进阶——5. 实例

作者: Lemon_Home | 来源:发表于2017-10-30 23:19 被阅读8次

    5.1 继承内置不可变对象

    python内置的tuple是不可变的,如果想一个tuple的子类,但是只接受int型和大于0的该如何实现。

    class IntTuple(tuple):
    
        def __new__(cls, iterable):
            g = (x for x in iterable if isinstance(x, int) and x > 0)
            return super(IntTuple, cls).__new__(cls, g)
    
        def __init__(self, iterable):
            super(IntTuple, self).__init__()
    
    
    if __name__ == "__main__":
        int_tuple = IntTuple([-1, 1, 3, [3, 4, 5], 8])
        print(int_tuple)
    
    (1, 3, 8)
    

    需要注意的是,init方法是在new这个实例化方法之后执行,所以在init对传入的参数进行操作的时候其实也是不可改变的。只能在new方法中对传入的参数进行操作,创造一个包含对应条件的生成器,然后调用父类的new方法,此时传入的生成器就对应了init方法中的iterable。

    5.2 节省内存方式创建实例

    如果在某些场景下需要创建许多实例时,对内存的考量也是十分重要的。使用slots属性来声明类的所有属性能够有效地节省实例所占用的内存资源。

    import sys
    class User1:
        def __init__(self, id, name, addr, sex):
            self.id = id
            self.name = name
            self.addr = addr
            self.sex = sex
    
    
    class User2:
        __slots__ = ['id', 'name', 'addr', 'sex']
    
        def __init__(self, id, name, addr, sex):
            self.id = id
            self.name = name
            self.addr = addr
            self.sex = sex
    
    if __name__ == "__main__":
        user1 = User1(1, 'dai', "bj", 'male')
        user2 = User2(2, 'blue', 'sy', 'male')
        print(set(dir(user1)) - set(dir(user2)))
        print(user1.__dict__)
        print(sys.getsizeof(user1.__dict__))
        print(sys.getsizeof(user2))
    
    {'__dict__', '__weakref__'}
    {'id': 1, 'name': 'dai', 'addr': 'bj', 'sex': 'male'}
    192
    72
    

    两种方式创建类的实例,首先列出两个实例的属性差集,可以看出user1多出了dict属性,此属性是为了保存实例的属性进行的映射,可以方便动态增加、删改实例属性的。调用sys库的getsizeof可以查看对象的内存大小。可以看出光user1的dict的内存就占用了192的字节,而user2总共占用了72个字节,其内存占用的效率还是很明显的。
    使用 slots还有另一点的考虑,能够禁止随意地更改实例的属性值,在很多第三方库中都对其有应用。

    5.3 让对象支持上下文管理

    像打开文件可以通过with语句来控制,这样的好处就是防止遗忘掉close方法释放资源,调用更加简便。如何使对象能够应用上with语句,这就涉及到让对象支持上下文的操作,其中关键要实现两个方法enterexit

    class Client:
        def __init__(self):
            print('init')
    
        def start(self):
            # raise Exception('Test')
            print('start')
    
        def stop(self):
            print('stop')
    
        def __enter__(self):
            self.start()
            return self
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            self.stop()
            return
    
    
    if __name__ == "__main__":
        with Client() as client:
            print('with')
    
    init
    start
    with
    stop
    

    可以看出with语句最先执行的也是对象的init方法,然后执行enter所定义的方法,其中return的值就是with中as后面的值。最后在with中的方法执行结束之后,会执行对象中定义的exit方法,后面的参数是捕获的异常相关。在enter或者 exit方法内抛出异常,会直接向上抛出,结束掉with的语句。

    5.4 可管理的对象属性

    在java中,提倡对对象的属性设置、取值使用setter和getter方法,这样的好处是提高封装性,其他用户不可随意修改。在python中,有两种方式可以设置、取出属性,一种是传统的setter、getter方法,另一种是通过类.属性操作,能够实现这样的操作是因为在python中一切都是对象。可以看出第二种方式调用起来非常简便,但是存在很多其他问题,例如想要的属性是int,但是通过.操作设置的是str,对于程序来说不会报错,只是输出的结果不是想要的,这样会造成很多不必要的问题。

    比较好的方法是通过使用property方法来简化操作并能很好保证其安全性。

    class User:
        def __init__(self):
            self.id = None
    
        def set_id(self, id):
            if isinstance(id, int):
                self.id = id
            else:
                raise Exception('Wrong type')
    
        def get_id(self):
            return self.id
    
        ID = property(get_id, set_id)
    
    
    if __name__ == "__main__":
        user = User()
        user.id = 1
        print(user.id)
        # user.set_id('123')
        user.set_id(1)
        print(user.ID)
        user.ID = 2
        print(user.ID)
    
    1
    1
    2
    

    可以看出将setter和getter方法作为property的参数,在外部调用时也可以通过.操作来访问,既能提供简单调用又能保证其安全性。

    5.5 让类支持比较操作

    如果让两个实例直接进行运算符操作,例如比较两个正方形的面积用>, <=等符合直接比较会更加简便,这就需要对象对操作符进行重载操作。

    在python中,重载方法lt, gt, le, ge, eq, ne,其含义依次为X<Y,X>Y,X<=Y,X>=Y, X==Y,X!=Y

    
    class Rectangle:
        def __init__(self, w, h):
            self.w = w
            self.h = h
    
        def area(self):
            return self.w * self.h
    
        def __lt__(self, other):
            return self.area() < other.area()
    
        def __eq__(self, other):
            return self.area() == other.area()
    
    if __name__ == "__main__":
        rect1 = Rectangle(3, 4)
        rect2 = Rectangle(4, 5)
        print(rect1 > rect2)
    

    还有另一种方法就是使用total_ordering装饰器来简化操作。

    from functools import total_ordering
    
    @total_ordering
    class Rectangle:
        def __init__(self, w, h):
            self.w = w
            self.h = h
    
        def area(self):
            return self.w * self.h
    
        def __lt__(self, other):
            return self.area() < other.area()
    
        def __eq__(self, other):
            return self.area() == other.area()
    
    if __name__ == "__main__":
        rect1 = Rectangle(3, 4)
        rect2 = Rectangle(4, 5)
        print(rect1 > rect2)
        print(rect1 != rect2)
    

    可以看到,并没有重载!=比较符,但是通过@total_ordering依然能够获取到对应得操作符,前提是已经有两个重载实现,它就会自动地进行其他比较符的实现。

    5.6 使用描述符对属性类型进行检查

    在java等静态语言中,数据的类型需要在使用之前声明,编译器会进行检查,但是对于python等动态语言来说,属性数据类型是可以不用提前声明的,这样可能会造成一定的误操作影响。

    在python中可以通过实现getsetdelete方法,之后通过简单的操作符就可以有效地实现类似java中的效果。

    class Attr:
        def __init__(self, name, type_):
            self.name = name
            self.type_ = type_
    
        def __get__(self, instance, owner):
            return instance.__dict__[self.name]
    
        def __set__(self, instance, value):
            if not isinstance(value, self.type_):
                raise TypeError("wrong type!")
            instance.__dict__[self.name] = value
    
        def __delete__(self, instance):
            del instance.__dict__[self.name]
    
    class User:
        name = Attr("name", str)
        age = Attr("age", int)
    
    
    if __name__ == "__main__":
        user = User()
        user.name = 'blue'
        print(user.name)
        user.age = '11'
    

    可以看出,赋值语句调用的是set可以在其中对值进行类型检查,取值语句调用的是get语句。这样,当赋值的数据类型不是所定义类型,就会抛出异常。

    5.7 循环调用垃圾管理

    python的垃圾回收机制是引用计数法,当引用计数为0时,触发gc操作。有些特殊情况类似两个对象循环引用彼此的方法、属性,此时引用计数是一直递增的,所以当想要回收掉这两个对象时,是无法自动触发gc的。

    class Data:
        def __init__(self, value, owner):
            self.value = value
            self.owner = owner
    
        def __del__(self):
            print('data del')
    
    class Node:
        def __init__(self, value):
            self.value = Data(value, self)
    
        def __del__(self):
            print('node del')
    
    if __name__ == "__main__":
        node = Node(100)
        input("wait...")
    

    创建两个类,Data和Node,两者在init方法中分别调用彼此,在main方法中使用input方法,在输入回车后会进行停止操作,但是发现无法调用各自的del方法,因为彼此的引用计数一直是递增,所以无法触发gc。

    在此情况下,需要创建一种能访问对象但是不增加引用计数的对象,通过使用weakref弱引用库来解决此问题。

    import weakref
    
    class Data:
        def __init__(self, value, owner):
            self.value = value
            self.owner = weakref.ref(owner)
    
        def __del__(self):
            print('data del')
    
        def __str__(self):
            print(self.owner())
    
    class Node:
        def __init__(self, value):
            self.value = Data(value, self)
    
        def __del__(self):
            print('node del')
    
    if __name__ == "__main__":
        node = Node(100)
        input("wait...")
    
    wait...
    node del
    data del
    

    可以看出,在对对象进行引用操作时,通过弱引用方式weakref.ref,然后在对其属性进行操作时使用类似方法调用。此时,输入回车就能正常进行gc操作了。

    5.8 通过字符串调用函数

    在python中可以通过方法名的字符串来调用此方法,所利用的是operator库的methodcaller。

    from operator import methodcaller
    
    s = "adb123qwer456"
    
    print(s.find('q', 3))
    print(methodcaller('find', 'q', 3)(s))
    
    6
    6
    

    在methodcaller中的第一个参数就是想要调用函数的字符串格式,接下来的参数是需要调用函数的参数,之后对methodcaller调用参数传入想要调用函数的实例对象。

    相关文章

      网友评论

          本文标题:python进阶——5. 实例

          本文链接:https://www.haomeiwen.com/subject/jbqypxtx.html