python面向对象

类和面向对象

  • 类:抽象的集合,可以看做模板,类中定义属性和方法
  • 对象:基于类的具体实例

类及类属性

class Person:
    """
    class doc
    """
    classVar = 100

    def showMe(self):
        return __class__.__name__


print(Person)
print(type(Person))
print(Person.__name__)
print(Person.__doc__)
print(Person.showMe)

image.png

  • class后接标识符定义类
  • 每个class实际上也是一个对象,是type类的对象
  • classVar是类变量,所有实例和类共享
  • showMe方法的本质是一个对象

实例化

class Person:
    """
    class doc
    """
    classVar = 100

    def showMe(self):
        return __class__.__name__


p = Person()
print(p)

image.png

  • __main__是文件模块名,Person object表示是Person类的对象,0x到结尾表示对象在内存中的对象地址

  • 每一次实例化都是生成不同的对象,及时实例中属性和方法相同

  • 实例化的本质是调用类的__init__方法,__init__方法不能有返回值

  • 上面Person类没有定义__init__方法,是因为调用object类的__init__方法,python3没有定义继承哪个类,默认继承object

  • 初始化对象可以看做两步,首先调用__new__方法(继承object)在内存开辟内存存放实例对象等操作,__init__用来填充实例的数据等操作,两步完成后才算真正的实例化

class Person:
    def __init__(self,name,age):
        self.name = name
        self.age = age
  • __init__方法的第一个参数self指的是第一个步骤__new__方法创建出的对象,可以不是self标识符,self.name和self.age是实例属性(实例变量),分别接受形参的name和age
class Person:
    age = 3

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


tom = Person('Tom')
jerry = Person('Jerry')
print(tom.name, tom.age)
print(jerry.name, tom.age)
print(Person.age)
Person.age = 30
print(Person.age, tom.age, jerry.age)

image.png

  • 每个实例变量值是自己独有的,类变量共享

特殊属性

特殊属性含义
_name_名称或模块名
_class_实例class信息
_dict_类或实例的属性字典
_qualname_简单来说是属性的完整路径
class Person:
    age = 3

    def __init__(self, name):
        self.name = name
        self.inner = self.__class__.Inner()

    class Inner:
        var = 100

        def test(self):
            pass


tom = Person('Tom')
jerry = Person('Jerry')

print(tom.__dict__)
print(tom.__class__)
print(tom.__class__.__name__)
print(__name__)
print(Person.__dict__)
print(Person.Inner.test.__qualname__)

image.png

  • 实例.__dict__主要输出__init__方法下定义的赋值语句,类.__dict__输出类的信息
  • 只用class.__name__输出类名,单独执行print(_name_)输出的是模块名,如果该文件被其他文件引入,模块名就是定义的文件名
  • __class__输出实例的类信息,每个类输出的是type类信息
  • __qualname__就是对象的完整路径,比如test方法的是Person.Inner.test

属性访问规则

class Person:
    age = 3
    height = 170

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


tom = Person("Tom")  # 实例化、初始化
jerry = Person('Jerry', 20)
Person.age = 30
print(1, Person.age, tom.age, jerry.age)  # 输出什么结果
print(2, Person.height, tom.height, jerry.height)  # 输出什么结果
jerry.height = 175
print(3, Person.height, tom.height, jerry.height)  # 输出什么结果
tom.height += 10
print(4, Person.height, tom.height, jerry.height)  # 输出什么结果
Person.height += 15
print(5, Person.height, tom.height, jerry.height)  # 输出什么结果
Person.weight = 70
print(6, Person.weight, tom.weight, jerry.weight)  # 输出什么结果
print(7, tom.__dict__['height'])  # 可以吗
print(8, tom.__dict__['weight'])  # 可以吗

  • 1输出30 18 20,没啥好说的,Person.age = 30改变类变量的值,tom使用默认18,jerry传入20
  • 2输出170 170 170,虽然实例变量没有height变量,但是公用了类变量height=170
  • 3输出170 170 175,经过jerry.height = 175动态给jerry动态赋予height属性,就不会用类变量的height属性,相当于在jerry.__dict__添加了height=175
  • 4输出170 180 175,可以理解为tom实例拿类变量height+=10了,然后添加到tom.__dict__中
  • 5输出185 180 175,对类变量+=15,对实例变量没有影响
  • 6输出70 70 70,动态给Person类添加weight属性,实例没有weight属性用类的变量
  • 7输出180,tom._dict_['height']等于tom.height
  • 8就会报错,这不是面向对象报错,是字典的报错,找不到weight的key,而tom.weight由python内部帮你找到类的变量,所以tom._dict_['weight']这种方式尽量不要用

