美文网首页
Python OOP

Python OOP

作者: 李霖弢 | 来源:发表于2019-12-16 16:52 被阅读0次

    面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数。

    面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。

    在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。

    class Student(object):
        __slots__=("__name" , "score")
        age = 18
        def __init__(self, name, score):
            self.__name = name
            self.score = score
        def print_score(self):
            print(self.__name , self.score , Student.age)
    

    类中的函数(包括构造函数),第一个形参都指向实例自身

    成员

    成员的增删改查
    • 通过dir()可以获取一个对象所有的成员的List
    >>> dir('ABC')
    ['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']
    
    • 通过hasattr()setattr()getattr()判断/获取/赋值指定成员
    >>> hasattr(obj, 'x')
    True
    >>> obj.x
    9
    >>> getattr(obj, 'y') # 获取属性'y' 没有则报错
    >>> getattr(obj, 'y', 20) # 获取属性'y' 没有则使用默认值20
    >>> setattr(obj, 'y', 19) # 设置一个属性'y'
    
    • 通过del可以删除实例的成员
    del jack.score
    
    私有成员

    在类中定义的成员,如以__开头,则为私有成员
    jack实例的Student类中的__name,在类外会被解释器变为_Student__name(不同版本解释器可能不一样)
    因此外部无法访问jack.__name,却能访问jack._Student__name,如对jack.__name赋值也不会覆盖jack._Student__name
    既以__开头又以__结尾的视为特殊成员,不受此影响

    _开头的成员一般约定为私有成员,但解释器不会对其做任何操作

    特殊成员

    既以__开头又以__结尾的成员,通常不需要人为声明

    • __len__方法
      len()函数内部,它自动去调用该对象的__len__()方法,所以,下面的代码是等价的:
    >>> len('ABC')
    3
    >>> 'ABC'.__len__()
    3
    
    • __slots__
    • __init__()
    • __class__
      该对象的类型(类则为type,实例则为其类)
      可以通过self.__class__.调用类属性
    • __str__()__repr__()
      变量/对象被print()时输出的字符串
    • __iter__()__next__()
      使类可以被for...in循环
    • __getitem__()__setitem__()__delitem__()
      把对象视作listtupledict进行赋值/删除成员
    class Test():
        age = 18
        def __getitem__(self, item):
            return getattr(self, item)
        def __setitem__(self, key, value):
            setattr(self, key, value)
    test = Test()
    test["age"] = 88
    print(test.age)  # 88
    print(test["age"])  # 88
    
    • __getattr__()
      没有找到的属性可以交给__getattr__()进行后续处理,而不报错
      存在的属性不会进入__getattr__()逻辑
    class Student(object):
    
        def __getattr__(self, attr):
            if attr=='age':
                return lambda: 25
    s.age()#25
    
    • __call__()
      使实例本身可以如同一个函数被调用
    class Student(object):
        def __init__(self, name):
            self.name = name
    
        def __call__(self):
            print('My name is %s.' % self.name)
    
    s = Student('Michael')
    s() # self参数不要传入 My name is Michael.
    

    通过callable()函数,我们就可以判断一个对象是否是“可调用”对象。

    类成员、实例成员、静态方法

    不同于JS和C#,python当试图获取/判断实例的某个成员是否存在时,若不存在则会取得其class的同名成员,因此建议不要对实例属性和类属性使用相同的名字
    该特性对hasattrgetattrdir均有效,但可以通过 <类名>.__dict__<实例>.__dict__ 可以获取类和实例的属性字典,以查看该属性是属于类还是实例

    • 两种属性

      • 类属性
        直接在类中声明
      • 实例属性
        通常在方法中通过self.进行声明
    • 三种方法,实例对象和类对象都可以调用

      • 实例方法
        用于给实例调用的方法,其实还是在类上,可以被类的__dict__获取
        第一个参数必须是实例对象(一般约定为self),通过它来传递实例的属性和方法;
      • 类方法
        使用装饰器@classmethod。第一个参数必须是当前类对象(一般约定为cls),通过它来调用类的方法或修改类的属性
      • 静态方法
        使用装饰器@staticmethod。参数随意,没有selfcls参数,但是方法体中不能使用类或实例的任何属性和方法,一般用于和类对象以及实例对象无关的代码;
    class CocaCola:
        formula = ['caffeine', 'sugar', 'water', 'soda']
        def drink(self):
            print('Energy!')
        @classmethod
        def drink2(cls):
            print('Energy2!')
        @staticmethod
        def drink3():
            print('Energy3!')
    
    coke = CocaCola()
    coke.drink()
    CocaCola.drink(coke)
    coke.drink2()
    CocaCola.drink2()
    coke.drink3()
    CocaCola.drink3()
    
    限制成员内容

    通过对类成员__slots__赋值一个tuple,可以限制该class能添加的实例属性
    注意__slots__不会影响子类,除非在子类中也定义__slots__,此时子类实例允许定义的属性就是自身的__slots__加上父类的__slots__

    限制成员读写
    • @property 装饰器规定了成员的读取,并自动创建@XXX.setter装饰器
    • @XXX.setter会对成员写入进行必要检查
    • 当只有@property时为只读属性
    class Student(object):
    
        @property
        def birth(self):
            return self._birth
    
        @birth.setter
        def birth(self, value):
            self._birth = value
    
        @property
        def age(self):
            return 2015 - self._birth
    

    继承和多态

    继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写。

    • Python3中,类默认继承object,因此写或不写都一样
    • Python2中若不写,会少获得一些方法

    动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。

    多重继承
    class Dog(Mammal, Runnable):
        pass
    

    通常,用于给一个类增加额外功能的类,其类名以MixIn结尾,这种设计也称为MixIn。

    JS(TS)、C#、Python中类继承时父类构造函数的调用情况
    • JS
      可以显式在子类的constructor中调用super() (TS中为必须调用)
      如果子类未声明constructor则会在隐式定义的constructor中调用super()执行父类构造函数
    • C#
      子类静态构造函数 => 父类静态构造函数 => 父类实例构造函数 => 子类实例构造函数
    • Python
      通常在子类的__init__函数中调用super().__init__()
      如无调用则不会触发父类构造函数

    枚举类

    通过Enum类实例化直接生成枚举
    from enum import Enum
    Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
    for name, member in Month.__members__.items():
        print(name, '=>', member, ',', member.value)
    

    其中value属性则是自动赋给成员的int常量,默认从1开始计数。

    Enum派生出自定义类自定义枚举

    @unique用于检查没有重复值

    from enum import Enum, unique
    @unique
    class Weekday(Enum):
        Sun = 0 # Sun的value被设定为0
        Mon = 1
        Tue = 2
        Wed = 3
        Thu = 4
        Fri = 5
        Sat = 6
    print(Weekday.Mon== Weekday["Mon"])#True
    print(Weekday.Mon== Weekday(1))#True
    print(Weekday.Tue.value)#2
    

    元类

    type

    type是所有类(包括自己)的类型,因此type()函数可以查看一个类型或变量的类型,也可以创建一个新类

    print(type(Hello))#<class 'type'>
    

    依次传入类名、继承的类、类的静态成员,可用于动态创建类

    def fn(self, name='world'): # 先定义函数
    ...     print('Hello, %s.' % name)
    ...
    >>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
    >>> h = Hello()
    >>> h.hello()
    Hello, world.
    >>> print(type(Hello))
    <class 'type'>
    >>> print(type(h))
    <class '__main__.Hello'>
    
    metaclass 元类

    metaclass允许创建类或者修改类,可以把类看成是metaclass创建出来的“实例”。
    通常metaclass的类名总是以Metaclass结尾

    # metaclass是类的模板,所以必须从`type`类型派生:
    class ListMetaclass(type):
        def __new__(cls, name, bases, attrs):
            attrs['add'] = lambda self, value: self.append(value)
            return type.__new__(cls, name, bases, attrs)
    
    # 使用ListMetaclass定义类的时候需传入关键字参数metaclass:
    class MyList(list, metaclass=ListMetaclass):
        pass
    

    如此,则创建MyList时,要通过ListMetaclass.__new__()

    • 其中__new__()方法接收到的参数依次是:
      1. 当前准备创建的类的对象;
      2. 类的名字;
      3. 类继承的父类集合;
      4. 类的方法集合。
    • metaclass会隐式地继承到子类

    相关文章

      网友评论

          本文标题:Python OOP

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