菜鸟初学python入门进阶第三节:面向对象,深入类与对象

python深入类与对象目录

  • 1.鸭子类型与多态
  • 2.抽象基类(abc模块)
    • 应用场景1:
    • 应用场景2:
  • 3.isinstance与type
  • 4.类变量和对象变量‍‍‍
  • 5.类属性和实例属性以及查找顺序
  • 6.静态方法、类方法以及实例方法以及参数
  • 7.数据封装和私有属性
  • 8.python对象的自省机制‍♂️
  • 9.super函数
  • 10.django rest framework中对多继承使用的经验
  • 11.python中的with语句✨
  • 12.contextlib简化上下文管理器


上一篇的链接:菜鸟初学python入门进阶第二节:面向对象,python中的魔法函数


这篇比较长,大凑合着看⑧

1.鸭子类型与多态

维基百科:当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。

在java、c++等静态语言中,如果要实现堕胎 多态,必须先写一个父类,然后再写子类来继承父类,并重写父类里的方法。

python之中,由于不需要事先指明对象(类对象)的类型(type),所以可以不用写出父类,只要子类里有同样名称的方法,就可以默认其继承了同一个父类。就像维基百科里说的那样。

class human(object):
    def eat(self):
        print("human can eat")
        
class rat(object):
    def eat(self):
        print("rat can eat")

class fly(object):
    def eat(self):
        print("fly can eat")

critters = [human, rat, fly]
for critter in critters:
    critter().eat()

输出:

human can eat
rat can eat
fly can eat

还有一个例子,比如经常使用的列表扩展函数extend

critters = [human, rat, fly]
critters_2 = set()
critters_2.add("asshole")
critters_2.add("rich people")
print(critters_2)

critters.extend(critters_2)
print(critters)

输出:

{'rich people', 'asshole'}
[<class '__main__.human'>, <class '__main__.rat'>, <class '__main__.fly'>, 'rich people', 'asshole']

可以看见,列表里原先存的是不同种类的class(不仅仅是数字与字符串),然后用extend加入了一个集合,结果仍然适用。去看看extend的源码,发现

def extend(self, *args, **kwargs): # real signature unknown
    """ Extend list by appending elements from the iterable. """
    pass

(python3.7.7)
“通过追加可迭代对象里的元素来扩展原有list”
所以只要是可迭代的对象(tuple、set、)都可以用extend方法来添加到列表。

只要你为你自己写的类追加了可迭代属性,都可以调用extend方法,仿佛你自己写的类和list、set等有一个同样的父亲。这就是鸭子类型。
例子:

class crit(object):
    def __init__(self, crit_list):
        self.crit_list = crit_list
        
    def __iter__(self):
        return iter(self.crit_list)
        
critters = [human, rat, fly]
crits = crit(["the poors", "the sluts", "the dopers"])
critters.extend(crits)
print(critters)

输出:

[<class '__main__.human'>, <class '__main__.rat'>, <class '__main__.fly'>, 'the poors', 'the sluts', 'the dopers']

2.抽象基类(abc模块)

抽象基类不能实例化
python是动态语言,动态语言是没有“类型”的,
变量只是用来指向不同对象的对象(指针)。

python抽象基类有点c++的纯虚函数的味道。
父类不指明方法具体实现步骤,子类必须将父类里的所有方法都具体实现一遍

既然python已经有鸭子类型了,为什么又需要抽象基类呢?

应用场景1:

要去检查某个类是否有某种方法

class crit(object):
    def __init__(self, crit_list):
        self.crit_list = crit_list
    def __iter__(self):
        return iter(self.crit_list)
crits = crit(["the poors", "the sluts", "the dopers"])
print(hasattr(crits, "__iter__"))

输出

True

我们可以通过hasattr来判断类里是否有某种方法,但我们更倾向于去判断类是否属于某种数据类型(可迭代类型、Sized类型、、)

from collections.abc import *
print(isinstance(crits, Iterable))
print(isinstance(crits, object))

输出:

True
True

判断的过程需要检查其是否有目标数据类型的方法

应用场景2:

