Python(13)面向对象

文章目录

  • 面向对象
    • 封装
    • 继承
    • 组合
    • 构造函数 init
    • 重写
    • super
    • Mix-in
    • 多态
    • 私有变量、私有方法和__slots__
      • 私有变量
      • 私有方法
      • __ slots __
    • 魔法方法(类的生命周期钩子)
      • __ new __ (cls,...)
      • __ del __ (self)
      • 对象的重生
        • 全局变量重生
        • 闭包重生
      • 属性相关的魔法方法(hasattr()、getattr()、setattr()、delattr())
        • getattr和hasattr 的魔法方法 __ getattribute __ 、 __ getattr __
        • setattr 的魔法方法__ setattr __
          • super 避免递归
          • __ dict __ 避免递归
        • delattr 的魔法方法__ delattr __
      • 索引、切片、迭代协议
        • 索引和切片
        • 迭代协议
      • in 和 not in 的魔法方法__ contains __
      • 比较运算符
    • property()和描述符
      • property
      • 描述符
    • 类方法和静态方法
      • 类方法
      • 静态方法
      • 对比

面向对象

封装

面向对象也是一种代码 封装 的方法

对象 = 属性(静态特征) + 方法(能做的事)

创建对象前通过类将相关的属性和方法打包到一起,然后通类来生成相印的对象

好比工厂在批量生产前会先制作一个模具,而类就是这个模具

首先我们需要创建一个类(class),类可以理解成摸具,更具这个摸具我们可以创造很多相似的对象。

class Animal():
    eyes = 2
    legs = 4

    def run(self):
        print("我在运动")

    def call(self):
        print("我在叫")

使用我们的类创建对象并使用内部方法和属性

...

a1 = Animal()
a1.run()  # 我在运动
a1.call()  # 我在叫
print(a1.legs)  # 4
a1.legs = 3
print(a1.legs)  # 3

两个对象之间互不干扰

a1 = Animal()
a2 = Animal()
a1.legs = 3
print(a1.legs)  # 3
print(a2.legs)  # 4

继承

可以使用现有类的所有功能,并且在无需重新编写代码的情况下对这些代码进行扩展

通过继承创建的新类我们称之为子类,被继承的类称之为父类或者基类

# 父类
class Animal():
    eyes = 2
    legs = 4

    def run(self):
        print("我在运动")

    def call(self):
        print("我在叫")


class Cat(Animal):
    def call(self):
        print("猫在叫")


c = Cat()
c.run()
c.call()
  • isinstance()
    • 判断对象是不是属于某个类
  • issubclass()
    • 检测一个类是否为某个类的字类
...

print(isinstance(c, Cat))  # T
print(isinstance(c, Animal))  # T
print(issubclass(Cat, Animal))  # T
print(issubclass(Animal, Cat))  # F

组合

我们可以把不相关的类放到另外的一个类中一起调用

class Animal():
    eyes = 2
    legs = 4
    name = ""

    def run(self):
        print("我在运动")

    def call(self):
        print("我在说话")


class Cat(Animal):
    name = "猫"
    varieties = ""

    def call(self):
        print("喵喵喵")


class Dog(Animal):
    name = "狗"

    def call(self):
        print("油猴!")


class PetShop():
    c = Cat()
    d = Dog()

    def call(self):
        self.c.call()
        self.d.call()


pet = PetShop()
pet.call()
"""
  喵喵喵
  油猴!
"""

构造函数 init

我们在编写类的时候可以使用 init 来接收外部传入的参数

class Class():
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def add(self):
        print(self.x + self.y)

    def mul(self):
        print(self.x * self.y)


c = Class(4, 5)

c.add()
c.mul()

重写

如果我们对于父类的某个属性或某个方法不满意的话完全可以重写写一个同名的属性或方法对其进行覆盖这种行为我们称之为 重写

class Animal():
    eyes = 2
    legs = 4

    def run(self):
        print("我在运动")

    def call(self):
        print("我在叫")


class Cat(Animal):
    def call(self):
        print("猫在叫")

如上代码所示,猫类继承动物类,内部有和动物类同名的方法,这样就实现了猫类对动物类call方法的重写

super

我们可以使用super调用父类中的方法,这样更安全。

class Animal():
    def __init__(self, eyes, legs):
        self.eyes = eyes
        self.legs = legs

    def run(self):
        print("我在运动")

    def call(self):
        print("我在叫")


class Cat(Animal):
    def __init__(self, eyes, legs):
        super(Cat, self).__init__(eyes, legs)

    def call(self):
        print("喵喵喵")
        super(Cat, self).call()


