Python-cookbook之类与对象(1)

创建大量对象时节省内存方法

问题:你的程序要创建大量 可能上百万 的对象,导致占用很大的内存

对于主要是用来当成简单的数据结构的类而言,你可以通过给类添加__slots__属性来极大的减少实例所占的内存,比如:

class Date:
    __slots__ = ['year','month','day']
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day
  • 当你定义__slots__后,Python就会为实例使用一种更加紧凑的内容表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__一个不好的地方就是我们不能再给实例添加新的属性了,只能使用在__slots__中定义的那些属性名。好处就是内存占用跟一个元组差不多了,省内存。

讨论
尽管__slots__看上去是一个很有用的特性,很多时候你还是得减少对它的使用冲动。Python的很多特性都依赖于普通的基于字典的实现,用了__slots__的实例就不用调用__dict__属性了。另外,定义__slots__后的类不再支持一些普通类特性了,比如多继承。大多数情况下,你应该只在那些经常被使用到的用作数据结构的类上定义__slots__,比如在程序中需要创建某个类的几百万个实例对象 。
关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管可以达到这样的目的,但是这个并不是它的初衷。__slots__更多的是用来作为一个内存优化工具。

创建可管理的属性

问题:你想给某个实例attribute增加除访问与修改之外的其他处理逻辑,比如类型检查或合法性验证。

class Person:
    def __init__(self, first_name):
        self.first_name = first_name
        
    # Getter function
    @property
    def first_name(self):
        return self._first_name
    
    # Setter function
    @first_name.setter
    def first_name(self, value):
        if not isinstance(value, str):
            raise TypeError('Expected a string')
        self._first_name = value
    
    # Deleter function(optional)
    @first_name.deleter
    def first_name(self):
        raise AttributeError('Can not delete attribute')
  • 上述代码中有三个相关联的方法,这三个方法的名字都必须一样。第一个方法是一 个getter函数,它使得first_name成为一个属性。其他两个方法给first_name属性添加了setter和deleter函数。需要强调的是只有在first_name属性被创建后,后面的两个装饰器@first_name.setter和@first_name.deleter才能被定义。
  • property的关键特征就是它看上去跟普通attribute没什么两样,但是访问这个property时会自动触发相应的setter,getter, deleter方法

讨论
一个property属性其实就是一系列相关绑定方法的集合。如果你去查看拥有property的类,就会发现property本身的fget、fset和fdel 属性就是类里面的普通方法。
eg:
In [148]: Person.first_name.fset
Out[148]: <function main.Person.first_name(self, value)>
In [149]: Person.first_name.fget
Out[149]: <function main.Person.first_name(self)>

class Person:
    def __init__(self, first_name):
        self.set_first_name(first_name)

    # Getter function
    def get_first_name(self):
        return self._first_name

    # Setter function
    def set_first_name(self, value):
        if not isinstance(value, str):
            raise TypeError('Expected a string')
        self._first_name = value

    # Deleter function(optional)
    def del_first_name(self):
        raise AttributeError('Can not delete attribute')

    # Make a property from existing get/set/del methods
    name = property(get_first_name, set_first_name, del_first_name())
  • properties还是一种定义动态计算attribute的方法。这种类型的attribute并不会 被实际的存储,而是在需要的时候计算出来。

调用父类的方法

问题:你想在子类中调用父类的某个已经被覆盖的方法。

  • 为了调用父类 超类 的一个方法,可以使用super()函数,比如:
class A:
    def spam(self):
        print('A.spam')


class B(A):
    def spam(self):
        print('B.spam')
        super().spam()  # call parent spam()
  • super()函数的一个常见用法是在__init__()方法中确保父类被正确的初始化了:
class A:
    def __init__(self):
        self.x = 0


class B(A):
    def __init__(self):
        super().__init__()
        self.y = 1
  • super()的另外一个常见用法出现在覆盖python特殊方法的代码中,比如:
class Proxy:
    def __init__(self, obj):
        self._obj = obj

    # Delegate attribute look up to internal obj
    def __getattr__(self, name):
        return getattr(self._obj, name)

    # Delegate attribute assignment
    def __setattr__(self, name, value):
        if name.startswith('_'):
            super().__setattr__(name, value)
        else:
            setattr(self._obj, name, value)

在上面代码中, __setattr__()的实现包含一个名字检查。如果某个属性名以下划线_开头,就通过super()调用原始的__setattr__(),否则的话就委派给内部的 代理对象self.obj去处理。这看上去有点意思,因为就算没有显式的指明某个类的父类(个人认为这肯定是调用了默认基类object的方法了,其实还是有父类的),super()仍然可以有效的工作。

  • 上述例子中,B中也可以直接使用父类名来调用如A.__init__(self)但这样涉及复杂多继承情况就会有可能重复执行父类方法了(eg: A(Base), B(Base), C(A, B)这样的情况下C的init里同时调用了A和B的同个方法,A、B这个方法又用了 Base来直接调用Base方法)

  • 当你使用super()函数时, Python会在MRO列表(C3线性化算法来实现的)上继续搜索下一个类。只要每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次。这也是为什么在第二个例子中你不会调用两次Base.__init__()的原因。

class Base:
    def __init__(self):
        print('Base.__init__')


class A(Base):
    def __init__(self):
        super().__init__()
        print('A.__init__')


class B(Base):
    def __init__(self):
        super().__init__()
        print('B.__init__')


class C(A, B):
    def __init__(self):
        super().__init__()  # Only one call to super() here
        print('C.__init__')


C.mro()
# [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, 
# <class '__main__.Base'>, <class 'object'>]

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

推荐阅读更多精彩内容