我们需要强制某个子类必须实现某种方法
模拟一个抽象基类:

class shit_base():
    def smell(self, smell):
        raise NotImplementedError
        
    def color(self, color):
        raise NotImplementedError
        
class shit01(shit_base):
    pass
yellow_shit = shit01().color('yellow')

输出:

Traceback (most recent call last):
  File "D:/python/python3/cn-bug/o.py", line 202, in <module>
    yellow_shit = shit01().color('yellow')
  File "D:/python/python3/cn-bug/o.py", line 199, in color
    raise NotImplementedError
NotImplementedError

可以看到,如果子类不实现父类的方法,我们可以用raise Notimplementederror来让其报错,这样就模拟了一个抽象基类。
但这样只有在具体实现某个方法时才会抛出异常,不太好。

我们可以用abc模块:

import abc
class shit_base(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def smell(self, smell):
        pass
        
    @abc.abstractmethod
    def color(self, color):
        pass


class shit01(shit_base):
    def smell(self, smell):
        print(smell)
        
    def color(self,color):
        print(color)


class shit02(shit_base):
    pass
    
yellow_shit = shit01().color('yellow')
blue_shit = shit02().color('blue')

输出:

yellow
Traceback (most recent call last):
  File "D:/python/python3/cn-bug/o.py", line 210, in <module>
    blue_shit = shit02().color('blue')
TypeError: Can't instantiate abstract class shit02 with abstract methods color, smell

这样更好

python的抽象基类在collections.abc模块当中,可以自行查看
isinstance方法还可以判断继承链


3.isinstance与type

尽量使用isinstance而不是type

python中的 “is” 是判断两个对象的 id 是否相同,“==” 是判断两个对象的 值 是否相同。
isinstance(实例, 某种类型)

class A():
    pass
    
class B(A):
    pass
    
b = B()

print(isinstance(b, B))
print(isinstance(b, A))

print(type(b) is B)
print(type(b) is A)

输出:

True
True
True
False

isinstance方法可以判断继承链,type仅仅只看地址,可能会有误差


4.类变量和对象变量‍‍‍

什么是类变量?
类变量是属于类本身的变量(类内定义,前面没有self.),对象变量是属于类的实例的变量(类内定义,前面有self.)
先看个例子

class A():
    aa = 1
    def __init__(self, x, y):
        self.x = x
        self.y = y
        
a = A(2, 3)
print(a.x, a.y, a.aa)
print(A.aa)
print(A.x)

输出:

2 3 1
1
Traceback (most recent call last):
  File "D:/python/python3/cn-bug/o.py", line 234, in <module>
    print(A.x)
AttributeError: type object 'A' has no attribute 'x'

要注意,self是一个实例,2,3,传递给了类的实例,而不是类本身。
可以看出,当print到a.aa时,实例a没有变量aa,python会往上找类本身的变量,结果输出类变量aa的值。
后面因为类没有变量x(x是属于实例的实例变量),所以报错了。

如果我们这样操作,多加上a.aa的赋值语句

a = A(2, 3)
A.aa = 114514
a.aa = 0x3f3f3f3f
print(a.x, a.y, a.aa)
print(A.aa)

输出:

2 3 1061109567
114514

可以看到,实例a会多出一个aa属性,并且与A的类变量是独立的


5.类属性和实例属性以及查找顺序

由上一个例子可以看到,该查找顺序是从下往上的,先找实例有没有该属性,再找实例的类有没有该属性

python3现在的属性查询算法是C3算法
↓↓↓↓

C3 线性化算法与 MRO

↑↑↑↑↑
可以说是很详细了


6.静态方法、类方法以及实例方法以及参数

看一个例子,一般在类内定义的,参数带有self的都是实例的方法

class looser:
    def __init__(self, name, age, height):
        self.name = name
        self.age = age
        self.height = height
        
    def next_year(self):
        self.age += 1
        
    def __str__(self):
        return "{name}/{age}/{height}".format(name=self.name, age=str(self.age), height=str(self.height))
        
if __name__ == '__main__':
    luoyonghao = looser('luoyonghao', 44, 168)
    print(luoyonghao)
    luoyonghao.next_year()
    print(luoyonghao)

输出:

luoyonghao/44/168
luoyonghao/45/168

如上,我们用魔法函数给looser类增加了一个str的属性,使其可以按一定的格式输出。

如果我们输入的类参数格式不太对怎么办?
我们可以用静态方法来进行预处理。

class looser:
    def __init__(self, name, age, height):
        self.name = name
        self.age = age
        self.height = height
    def next_year(self):
        self.age += 1
    def __str__(self):
        return "{name}/{age}/{height}".format(name=self.name, age=str(self.age), height=str(self.height))
        
    @staticmethod
    def pre_executed(information):
        name, age, height = tuple(information.split("-"))
        return looser(name, int(age), int(height))
        
if __name__ == '__main__':
    luoyonghao = looser.pre_executed('luoyonghao-44-168')
    print(luoyonghao)

输出:

luoyonghao/44/168

但是静态方法不太适合这样做,因为每次类名的改变,都需要改变静态方法的返回值里的类名。静态方法比较适合做某个类的预先检查,而不是顺带实例化。

python提供了类方法,类方法适合做真正的实例化。

class looser:
    def __init__(self, name, age, height):
        self.name = name
        self.age = age
        self.height = height
    def next_year(self):
        self.age += 1
    def __str__(self):
        return "{name}/{age}/{height}".format(name=self.name, age=str(self.age), height=str(self.height))
    @staticmethod
    def pre_checked(information):
        name, age, height = tuple(information.split("-"))
        print(name,' ', int(age),' ', int(height))
        
    @classmethod
    def pre_executed(cls, information):
        name, age, height = tuple(information.split("-"))
        return cls(name, int(age), int(height))
        
if __name__ == '__main__':
    luoyonghao = looser.pre_executed('luoyonghao-44-168')
    print(luoyonghao)

输出

luoyonghao/44/168

cls是class的简写,是规范的写法,表示传递类本身


7.数据封装和私有属性

接着上面的looser类,我们建立一个crime类来进一步对looser进行封装。
python类中,以双下划线开头的属性是私有的,只能通过类里的方法进行访问。

class crime:
    def __init__(self, inform):
        self.__inform = inform
        
    def get_age(self):
        return self.__inform.age
        
if __name__ == '__main__':
    luoyonghao = crime(looser.pre_executed('luoyonghao-44-168'))
    print(luoyonghao.get_age())
    print(luoyonghao._crime__inform)
    print(luoyonghao.__inform)

输出:

44
luoyonghao/44/168
Traceback (most recent call last):
  File "D:/python/python3/cn-bug/o.py", line 266, in <module>
    print(luoyonghao.__inform)
AttributeError: 'crime' object has no attribute '__inform'

第一条输出,我们看到在建立luoyonghao这个对象时把inform先用looser类给实例化了,并把实例化的结果赋给crime类的类对象self.__inform。
输出luoyonghao的年龄。
第三条输出,由于luoyonghao.__inform是私有的,不能直接调用,所以输出报错了。
第二条输出,可见,尽管luoyonghao.__inform是私有的,通过一些手段我们还是能获取到数据的。


8.python对象的自省机制‍♂️

自省是通过一定的机制查询到对象的内部结构

1.通过dict查询属性❤

class looser:
    '''this is a loser'''
    name = "user"
    
class crime(looser):
    def __init__(self, crime_name):
        self.crimename = crime_name
        
if __name__ == "__main__":
    asshole = crime("luoyonghao")
    print(asshole.__dict__)
    print(crime.__dict__)
    print(looser.__dict__)

输出:

{'crimename': 'luoyonghao'}
{'__module__': '__main__', '__init__': <function crime.__init__ at 0x000001CC76673CA8>, '__doc__': None}
{'__module__': '__main__', '__doc__': 'this is a loser', 'name': 'user', '__dict__': <attribute '__dict__' of 'looser' objects>, '__weakref__': <attribute '__weakref__' of 'looser' objects>}

可以发现
: 第一条输出,dict可以找到实例的所有属性
: 第二条输出,dict可以找到类的所有属性
: 第三条输出,dict是不会向上查找属性的

dict其实可以动态增加/修改实例或类的属性

if __name__ == "__main__":
    asshole = crime("luoyonghao")
    print(asshole.__dict__)
    # print(crime.__dict__)
    # print(looser.__dict__)
    asshole.__dict__["why"] = "liar"
    print(asshole.__dict__)

输出:

{'crimename': 'luoyonghao'}
{'crimename': 'luoyonghao', 'why': 'liar'}

2.通过dir查询属性❤

dir比dict更加强大

if __name__ == "__main__":
    asshole = crime("luoyonghao")
    print(asshole.__dict__)
    print(dir(asshole))
    a = [1, 2]
    print(dir(a))
    print(a.__dict__)

输出:

{'crimename': 'luoyonghao'}
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'crimename', 'name']
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
Traceback (most recent call last):
  File "D:/python/python3/cn-bug/o.py", line 279, in <module>
    print(a.__dict__)
