python进阶——5. 实例

2017-10-30  本文已影响8人  Lemon_Home

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调用参数传入想要调用函数的实例对象。

上一篇下一篇

猜你喜欢

热点阅读