python - OOP进阶

前言

python的class有很多高级特性,除了OOP的三大特性,还在设计模式,自定制类和内存优化等都下了很多功夫。记载一些常用的类特性。

给实例动态绑定方法

不是一定要在定义的时候就要给class写出所有的方法,可以在程序中动态的为实例绑定新的方法。

def func():
    print('hahaha')
class A(object):
    pass
a = A()
a.value = 1
from types import MethodType
a.hahaha = MethodType(func,a)

给类绑定方法的话比较简单,直接绑定就可以,没必要使MethodType去绑定

slots

我们知道,创建一个类时,为这个类创建了一个对象,这个类在实例化后会产生一个 __dict__,这个字典的目的是为了存储实例中的各种属性,你为实例添加变量或者函数都会记录在这个字典中。但是如果在类中,声明了__slots__ ,实例化时不会去创建字典,只会创建一个给定大小的数组用来存储__slots__中指定的属性。

class Person(object):
    __slots__ = ('name','age')

p = Person()
p.name = "Allenware"
p.age = 22

p.country = 'China' #报错

__slots__ 只对当前类有效,对继承的子类没有作用。子类也做此声明,则父类和子类本身的__slot__中声明的名称同时有效。

当然,__slots__ 虽然有着访问限制的作用,但是python中的访问限制全靠自觉,这个特性最主要的用法是在为一个类创建大量实例时,事先知道属性范围,使用__slots__可以节省大量的内存。原因就是开始的原因,定长数组是要比字典省内存的多的。

@property

这个语法糖,设计出来是为了让封装更加的间接。面向对象基础中有这个例子

class Student(object):
    kind = 'school'

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

    def output(self):
        print(self.name)
        print(self.age)
    
    def getage(self):
        return self.age
    
    def setage(self,age):
        if age is int:
            self.age = age
        else: 
            raise ValueError('Not int')

Allen = Student('Allen','22')
Peter = Student('Peter','23')

print(Allen.kind)
print(Peter.kind)

print(Allen.name,Allen.age)
print(Peter.name,Peter.age)

在这个例子中,想要给实例设置年龄的话,必须使用 instance.setage(),虽然可以通过这个方法来检查参数或者添加其他功能,但是不如 instance.age = age 直接绑定来的简单粗暴。而且有时候会导致代码可读性降低。

这时候可以利用@property来改善代码。

class Student(object):
    kind = 'school'

    def __init__(self,name,age):
        self.name = name
        self.age = age  #这里使用self._age会避开装饰器的作用

    @property
    def age(self):
        return self._age
    
    
    @age.setter
    def age(self,age):
        if type(age) is int:
            self._age = age
        else: 
            raise ValueError('Not int')

Allen = Student('Allen',22)
Peter = Student('Peter',23)

Allen.age = 23
print(Allen.age)
Allen.age = 'a'

最后一句的绑定是会报错的,这就是@property 的作用,给属性绑定值的时候自动调用了age的set函数。在你原来的setage()函数上加上@age.setter语法糖,初次之外,还有@age.deleter这个用于删除时的语法糖。不过这三个语法糖紧跟的函数必须是同名的。默认的@property其实就起着get的作用。

上面的例子可以换一种写法

class Student(object):
    kind = 'school'

    def __init__(self,name,age):
        self.name = name
        self.set_age(age)

    def get_age(self):
        return self._age
    
    def set_age(self,age):
        if type(age) is int:
            self._age = age
        else: 
            raise ValueError('Not int')
     
    def del_age(self):
        raise AttributeError('Cannot delete age!')

    age = property(get_age,set_age,del_age)

这个作用是跟之前的写法一样的,通过这个写法就能看出一点@property的原理了。property相当于一个类,为你指定的attribute实例化一个property对象,为get、set、del绑定好对应的三个方法,在你对attribute进行这三个操作时,会触发事先绑定好的方法,这牵扯到了装饰器的原理用法。所以用第一种写法可读性更高。

在用@property 要注意使用时的情景,并不是所有情况都适用,如果你有想要管理的属性,那就是使用 @property 的时机。

__str__

class Person(object):
    def __str__(self):
        return 'Person Object'
    
    __repr__ = __str__
    
pan = Person()
print(pan)

这样,print(instance) 的输出就是和 __str__ 函数中指定的输出了。看起来很简单的小功能,其实是很实用的。可以在 __str__ 中加入你想要的属性信息,很方便调试。

上下文管理

之前提到过with语句,让一个类支持with特性的话,需要自定义__enter__()__exit__()方法

from socket import socket, AF_INET, SOCK_STREAM

class LazyConnection:
    def __init__(self, address, family=AF_INET, type=SOCK_STREAM):
        self.address = address
        self.family = family
        self.type = type
        self.sock = None

    def __enter__(self):
        if self.sock is not None:
            raise RuntimeError('Already connected')
        self.sock = socket(self.family, self.type)
        self.sock.connect(self.address)
        return self.sock

    def __exit__(self, exc_ty, exc_val, tb):    #异常值、异常类型、回溯信息
        self.sock.close()
        self.sock = None

这个类在实例化的时候并不会去创建连接,只有在使用with时,连接的连接和关闭都是自动完成的,所以调用完with之后,可以直接send或者recv进行数据交互了。

上下文管理在资源管理中用的非常广,比较文件、网络socket,还有上次说的线程锁。可以在 __exit__ 中release来保证锁的释放。

枚举类

from eunm import Enum
Animal = Enum('Animal','cat dog ant')

class Animal(Enum):
    cat = 1
    dog = 2 
    ant = 3

__type__

type() 不仅仅用于检测对象的类型,也可以用于创建class对象,接收三个参数

  1. class的名称
  2. 继承的父类,用元组
  3. 属性,字典形式

拥有了type() ,我们可以动态的去创建类,让一个函数返回新建的类。结合上面的枚举类,自己用type()实现一个枚举类

def enum(**enums):
    return type('Enum', (), enums)
 
Numbers = enum(ONE=1, TWO=2, THREE='three')

#人性化一点,可以接受列表的输入
def enum(*sequential, **named):
    enums = dict(zip(sequential, range(len(sequential))), **named)
    return type('Enum', (), enums)
 
Numbers = enum('ZERO', 'ONE', 'TWO')

super()

super用来调用父类的方法,一般是用来保证在本身初始化的时候,父类也被正确的初始化。

class A:
    def __init__(self):
        self.x = 0

class B(A):
    def __init__(self):
        super().__init__()
        self.y = 1

这个可以结合访问限制的 __ 来使用,因为之前说过,双下划线的命名方式的目的就是为了防止父类的属性被子类不小心覆盖了。我们可以在子类中找不到对应属性的时候,使用super()去父类中寻找。

class Test(Father):
    def __init__(self, obj):
        self._obj = obj

    def __getattr__(self, name):
        return getattr(self._obj, name)

    def __setattr__(self, name, value):
        if name.startswith('__'):
            super().__setattr__(name, value) 
        else:
            setattr(self._obj, name, value)

super() 用于多继承时,会避免重复调用某一个父类的初始化方法。而如果直接使用父类的类名来调用初始化方法,则会重复调用。当然super是需要慎用的。

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

推荐阅读更多精彩内容