类方法和静态方法

  • 普通方法
class Person:
    def normal_function():
        print('普通的函数')
    def method(self):
        print('方法')
# 调用
Person.normal_function()
print(Person().normal_function)
print(Person().normal_function())
print(Person.__dict__)
  • Person().normal_function()会报错,因为这是因为normal_function()并没有把实例传入,Person().normal_function可以当做实例没有找到类的属性normal_function

  • 类方法

class Person:
    @classmethod
    def class_method(cls):
        print('类方法')
        print("{0.__name__}'s name = {0.__name__}".format(cls))
        cls.HEIGHT = 170


# 调用
Person.class_method()  
Person().class_method()  
print(Person.__dict__)  
print(Person.HEIGHT)
print(Person().HEIGHT)
  • @classmethod表示是类方法,类和实例都可以访问,cls表示类,cls.HEIGHT = 170相当于创建类变量

  • 静态方法

class Person:
    HEIGHT = 180
    @staticmethod
    def static_method():
        print('静态方法')
        print(Person.HEIGHT)
# 调用
Person.static_method() 
Person().static_method() 
print(Person.__dict__) 
  • @staticmethod表示是静态方法,比类方法少了传参的步骤,类和实例都可以访问

私有变量

  • 通过"__"定义私有变量
class Person:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age
p = Person("tom", 25)
print(p.__name) 
print(p.__age)
  • 上面p.__name和p.__age都会报错,实质是因为python将__name实例变量变成_Person__name和_Person__age。p.__dict__可以看到

  • 通过"_"定义保护成员

class Person:
    def __init__(self, name, age):
        self._name = name
        self._age = age
p = Person("tom", 25)
print(p._name) 
print(p._age)
  • 实际上没有保护效果,还是可以访问,只是一个约定

属性装饰器

  • 一般会将属性设置成私有变量,然后设置方法进行取值和传值、
class Person:
    def __init__(self, name):
        self._name = name

    def name(self):
        return self._name

    def set_name(self, value):
        self._name = value
  • Python提供了property装饰器,简化调用
class Person:
    def __init__(self, name):
        self._name = name
    @property
    def name(self):
        return self._name
    @name.setter
    def set_name(self, value):
        self._name = value
  • property装饰器必须在前,setter、deleter装饰器在后
  • property装饰器能通过简单的方式,把对方法的操作变成对属性的访问,并起到了一定隐藏效果

继承

  • 继承:继承其他类的属性和方法,也可以有自己的属性和方法,减少代码冗余
class Animal:
    def __init__(self, name):
        self._name = name

    def shout(self):
        print(f"{self._name} shout")

    @property
    def name(self):
        return self._name

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


a = Animal("tom")
a.shout()


class Cat(Animal):
    pass


c = Cat("miao")
c.shout()


class Dog(Animal):
    pass


d = Dog("wang")
d.shout()

  • Animal类是Cat和Dog类的父类,Cat和Dog类是Animal的子类

  • 特殊属性

特殊属性和方法含义
_bases_类的父类们
_base___bases__元祖的第一个
_mro_方法的查找顺序
mro()同上
_subclasses_()类的子类列表
class A:
    pass
print(A.__base__)
print(A.__bases__)
print(A.mro())
print(A.__mro__)
print(int.__subclasses__())
print(bool.mro())

image.png

  • 继承中的访问控制
class Animal:
    __a = 10
    _b = 20
    c = 30

    def __init__(self):
        print("self init")
        self.__d = 40
        self._e = 50
        self.f = 60
        self.__a += 1

    def showa(self):
        print(self.__a)
        print(self.__class__.__a)

    def __showb(self):
        print(self._b)
        print(self.__a)
        print(self.__class__.__a)


class Cat(Animal):
    __a = 100
    _b = 200


c = Cat()
c.showa()
c._Animal__showb()
print(c.c)
print(c._Animal__d)
print(c._e, c.f, c._Animal__a)
print(c.__dict__)
print(c.__class__.__dict__.keys())