AttributeError: 'list' object has no attribute '__dict__'

一二条输出看出:dir比dict多输出了很多东西,而且进行了向上查找,连父类的属性也输出了。
三四条输出看出:list并没有魔法函数dict,但有dir


9.super函数

class A:
    def __init__(self):
        print('A')
        
class B(A):
    def __init__(self):
        print('B')
        super().__init__()
        
b = B()

输出:

B
A

既然我们重写了构造函数,为什么还要调用super?
有时候一些参数我们可以直接用super里的(直接把参数赋给super的构造函数),就不用再在类内写属性了。

实际上,super调用的是一个MRO序列,详见:

5.类属性和实例属性以及查找顺序


10.django rest framework中对多继承使用的经验

实际上,我们并不推荐多继承(容易继承混乱)
可以使用mixin模式。
mixin模式特点:
1.Mixin类功能单一。
2.不和基类关联,可以和任意基类组合(基类可以不和mixin关联就能初始化成功)。
3.在mixin中不要哦使用super这种方法。

class AMixin(object):
    def __init__(self):
        print('A')
        
class B(mixins.AMixin):
    def __init__(self):
        print('B')

11.python中的with语句✨

1.try except else finally语句

try:
    print("a")
    raise KeyError
except KeyError as e:
    print("e")