c = Cat(4, 5)
c.call()
"""
  喵喵喵
  我在叫
"""

Mix-in

这个概念类似于插件,在不改变原本代码的情况下利用多继承让类多出一些功能

class Animal():
    def __init__(self, eyes, legs):
        self.eyes = eyes
        self.legs = legs

    def run(self):
        print("我在运动")

    def call(self):
        print("我在叫")


class Cat(Animal):
    def __init__(self, eyes, legs):
        super(Cat, self).__init__(eyes, legs)

    def call(self):
        print("喵喵喵")
        super(Cat, self).call()

如上代码所示,我现在想让猫类可以飞,如何最小的改动代码呢?

...


class FlyMixin():
    def fly(self):
        print("我会飞")


class Cat(FlyMixin, Animal):


    ...

只需要在猫类前创建一个飞的mixin 像插件一样直接继承就可以了

多态

当我们使用 + 和 * 的时候我们会发现当两边的类型发生变化时,结果也会发生变化

print(1 + 1)
print("1" + "1")

有类似特征的还有 len()

len("123")
len(['123'])

这种传入不同类型数据,返回不同结果的性质我们叫做多态。

首先我们要知道我门传入的都是对象,不同的对象。我们来构建类似的方法

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

    def show(self):
        print(f"我是{self.name},今年{self.age}")


class Cat(Animal):
    pass


class Dog(Animal):
    pass


class Pig(Animal):
    pass


c = Cat('cat', '1')
d = Dog('dog', '1')
p = Pig('pig', '1')


def animal(obj):
    obj.show()


animal(c)
animal(d)
animal(p)

私有变量、私有方法和__slots__

私有变量

我们都知道,class中的属性和方法,外部都可以通过对象直接调用,如果们不想被直接调用可以这样写

# coding=utf-8
class A():
    def __init__(self, a):
        self.__a = a

    def get_a(self):
        return self.__a

    def set_a(self, a):
        self.__a = a
        return self.__a


a = A(1)
# print(a.__a)  # AttributeError: 'A' object has no attribute '__a'
print(a.get_a())    # 1
print(a.set_a(2))   # 2

我们这样就只能通过我们指定的接口来访问了。但真的如此么?

...
print(a.__dict__)   # {'_A__a': 2}
print(a._A__a)  # 2

我们发现其实__a 只是被换了名字。

私有方法

私有方法就是在方法名前去添加__

...
    def __b_func(self):
        print("func")

a._A__b_func()  # func

__ slots __

为什么python的对象可以动态的让我们添加属性呢?是因为对象的属性由dict存放.

class A():
    def __init__(self, a):
        self.a = a

a = A(1)
print(a.__dict__)   # {'a': 1}
a.z = 30
print(a.__dict__)   # {'a': 1, 'z': 30}

我们甚至可以通过修改这个__ dict __ 来修改对象的属性

class A():
    def __init__(self, a):
        self.a = a


a = A(1)
print(a.__dict__)  # {'a': 1}
a.z = 30
print(a.__dict__)  # {'a': 1, 'z': 30}
a.__dict__['z'] = 50
print(a.z)  # 50
a.__dict__['un'] = 55
print(a.un)  # 55

如果我们不需要动态修改对象的属性这样会造成内存空间的浪费,我们可以使用 __ slots __

class A():
    __slots__ = ["a", "b"]

    def __init__(self, a):
        self.a = a


a = A(1)
a.b = 4
a.c = 3 # AttributeError: 'A' object has no attribute 'c'

魔法方法(类的生命周期钩子)

魔法方法: 在类中不需要我们显性调用的方法就是魔法方法 比如 __ init __ ()

__ new __ (cls,…)

new 也是一个魔法方法,他的作用就是创建一个类的实例,将其传递给 init 方法。 self 就是其返回的

class CapStr(str):
    def __new__(cls, string):
        string = string.upper()
        return super().__new__(cls, string)


c = CapStr("python")
print(c)  # PYTHON

__ del __ (self)

del 在对象被销毁后会调用

class C():
    def __init__(self):
        print("我来拉~")

    def __del__(self):
        print("我走拉~")


c = C()  # 我来拉~
del c  # 我走拉~

Python的垃圾回收机制,当一个对象没有任何引用的时候才会将其销毁。

也就是说 我们直接使用 del 删除可能不会触发 __ del __

class C():
    def __init__(self):
        print("我来拉~")

    def __del__(self):
        print("我走拉~")

    def call(self):
        print("呦吼~")


c = C()  # 我来拉~
d = c
del c
d.call()  # 呦吼~
del d  # 我走拉~

对象的重生

对象的重生 不推荐使用了解原理就行