image.png

  • c.showa():c调用父类showa方法,需要注意的是Cat实例化时用的是Animal的__init__方法,self.__a+=1实际上是self._Animal__a+=1,也就是c._dict__会有_Animal__a=11的原因,self._class.__a__会在Cat.__dict__查找,但是没有找到,最终在父类Animal.__dict__找到。方法或实例的调用顺序:实例._dict_->实例类._dict_->父类._dict_

  • 有了查找顺序,后面输出就很简单了。总结一下:实例.__dict__可以理解为__init__方法下的赋值,如果子类没有重写覆盖父类的__init__方法,而父类在自己的__init__方法下有self.__a的赋值,那么实例上的赋值是self._父类名__a。类.__dict__可以理解为类中包含的属性(不在__init__下)和方法集合。

  • 方法重写

class Animal:
    def shout(self):
        print('Animal shouts')
class Cat(Animal):
    # 覆盖了父类方法
    def shout(self):
         print('miao')

    def shout(self):
        print('miao2')
a = Animal()
a.shout()
c = Cat()
c.shout()
print(Animal.__dict__)
print(Cat.__dict__)

image.png

  • Cat类的shout方法覆盖Animal类的shout方法,然后被自己类的shout方法覆盖,本质是改变Cat.__dict__中shout变量指向的方法地址

  • 对父类方法增强

class Animal:
    def shout(self):
        print('Animal shouts')


class Cat(Animal):
    def shout(self):
        super(Cat, self).shout()
        super().shout()
        print('miao2')


a = Animal()
a.shout()
c = Cat()
c.shout()
print(Animal.__dict__)
print(Cat.__dict__)

image.png

  • 场景:子类想继承父类属性,但也想有自己的属性
class A:
    def __init__(self, a):
        self.a = a


class B(A):
    def __init__(self, b, c):
        super(B, self).__init__(b + c)
        self.b = b
        self.c = c


b = B(2, 3)
print(b.a)
print(b.b)
print(b.c)

  • 从上面例子得知子类的__init__方法会覆盖父类的__init__方法,所以要想子类有父类的a属性,需要调用父类的__init__方法

  • 多继承

zhuanlan.zhihu.com/p/268136917

  • mixin
class Doc:
    def __init__(self, content):
        self.content = content
    # 抽象方法,需要子类重写该方法,要不然报错
    def print(self):
        raise NotImplementedError()

class Printable:
    def printContent(self):
        print(self.content)

class Pdf(Printable,Doc):
    pass

p = Pdf("pdf content")
print(Pdf.mro())
p.printContent()


魔术方法

_new_:实例化对象,主要是继承object类的__new__方法,可以理解为分配内存存放对象,__init__方法主要是为对象分配属性。一般情况用不到这个魔术方法,但涉及到设计模式点情况,需要用到。参考:juejin.cn/post/684490…

class SingleObject:
    def __new__(cls, *args, **kwargs):
        if not hasattr(SingleObject, "_instance"):
            SingleObject._instance = object.__new__(cls)
        return SingleObject._instance

    def __init__(self):
        pass


print(SingleObject.__dict__.keys())
a = SingleObject()
print(SingleObject.__dict__.keys())
b = SingleObject()
print(id(a))
print(id(b))

image.png

  • 可以看到a=SingleObject()后类字典多了一个_instance属性

_str_:str()函数、format()函数、print()函数调用,需要返回对象的字符串表达。如果没 有定义,就去调用 repr 方法返回字符串表达,如果 repr 没有定义,就 直接返回对象的内存地址信息

class Test:
    def __str__(self):
            # 必须return str,__repr__同理
        return "str method called"


print(Test())
print("Test:{}".format(Test()))
print(str(Test()))
print([Test()])

image.png - 当在容器中使用实例对象时,找的是__repr__魔术方法,开发过程中只重写__repr__魔术方法即可

image.png

_repr_:和__str__魔术方法类似,return str,开发中重写这个魔术方法就好

class Test:
    def __repr__(self):
        return "repr method called"


print(Test())
print("Test:{}".format(Test()))
print(str(Test()))
print({Test()})

image.png

_bytes_:返回字节对象

 def __bytes__(self):
        #return "{} is {}".format(self.name, self.age).encode()
        import json
        return json.dumps(self.__dict__).encode()

