Chapter-6 Python 中的 OOP

1 | 类 Class

1-1 | 对象

对象 = 属性 attributes + 方法 method,它代表了某种具体事物的唯一实例

象是盒子就像制作盒子的模具

使用 class 定义类:

class Person:
	pass  # 可用于表示一个空类
	
class Person():  # 也可以加上括号
	pass

class Person():
    # 初始化函数
    def __init__(self, name): 
        self.name = name
        self.arr = []

    def func(self):
        self.other_func() # 通过self, 方法可互相调用 
        print('This is a method')

实例化 instantiation

captain = Person('Davy Jones')

获取属性 & 调用方法

>>> captain.name  # data attributes
'Davy Jones'
>>> captain.func()  # method
'This is a method'

方法也是普通函数,因此也具有 __doc__ 等属性

1-2 | self

定义实例方法时,第一个参数必须是 self,其没有任何特殊含义

car = Car()
car.exclaim()

执行这两行代码时,Python 实际做了两件事情:

先查找类Car, 再把 car 对象作为 self 参数传给 Car 类的 exclaim() 方法

相当于:

Car.exclaim(car)

1-3 | 类中方法

  • 普通方法

  • 实例方法 instance method

    self 作为第一个参数的方法

  • 类方法 class method

    作用于整个类,对类做出任何改变会对它所属的 instance 产生影响, 用@classmethod 定义

  • 静态方法 static method

    既不影响类也不影响实例,既不用self也不用cls参数, 类与实例均可调用,用@staticmethod 定义

    **何时使用?**逻辑上相对独立可以封装,与类相关,希望记录在类中

class A:
    count = 0 # 类属性
    def __init__(self):
        self.no = A.count
        A.count += 1 

    def exclaim(self):  # 实例方法
        print('I am an A')

    @classmethod
    def num(cls):  # 类方法,第一个参数必须是cls, cls可调用类特性,写 self 也不出错
        print(f'Count is {cls.count}')

    @staticmethod
    def intro():
        print('This is a static method')

📋也可传入staticmethod() 定义来自类外的函数

def some_function():
    pass

class C:
    method = staticmethod(some_function)

📋实例可以调用实例方法、类方法、静态方法,类只能调用类方法、静态方法

实例
实例方法✔️
类方法 classmethod✔️✔️
静态方法 staticmethod✔️✔️
Classmethod 🆚 Staticmethod
  • 入参不同:类方法第一个入参为 cls, 静态方法无限制

  • 属性可见:类方法可以获取/修改类属性,但静态方法需要改写法 A.intro(A)

  • 定义方式不同:类方法用 @classmethod,静态方法用 @staticmethod

1-4 | 类特性

类与其实例均可定义特性,且相互独立

class Grape:
	color = 'purple'
	
	def __init__(self):
		self.color = 'purple'
		
>>> Grape().color = 'green'
>>> Grape.color
'purple'

1-5 | 类保留名称

  • _下划线开头的对属性或方法,无法被 from mod import *这种代码引入
  • _*_下划线开头结尾的属于解释器定义的 dunder
  • __* 两个下划线开头,类“私有”名称,防止子类覆写父类

“Private” instance variables that cannot be accessed except from inside an object don’t exist in Python

–Python official document

Python 不存在私有变量,但是可采用以上命名惯例予以表示,Python 会自动对其"保护"

class Person():
    def __init__(self, name, age):
        self._age = age
        self.__nombre = name

class Man(Person):
    pass

jax = Man("Jax", 29)
>>> jax._age
29
>>> jax.__nombre
AttributeError: 'Man' object has no attribute '__nombre'

💡双下划线开头的属性可通过 _class__attr 的形式取值、设值,jax._Person__nombre

💻dataclass

🆕Python 3.7+,省去了 __init__方法的构造

from dataclasses import dataclass
@dataclass
class Car:
    color: str
    mileage: float
    automatic: bool

>>> car1 = Car("red", 3812.4, True)

💻isinstance(obj, cls)

检查某 object 是否属于某 class

>>> a = 1
>>> isinstance(a, int)
True

💻vars(obj)

调用 __dict__ 方法,只要底层定义了该方法的任何对象都可以,如模块,类,实例等

2 | 继承 Inheritance

被继承者被称为父类超类基类;继承者被称为子类次类派生类

从已有类中衍生出的新类,添加或修改部分功能,实质是代码复用

class Car:
    pass

class Benz(Car):
    pass

💡过多使用继承会导致代码复杂,不方便维护!建议采用聚合、组合

覆盖 Override:Python 不可能强制隐藏数据,子类的任何同名方法、属性会覆盖父类的

子类中的新方法,父类对象无法调用

💻 issubclass()

语法:issubclass(cls, class_or_tuple)

检查某 class 是否继承某 class , 被继承的class 可以传一个,也可以传多个

>>> issubclass(OrderedDict, dict)
True

3 | 多继承 Multiple Inheritance

class D(A, B, C):
    pass

调用顺序:D -> A -> B -C

假设有两个方法 fun1 fun2,ABC 三类中均定义了这两个方法,若指定某类的某方法可以通过类名调用:

class D(A, B):
    def call_func_from_a(self):
        A.func(self)  # 别忘传入self

3-1 |❓ 不确定先后顺序时怎么办

靠前的先使用

每个 class 都有一个特殊方法 mro()__mro__,返回一个类名列表,用于决定调用方法、属性的先后顺序

# mro() method of builtins.type instance.Return a type's method resolution order.
>>> D.mro()
Out[20]: [__main__.D, __main__.A, __main__.B, object]

3-2 | mixin

实质是一个普通类,作用是为子类添加额外功能,复用代码