对象被del删除前将self送出去对象就算重生了

全局变量重生

class C():
    def __init__(self, name):
        self.name = name
        print("我来拉~")

    def __del__(self):
        print("我走拉~")
        global x
        x = self


c = C("python")  # 我来拉~
print(c)  # <__main__.C object at 0x000002E69F331FD0>
print(c.name)  # python
del c  # 我走拉~
print(x)  # <__main__.C object at 0x000002E69F331FD0>
print(x.name)  # python

闭包重生

class C():
    def __init__(self, name, func):
        self.name = name
        self.func = func
        print("我来拉~")

    def __del__(self):
        print("我走拉~")
        self.func(self)


def outter():
    x = ""

    def inner(y=None):
        nonlocal x
        if y:
            x = y
        else:
            return x

    return inner


f = outter()

c = C("python", f)  # 我来拉~
print(c)  # <__main__.C object at 0x000002AB9E0E3F70>
print(c.name)  # python
del c  # 我走拉~
print(f())  # <__main__.C object at 0x000002AB9E0E3F70>
print(f().name)  # python

属性相关的魔法方法(hasattr()、getattr()、setattr()、delattr())

四个方法的基本使用

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


f = Fish("鱼", 18)
print(hasattr(f, "name"))  # True

print(getattr(f, "name"))  # 鱼

setattr(f, "name", "猪")
print(getattr(f, "name"))  # 猪

delattr(f, "name")
print(getattr(f, "name", "1"))  # 1

getattr和hasattr 的魔法方法 __ getattribute __ 、 __ getattr __

我们在类中重写这两个方法,并且输出一下方法名字,让我们看一下如何调用的。

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

    def __getattribute__(self, item):
        print("__getattribute__")
        return super(Fish, self).__getattribute__(item)

    def __getattr__(self, item):
        print("__getattr__")
        if item == "gtd":
            print(f"hello {item}")
        else:
            return super(Fish, self).__getattr__(item)


f = Fish("鱼", 18)
print(hasattr(f, "name"))
"""
__getattribute__
True
"""

print(hasattr(f, "1"))
"""
__getattribute__
__getattr__
False
"""

发现 当用户使用 hasattr 时__ getattribute __ 方法会被调用,当属性不存在时 __ getattr __ 会被调用。

getattr()呢?

...

print(getattr(f, "name"))
"""
__getattribute__
鱼
"""

print(getattr(f, "1"))
"""
__getattribute__
__getattr__
AttributeError: 'super' object has no attribute '__getattr__'
"""

调用逻辑一样,但当属性不存在时会报错。

我们可以给getattr()加第三个参数,来设置默认值如:

print(getattr(f, "1", "default"))
"""
__getattribute__
__getattr__
default
"""

除了他俩会调用方法,还有self.属性也会调用这个方法.

...
f = Fish("鱼", 18)
print(f.name)
"""
__getattribute__
鱼
"""

print(f.a)
"""
__getattribute__
__getattr__
AttributeError: 'super' object has no attribute '__getattr__'
"""

setattr 的魔法方法__ setattr __

我们思考一下这个方法是如何实现的?直接使用self.key = value么?让我们来试试。



class Fish():
    ...

    def __setattr__(self, key, value):
        self.key = value


f = Fish("鱼", 18)
setattr(f, "name", "猪")  # RecursionError: maximum recursion depth exceeded

为什么会出现递归的报错呢?其实更具上面的getattr()就明白 getatr()和self.属性 其实调用的是相同的方法。

那么setattr()和self.属性 = xxx 是不是也一样呢?

...
f.name = "猪"  # RecursionError: maximum recursion depth exceeded

一样的报错,那么既然一样,递归就不难懂了。那么我们怎么避免呢?两种方法

super 避免递归
class Fish():
    ...

    def __setattr__(self, key, value):
        super(Fish, self).__setattr__(key, value)


f = Fish("鱼", 18)
f.name = "猪"
print(f.name)  # 猪
__ dict __ 避免递归
class Fish():
    ...

    def __setattr__(self, key, value):
        self.__dict__[key] = value


f = Fish("鱼", 18)
f.name = "猪"
print(f.name)  # 猪

delattr 的魔法方法__ delattr __

delattr 和 setattr 一样 要注意避免递归问题。

class Fish():
    ...

    def __delattr__(self, item):
        print("__delattr__")
        self.__dict__.pop(item)
        # del self.__dict__[item]


f = Fish("鱼", 18)
del f.name  # __delattr__
# delattr(f, "name")    # __delattr__
print(getattr(f, "name", "没值"))  # 没值

索引、切片、迭代协议

索引和切片

