PYTHON 面向对象


面向对象之特殊方法

__str__

这个方法有2个注意点,

  1. str 必须返回字符串
  2. 触发这个方法是在把对象p用str(p)进行强转的时候才会触发,普通情况下 print(p)只能输出对象的内存地址.
    但是有时候像orm中希望看到这个对象的一些属性,就可以这么操作
class test():
    """类的描述"""
    def __init__(self,name,age):
        self.name=name
        self.age=age

    def __str__(self):
        # 这个方法必须返回字符串
        return "对象self的属性name是%s,age是%s"%(self.name,self.age)

p = test("alex",18)
print(str(p))  # 对象self的属性name是alex,age是18

__call__

__call__的会在实例对象运行时候触发,比如p是实例对象,p({"age":18})在自我运行时候就出发了__call__方法
**{"age":18}会传给
kwargs**

class test():
    """类的描述"""
    def __init__(self,name):
        self.name=name
        
    def __call__(self, *args, **kwargs):
        print(self.name,args,kwargs)

p = test("alex")
p(**{"age":18})   # alex () {'age': 18}

__new__,__init__

这2个都是类初始化的时候自动运行的.注意有2点

  1. __new__比__init__先运行
  2. 如果__new__有返回值才会运行__init__,否则不会运行__init__ 下面代码示例__new__没有返回值,所以__init__没有运行,导致实例没有name属性
class test():
    """类的描述"""
    def __init__(self,name,age):
        print("init开始了")
        self.name=name
        self.age=age

    def __new__(cls, *args, **kwargs):
        print("new开始了")
        print(cls.__doc__)

p = test("alex",18)
print(p.name)  # AttributeError: 'NoneType' object has no attribute 'name'

__new__ 更多是用在单例模式下

class Student:
 
  _instance = None
 
  def __init__(self, name, age):
    self.name = name
    self.age = age
 
  def __new__(cls, *args, **kwargs):
    # if cls._instance:
    #   return cls._instance            # 有实例则直接返回
    # else:
    #   cls._instance = super().__new__(cls)    # 没有实例则new一个并保存
    #   return cls._instance            # 这个返回是给是给init,再实例化一次,也没有关系
 
    if not cls._instance:               # 这是简化的写法,上面注释的写法更容易提现判断思路
      cls._instance = super().__new__(cls)
    return cls._instance
 
 
stu1 = Student('jack', 18)
stu2 = Student('jack', 18)
print(stu1 is stu2)
print(stu1.__dict__, stu2.__dict__)
 
# 原理:和方法2类似,将判断的实现方式,从类的绑定方法中转移到类的__new__中
# 归根结底都是 判断类有没有实例,有则直接返回,无则实例化并保存到_instance中。

__repr__

__dict__

查看类和对象中的所有成员

class test():
    """类的描述"""
    def __init__(self,name,age):
        self.name=name
        self.age=age

    def __str__(self):
        # 这个方法必须返回字符串
        return "对象self的属性name是%s,age是%s"%(self.name,self.age)

p = test("alex",18)
print(p.__dict__)  # {'name': 'alex', 'age': 18}
print(test.__dict__) # {'__module__': '__main__', '__doc__': '类的描述', '__init__': , '__str__': , '__dict__': , '__weakref__': }

__doc__

每个对象都会有一个__doc__属性,用于描述该对象的作用,相当于注解

class test():
"""类的描述"""
def init(self):
pass
def run(self):
"""方法的描述"""
pass
t = test()
print(t.doc) # 类的描述
print(t.run.doc) # 方法的描述

__getitem__,__setitem__,__delitem__

这三个方法就如同字典的键值增删改查一样.
如果实例对象按照对象的增删改查操作,就会对应触发这三个内置方法,详见代码示例

class test():
    """类的描述"""
    def __init__(self,name,age):

        self.name=name
        self.age=age

    def __getitem__(self, item):
        print("当前实例对象的属性有"%self.__dict__)
        print(self.__dict__.get(item))

    def __setitem__(self, key, value):
        print("当前实例对象的属性有%s,接下来添加的key是%s,value是%s"%(self.__dict__,key,value))
        # 当前实例对象的属性有{'name': 'alex', 'age': 18},接下来添加的key是home,value是北京
        self.__dict__[key]=value
        return self

    def __delitem__(self, key):
        print("要删除对象的key是%s"%key)  # 要删除对象的key是name
p = test("alex",18)
# __getitem__ 示例
p["name"]  # alex
p["name2"]  # None

# __setitem__ 示例
p["home"]="北京"
print(p.__dict__) # {'name': 'alex', 'age': 18, 'home': '北京'}

