python进阶——5. 实例

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,607评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,239评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,960评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,750评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,764评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,604评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,347评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,253评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,702评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,893评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,015评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,734评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,352评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,934评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,052评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,216评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,969评论 2 355

推荐阅读更多精彩内容

  • 定义类并创建实例 在Python中,类通过 class 关键字定义。以 Person 为例,定义一个Person类...
    绩重KF阅读 3,952评论 0 13
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,975评论 6 13
  • Python进阶框架 希望大家喜欢,点赞哦首先感谢廖雪峰老师对于该课程的讲解 一、函数式编程 1.1 函数式编程简...
    Gaolex阅读 5,499评论 6 53
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,639评论 18 399
  • 感賞我的按摩師,總是那麼關心我,今天去拜訪她,她知道我感冒了還特地幫我煮藥,太窩心了吧,謝謝,謝謝,謝謝 感賞被在...
    童欣怡_中阅读 137评论 0 0