索引和切片的魔法方法是 __ getitem __ 和 __ setitem __当我们对这个对象使用切片和索引操作就会调用这个方法

class Data():
    def __init__(self, data):
        self.data = data

    def __getitem__(self, index):
        print("__getitem__", index)
        return self.data[index]

    def __setitem__(self, index, value):
        print("__setitem__", index, value)
        self.data[index] = value


d = Data([1, 2, 3, 4, 5])
print(d[2])
"""
__getitem__ 2
3
"""

print(d[2:5])
"""
__getitem__ slice(2, 5, None)
[3, 4, 5]
"""

d[2] = 1  # __setitem__ 2 1
print(d[2])
"""
__setitem__ 2 1
__getitem__ 2
1
"""

d[0:3] = d[3:5]  # __getitem__ slice(4, 5, None)    __setitem__ slice(2, 3, None) None
print(d[0:3])
"""
__getitem__ slice(3, 5, None)
__setitem__ slice(0, 3, None) [4, 5]
__getitem__ slice(0, 3, None)
[4, 5, 4]
"""

迭代协议

__ getitem __ 除了切片操作外其实还拦截了循环

...

d = Data([1, 2, 3, 4, 5])

for a in d:
    print(a)
"""
__getitem__ 0
1
__getitem__ 1
2
__getitem__ 2
1
__getitem__ 3
4
__getitem__ 4
5
__getitem__ 5
"""

虽然可以拦截,但这是一种求全的方法,正常我们应该使用 __ iter ____ next __

对象所属的类中拥有 __ iter __ 时他就是一个可迭代对象

当拥有 __ next __ 时他就是一个迭代器

__ iter __ 会返回一个迭代器,我们试试

x = [1, 2, 3, 4, 5]
# 列表不是一个迭代器
# next(x)  # TypeError: 'list' object is not an iterator

iterator_x = iter(x)

while True:
    try:
        print(next(iterator_x))
    except StopIteration:
        break

已知 iter 可以返回一个迭代器,那让我们试着写一个可迭代的类吧

class Data():
    def __init__(self, start, stop):
        self.value = start - 1
        self.stop = stop

    def __iter__(self):
        print("__iter__")
        return self

    def __next__(self):
        print("__next__")
        if self.value == self.stop:
            raise StopIteration
        self.value += 1
        return self.value


d = Data(1, 5)
for i in d:
    print(i)

"""
__iter__
__next__
1
__next__
2
__next__
3
__next__
4
__next__
5
__next__
"""

那如果 getitem 和 iter next 同时存在 会走谁呢?会优先走 iter 和 next 如果没有这两个方法再寻找 getitem 这种行为叫做代偿

class Data():
    def __init__(self, start, stop, data):
        self.value = start - 1
        self.stop = stop
        self.data = data

    def __iter__(self):
        print("__iter__")
        return self

    def __next__(self):
        print("__next__")
        if self.value == self.stop:
            raise StopIteration
        self.value += 1
        return self.value

    def __getitem__(self, index):
        print("__getitem__", index)
        return self.data[index]


d = Data(1, 5, [1, 2, 3, 4, 5])
for i in d:
    print(i)
"""
__iter__
__next__
1
__next__
2
__next__
3
__next__
4
__next__
5
__next__
"""

in 和 not in 的魔法方法__ contains __

class Data():
    def __init__(self, data):
        self.data = data

    def __contains__(self, item):
        print("__contains__")
        return item in self.data

    def __iter__(self):
        self.i = 0
        return self

    def __next__(self):
        if self.i == len(self.data):
            raise StopIteration
        item = self.data[self.i]
        self.i += 1
        return item

    def __getitem__(self, index):
        print("__getitem__")
        return self.data[index]


d = Data([1, 2, 3, 4, 5])
print(2 in d)
"""
__contains__
True
"""

如果程序中没有 contains 那么会寻找 iter next 如果还没有 使用 getitem。

比较运算符

  • < __ lt __(self,other)
  • <= __ le __(self,other)
  • > __ gt __(self,other)
  • >= __ ge __(self,other)
  • == __ eq __(self,other)
  • != __ ne __(self,other)

举个例子 我觉得字符串比较时应该是去比较字符的长度这样更有意义。

class Data(str):
    def __gt__(self, other):
        return len(self) > len(other)

    def __ge__(self, other):
        return len(self) >= len(other)

    def __lt__(self, other):
        return len(self) < len(other)

    def __le__(self, other):
        return len(self) <= len(other)

    def __eq__(self, other):
        return len(self) == len(other)

    def __ne__(self, other):
        return len(self) != len(other)