_bool_:内建函数bool(),或者对象放在逻辑表达式的位置,调用这个函数返回布尔值。 没有定义 bool (),就找 len ()返回长度,非0为真。如果 len ()也没有定义,那么所有实例都返回真

class A:pass


print(bool(A()))
print(bool(A))

class B:
    def __bool__(self):
        return False

print(bool(B))
print(bool(B()))

class C:
    def __len__(self):
        return 0

print(bool(C()))
print(bool(C))


class D:
    def __bool__(self):
        return False

    def __len__(self):
        return 1

print(bool(D))
print(bool(D()))

运算法重载

image.png

class A:
    def __add__(self, other):
        return A(self.data + other.data)

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

    def __lt__(self, other):
        return self.data < other.data

    def __ge__(self, other):
        return self.data == other.data

    def __gt__(self, other):
        return self.data > other.data

    def __iadd__(self, other):
        return A(self.data + other.data)

    def __repr__(self):
        return f'A:[data={self.data}]'


a = A(2)
b = A(2)
print(id(a))
if a > b:
    print("a>b")
elif a == b:
    print("a==b")
else:
    print("a<b")
print(a + b)
print(id(a))
print(a)
a += b
print(id(a))
print(a)

image.png

  • 实例默认是独一无二,a实例不等于b实例,需要重写__eq__方法
  • __add__返回点值不会修改调用对象,__iadd__会修改原有对象,也就是说a指向点实例发生改变

上下文管理

  • 对类实例的增强,当一个对象同时实现了 _enter_ ()和 _exit_ ()方法,它就属于上下文管理的对象
class Point:
    def __init__(self):
        print("init")

    def __enter__(self):
        print("enter")

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("exit")


with Point() as p:
    print("with")

image.png

  • 从上图中可以看到with Point()时会执行__init__方法
  • 在执行with块中代码时,会先执行__enter__方法
  • 执行完with块中代码后会执行__exit__方法
  • 即使with块中抛出异常还是会执行__exit__方法。前提是__exit__方法return True,关于上下文的用法:www.cnblogs.com/wongbingmin…
  • 上文中as p,就像操作文件那样是一个别名,前提是__enter__方法返回self,那么p就是Point()对象的别名,这也说明open函数返回的文件对象的__enter__方法返回self了。
class Point:
    def __init__(self):
        print("init")

    def __enter__(self):
        print("enter")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("exit")


with Point() as p:
    print("with")

with p as f:
    print(p)
    print(f)
    print(p == f)
    print(p is f)

f = open("timeDemo.py", "r")
print(f.__enter__())
with f as f1:
    print(f)
    print(f1)
    print(f == f1)
    print(f is f1)

image.png

生成器函数

  • 在一个函数定义中,出现了yield语句,此函数就是生成器函数
def foo():
    a = 0
    while True:
        print("yield before")
        yield a
        print("yield after")
        a += 1


f = foo()
print(next(f))
print("~~~~~~")
print(next(f))
print("=====")
print(next(f))

  • 从上面代码执行来看,通过next()调用时,当代码执行到yield这行时,返回yield后到值,下一次从yield后到代码执行
  • 打印上面f对象,发现是一个迭代器,也就是每一次next()才返回值。参考:Python 的关键字 yield 有哪些用法和用途? www.zhihu.com/question/34…

contextlib.contextmanager

  • contextlib.contextmanager它是一个装饰器实现上下文管理,装饰一个函数,而不用像类一样实现 _enter_ 和 _exit_ 方法。函数的要求:必须有yield,也就是这个函数必须返回一个生成器,且只有yield一个值。
import contextlib


@contextlib.contextmanager
def foo():  #
    print('enter')
    try:
        yield  5 # yield 5,yield的值只能有一个,作为__enter__方法的返回值 finally:
        print('exit')
    except:
        print("error")
    finally:
        print("quit")


with foo() as f:
    raise Exception("error")
    print(f)
  • 实际应用
import contextlib
import datetime
import time


@contextlib.contextmanager
def timeit():
    print('enter')
    start = datetime.datetime.now()
    try:
        yield
    finally:
        print('exit')
        delta = (datetime.datetime.now() - start).total_seconds()
        print('delta = {}'.format(delta))


def add(x, y):
    time.sleep(2)
    return x + y


with timeit():
    add(4, 5)