# __delitem__ 示例
del p["name"] #触发 __delitem__的方法

__getattr__,__setattr__,__delattr__

和上面不同的是他是对象的属性增删改查的时候才会触发的, 注意__setattr__, 对init里的属性设置也会触发__setattr__的方法

class test():
    """类的描述"""
    def __init__(self,name,age):

        self.name=name
        self.age=age


    def __getattr__(self, item):
        print("查询对象的%s属性了"%item)


    def __setattr__(self, key, value):
        print("设置一个键值对,key是%s,value是%s"%(key ,value))

    def __delattr__(self, item):
        print("删除对象的%s属性了" % item)

p = test("alex",18)
# __getattr__ 示例
p.name   # 查询对象的name属性了

# __setattr__ 示例
p.home="北京"

#设置一个键值对,key是name,value是alex
#设置一个键值对,key是age,value是18
#查询对象的name属性了
#设置一个键值对,key是home,value是北京

# __delattr__ 示例

del p.name  # 删除对象的name属性了

__enter__,__exit__

这2个方法是对with上下文管理的,从with p as fwith语句开始就执行__enter__,运行结束执行__exit__
注意这里的f是__enter__的返回值,所以f.name才能输出p的name属性

class test():
    """类的描述"""
    def __init__(self,name,age):

        self.name=name
        self.age=age


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


    def __exit__(self, exc_type, exc_val, exc_tb):
        print("eixt结束")
        pass
p = test("alex",20)
with p as f:
    print(f.name)
    #enter开始
    #alex
    #eixt结束

上下文管理更多用的是对一些即开即关的服务的,比如开启一个示例查询数据库,查完以后及时关闭,避免一直后台和数据库保持连接,节约资源

class test():
    """类的描述"""
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def __enter__(self):
        self.conn="数据库的连接代码示例"
        return self.conn


    def __exit__(self, exc_type, exc_val, exc_tb):
        self.conn.close()
p = test("alex",20)
with p as f:
    print(f.curser())

面向对象归一化

归一化指的是我们在父类中创建模板,子类在创建的时候必须要遵循父类的规则.比如下面的,我们要遵从父类Animal的规则,必须在子类创建run和eat的方法.否则无法正常实例化.
这个归一化的模块用的是abc,设置如下,而Animal这个类本身作用也只是为了限制规范,并无自己的方法,我们称为抽象类.
这种把abc.ABCMeta作为父类抽象类,自身是无法被实例化的.

import abc

class Animal(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def run(self):
        pass
    @abc.abstractmethod
    def eat(self):
        pass

    def sleep(self):
        pass


class People(Animal):
    def __init__(self):
        pass

    def wear(self):
        pass
    

a=People()

报错信息如下:
Traceback (most recent call last):
  File "D:/PyCharm 2018.2.4/test/test.py", line 1582, in 
    a=People()
TypeError: Can't instantiate abstract class People with abstract methods eat, run

类的属性方法隐藏

一般不想让外部调用类的方法或者属性,可以前面加__进行隐藏.但是隐藏只是改变了调用规则,并不是真正意义的禁止调用
有三大特性如下
特性1: 开头的属性或者方法 直接调用会报错,因为__的机制会把属性重新修改为_Classname__attribute的格式,具体可以查看__dict
特性2 所以虽然是隐藏,但是知道里面的修改机制,还是可以直接调用
特性3 __的方法和属性无法覆盖父类的.比如c的__type实际已经被内部转成了_China__type,而父类的是_People__type.所以他们不重名,也无法被覆盖

class People():
    __type="人类"
    home="地球"
    def __init__(self,name):
        self.name=name

    def __hobby(self):
        print("%s喜欢自由"%self.name)

    def attr(self):
        self.__hobby()

    def run(self):
        print("%s开始跑步"%self.name)

class China(People):
    __type = "中国人"
    def __init__(self,name,home):
        super().__init__(name)
        self.home=home


a=People("alex")
#特性1: __开头的属性或者方法 直接调用会报错,因为__的机制会把属性重新修改为_Classname__attribute的格式,具体可以查看__dict__
#a.__hobby()       # AttributeError: 'People' object has no attribute '__hobby'
#print(a.__type)  #  AttributeError: 'People' object has no attribute '__type'

# 特性2 所以虽然是隐藏,但是知道里面的修改机制,还是可以直接调用
print(a._People__type)    # 人类
# 特性3 类的内部调用可以直接调
a.attr()    # alex喜欢自由

c = China("周杰伦","台湾")
# 特性3 __的方法和属性无法覆盖父类的.比如c的__type实际已经被内部转成了_China__type,而父类的是_People__type.所以他们不重名,也无法被覆盖