class DocMixin:
	def doc(self):
		print(self.__doc__)

class Test(DocMixin):
	"""This is a test class"""
	pass

t = Test()
>>> t.doc()
This is a test class

4 | 调用父类方法 super()

需要父类的某一方法,不要直接复制粘贴代码,不利于维护,父类更改后需手动修改子类

class B(A):
    def __init__(self):
        super().p() # 调用时不用加self
        # 其实相当于 super(B, self).p()

调用爷类方法

class A:
class B(A):
class C(B):
    def foo(self):
        # M1.直接用类 A 调用
        A.func_belong_to_A(self)
        # M2.super() 中写父类 B 调用爷类 A
        super(B, self).func_belong_to_A()

5 | 描述符 Descriptors

Any object which defines the methods __get__(), __set__(), or __delete__()

Descriptor 让对象可以自定义特性查找,存储,删除

Descriptor 是深度理解 Python 的重要基础知识,因为 Python 中很多东西都基于其实现,如函数,方法,类方法,静态方法等

# Descriptor Protocol
__get__(self, obj, obj_type=None) -> object
__set__(self, obj, value) -> None
__delete__(self, obj) -> None    

# 可选
__set_name__

典型用例

  • 返回常量:多个类返回同一个值时,可复用代码

    class Ten:
        def __get__(self, obj, objtype=None):
            return 10
    
    class A:
        num = Ten()
    
  • 返回变量(动态求值)

    class DirectorySize:
        def __get__(self, obj, objtype=None):
            return len(os.listdir(obj.dirname))
    
    class Directory:
        size = DirectorySize()  # 动态查看文件数量   
    
  • 控制数据访问:修改实例数据前,先执行一些业务逻辑,如数据校验

  • 自定义名称:将实例的某个特性的名称分为对外名称和内部名称

    class LoggedAccess:
        def __set_name__(self, owner, name):
            self.public_name = name
            self.private_name = '_' + name
    
        def __get__(self, obj, objtype=None):
            value = getattr(obj, self.private_name)
            return value
    
        def __set__(self, obj, value):
            setattr(obj, self.private_name, value)
    
    class Person:
        name = LoggedAccess()                
    
        def __init__(self, name):
            self.name = name  # 调用 __set_name__
    

6 | 属性 Property

Python 的特性 Attribute 都是公开的,如果不放心直接访问对象特性,可用属性替代

🆚descriptor:property 是高级别的机制,其实现依赖于 descriptor

如何定义

定义属性需要编写 gettersetter ,不设定 setter 方法将无法从外部修改属性

  • 方法一:直接声明方法,再通过property() 进行绑定
class Person():
    def __init__(self, input_name):
        self.the_name = input_name  # 隐藏了属性 _name

    def get_name(self):
        print('This is Getter')
        return self.the_name

    def set_name(self, input_name): 
        print('Name can\'t change!')

    name = property(get_name, set_name) # 获取 name 调用前者,修改调用后者

>>> batman = Person("Bruce Wayne")
>>> batman.name
This is Getter
'Bruce Wayne'
>>> batman.name = "Joker"
Name can't change!
  • 方法二:加装饰器声明
class Person:
    def __init__(self):
        self._name = None

    @property
    def name(self):
        return self._name  # 隐藏了属性 _name

    @name.setter
    def name(self, value):
        self._name = value

    @name.deleter
    def name(self):
        del self._name
        print("name is cleared")

batman = Person()
batman.name = "bruce wayne"
>>> batman.name
'bruce wayne'
>>> del batman.name
name is cleared

※ 但如果能猜到类中有一个 _name的特性,仍可通过直接访问进行修改,属性包裹后也并不完全私密

7 | 多态 Polymorphism

  • 父类声明接口,子类定义具体实现

  • 不同子类对同一行为的不同表现形式

不同类,同名方法,入参相同,结果不同,这是多态的传统形式:

class A:
    def say(self):
        print('This is a A')

class B(A):
    def say(self):
        print('This is a B')

鸭子类型

Python 对于多态更进一步,即使类不相互继承,只要有该方法即可调用

def call_say(obj):
	obj.say()

# 与 A, B 类无关的 C 也有一个方法 say ()
class C:
    def say(self):
        print('This is a C')

>>> call_say(A())
This is a A
>>> call_say(B())
This is a B
>>> call_say(C())
This is a C

这种行为有时被称作鸭子类型 duck typing,该名称源自以下这句俗语。

如果它像鸭子一样走路,像鸭子一样叫,那它就是一只鸭子

8 | 魔术方法 Dunder

以双下划线 __ 开头和结束的方法,主要是 Python 底层方法,由于实际编程中不会采用这种格式命名变量,

示例:obj_A == obj_B 比较等值时会自动调用两个对象所属类中的__eq__方法

这些接口也可自定义

__eq__(self, other)  # self == other  
__ne__(self, other)  # self != other  
__lt__(self, other)  # self < other   
__le__(self, other)  # self <= other
__gt__(self, other)  # self > other   
__ge__(self, other)  # self >= other
__add__(self, other)  # self + other 
__sub__(self, other)  # self - other
__mul__(self, other)  # self * other  
__truediv__(self, other)  # self / other
__floordiv__(self, other)  # self // other
__mod__(self, other)  # self % other
__pow__(self, other)  # self ** other
__len__(self)  # len(self) 

9 | 组合 Composition

不继承类,将某类的实例作为当前类实例的特性,可调用其方法

class Hand:
    def shake_hand(self):
        print('shaking....')
        
class Person:
    def __init__(self, hand):
        self.hand = hand

hand = Hand()
p = Person(hand=hand)
>>> p.hand.shake_hand()
shaking....

END

  • 21
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值