else:
    print("no keyerror")
finally:
    print("finally")

输出:

a
e
finally

如果不raise错误:

try:
    print("a")
    # raise KeyError
except KeyError as e:
    print("e")
else:
    print("no keyerror")
finally:
    print("finally")

输出:

a
no keyerror
finally

先试着执行try下的语句,如果抛出异常则执行except下的语句;没有异常执行else的语句;finally的语句最终一定会执行

要注意,如果函数在不同语句下有多个返回个值,则会先返回finally语句下的返回值。

2.python上下文管理器:
python是基于协议来进行编程的(必须要按照协议实现相应的魔法函数)

class Sample:
    def __enter__(self):
        print('enter now')
        # 获取资源
        return self
        
    def __exit__(self, exc_type, exc_val, exc_tb):
        # 释放资源
        print('exit now')
        
    def do_something(self):
        print('doing sth')
        
with Sample() as s:
    s.do_something()

输出:

enter now
doing sth
exit now

12.contextlib简化上下文管理器

看例子:

import contextlib

@contextlib.contextmanager
def file_open(file_name):
    print("file open")
    yield {}
    print("file close")
    
with file_open("None") as f_op:
    print("opening")

输出:

file open
opening
file close



就到这里


下一篇的链接:菜鸟初学python入门进阶第四节:面向对象,自定义序列类


❤⭐✨✨✨d=====( ̄▽ ̄*)b
U•ェ•*U-U•ェ•*U-U•ェ•*U-U•ェ•*U-U•ェ•*U-U•ェ•*U-U•ェ•*UU•ェ•*U-U•ェ•*U-U•ェ•*U-U•ェ•*U-U•ェ•*U-U•ェ•*U-

你可能感兴趣的:(py面向对象)