d = Data("123")
s = Data("789")
print(d > s)  # False
print(d >= s)  # True
print(d < s)  # False
print(d <= s)  # True
print(d == s)  # True
print(d != s)  # False

property()和描述符

property

通过这个装饰器我们可以不用加()实现直接调用

class Data(str):
    @property
    def get_data(self):
        return "123"


d = Data()
print(d.get_data)

我们还可以这样用,使用property去管理我们的get、set、del三个方法

class D():
    def __init__(self):
        self._x = 250

    def getx(self):
        return self._x

    def setx(self, value):
        self._x = value

    def delx(self):
        del self._x

    x = property(getx, setx, delx)


d = D()
print(d.x)
d.x = 2
print(d.x)
del d.x
print(d.__dict__)

描述符

如果一个类中有 __ set __ 、 __ get __ 、 __ delete __ 那么这个类被叫做描述符,用来管理别人的属性。

class A():
    def __get__(self, instance, owner):
        print("__get__", instance, owner)

    def __set__(self, instance, value):
        print("__set__", instance, value)

    def __delete__(self, instance):
        print("__delete__", instance)


class B():
    x = A()


b = B()
print(b.x)  # __get__ <__main__.B object at 0x107bc0fa0> 
b.x = 1  # __set__ <__main__.B object at 0x107bc0fa0> 1
del b.x  # __delete__ <__main__.B object at 0x107bc0fa0>

由代码返回值可知,instance 是被描述属性类的实例,owner 是被描述属性的类

由此我们可以实现,property的功能

class A():
    def __get__(self, instance, owner):
        print("__get__", instance, owner)
        return instance._x

    def __set__(self, instance, value):
        print("__set__", instance, value)
        instance._x = value

    def __delete__(self, instance):
        print("__delete__", instance)
        del instance._x


class B():

    def __init__(self):
        self._x = 250

    x = A()


b = B()
print(b.x)  # 250
b.x = 1
print(b.x)  # 1
del b.x
print(b.__dict__)  # {}

这样虽然实现了类似功能,但是在一个类中调用别的类中的实例是不是有点不太优雅,我们可以这样改

class MyProperty():
    def __init__(self, fget, fset, fdel):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel

    def __get__(self, instance, owner):
        print("__get__", instance, owner)
        return self.fget(instance)

    def __set__(self, instance, value):
        print("__set__", instance, value)
        self.fset(instance, value)

    def __delete__(self, instance):
        print("__delete__", instance)
        self.fdel(instance)


class D():
    def __init__(self):
        self._x = 250

    def getx(self):
        return self._x

    def setx(self, value):
        self._x = value

    def delx(self):
        del self._x

    x = MyProperty(getx, setx, delx)


d = D()
print(d.x)
d.x = 1
print(d.x)
del d.x
print(d.__dict__)

类方法和静态方法

类方法

通过类方法装饰器 @classmethod 可以定义一个类方法,第一个参数是cls表示当前的类。

我们可以通过这个来管理我们的对象实例,如下:

class Data():
    count = 0

    def __init__(self):
        Data.count += 1

    @classmethod
    def get_count(cls):
        return Data.count


d1 = Data()
d2 = Data()
d3 = Data()

print(Data.get_count())  # 3

静态方法

我们可以通过 @staticmethod 装饰器实现一个静态方法,也同样可以实现上述功能。

class Data():
    count = 0

    def __init__(self):
        Data.count += 1

    @staticmethod
    def get_count():
        return Data.count


d1 = Data()
d2 = Data()
d3 = Data()

print(Data.get_count())

对比

既然都能实现,那到底那种方法好一些呢?

两种方法都是类层级的方法,无需创建实例去使用。区别是类方法需要和类绑定,静态方法则不需要。

如果和类发生关系,则需要使用类方法,否则使用静态方法即可。

如果发生继承关系,我们想让每个类都能统计自己的实例数量,通过类方法就可以实现继承。静态适合在子方法中使用,或者某些全局情况下使用

class Data():
    count = 0

    @classmethod
    def add_count(cls):
        cls.count += 1
        Data.count += 1

    def __init__(self):
        self.add_count()

    @classmethod
    def get_count(cls):
        return cls.count

    @staticmethod
    def get_all_count():
        return Data.count


class A(Data):
    count = 0


class B(Data):
    count = 0


class C(Data):
    count = 0


A1 = A()
B1, B2 = B(), B()
C1, C2, C3 = C(), C(), C()

print(A.get_count())  # 1
print(B.get_count())  # 2
print(C.get_count())  # 3

# 静态方法实现
print(Data.count)  # 6

你可能感兴趣的:(Python,python,开发语言,numpy)