python面向对象编程

本文链接:http://blog.csdn.net/sunchengquan/article/details/84673673

 

 

面向对象编程概述


面向过程–怎么做?

  1. 把完成某一个需求的所有步骤,从头到尾逐步实现
  2. 根据开发需求,将某些功能独立的代码封装成一个又一个函数
  3. 最后完成的代码,就是顺序地调用不同的函数

特点:

  1. 注重步骤与过程,不注重职责分工
  2. 如果需求复杂,代码就会变得很复杂
  3. 开发复杂项目,没有固定的套路,开发难度很大!

面向对象–谁来做?

面向对象程序设计(object oriented programming)简称OOP。面向对象编程,使得数据的管理更加合理化和自动化,减少程序出错,少写代码,程序更加容易维护。相比较函数,面向对象是更大的封装,根据职责在一个对象中封装多个方法

1 在完成某个需求前,首先确定职责–要做的事情(方法)
2 根据职责确定不同的对象,在对象内部封装不同的方法(多个)
3 最后完成的代码,就是顺序地让不同的对象调用不同的方法

万物皆对象

类和对象是面向对象编程的两个核心概念

对象优越性

  • 封装:对象将属性和方法进行封装,可以实现信息隐蔽化,用户仅能够使用对象的方法而不能修改这些方法和属性。
  • 继承:获取父对象的属性和能力,再加上自定义的属性和能力,实现代码的重用
  • 包含:一个对象划分几部分,分别进行建模,最后组装成一个完整的对象
  • 多态性:不同对象对同一方法响应不同的行动

类和对象

类和对象是面向对象编程的两个核心概念

是对一群具有相同特征或者行为的事物的一个统称,是抽象的,不能直接使用

特征 被称为 属性

行为 被称为 方法

类(class)就相当于制造飞机的图纸,就是一个模板 ,是负责创建对象

对象(object) 是由类创建出来的一个具体存在,可以直接使用

对象 (object)就是相当于用 图纸 制造的飞机

类的创建

三要素:

  1. 类名 ,满足大驼峰命名法
  2. 属性:特征
  3. 方法:行为

大驼峰命名法:1 每个单词的首字母大写;2 单词与单词之间没有下划线 例如CapWords

  1. 创建出来的 对象 叫做 类 的 实例

  2. 创建对象的 动作 叫做 实例化

  3. 对象的属性 叫做 实例属性

  4. 对象调用的方法 叫做 实例方法

  5. 类的属性 叫做 类属性

普通方式

class Person(object):
    pass

class后面紧接着是类名,即Person,类名通常是大写开头的单词,满足大驼峰命名法,紧接着是(object),表示该类是从哪个类继承下来的,继承的概念我们后面再讲,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类

定义好了Person类,就可以根据Person类创建出Person的实例(instance),创建实例是通过类名+()实现的:

这里实例(instance) 和 对象(object)是一样一样的

tony = Person()
print(tony)
print(Person)
<__main__.Person object at 0x7f5748036470>
<class '__main__.Person'>

可以看到,变量tony指向的就是一个Person的实例,后面的0x7f574891ff98是内存地址,每个object的地址都不一样,而Person本身则是一个类。

给一个实例变量绑定属性,比如,给实例tony绑定一个sex属性:

tony.sex = 'man'
print(tony.sex)
man

由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__方法,在创建实例的时候,就把name,age等属性绑上去:

提到的第一个方法__init__ ,特殊方法,前后各有两个下划线

class Person(object):
    def __init__(self, name, weight):
        self.name = name
        self.weight = weight
        

注意到__init__方法的第一个参数永远是self,表示创建的实例本身,而非类,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身

richard = Person("理查德", 60)
print(richard.name)
print(richard.weight)
理查德
60

和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数

特殊方式

def run(self):
    print("%s 爱跑步,跑步锻炼身体" % self.name)

def eat(self):
    print("%s 是吃货,吃完这顿再减肥" % self.name)
    self.weight += 1

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

Person = type('Person', (object,), {'run':run, 'eat':eat, '__init__':__init__})

mary = Person("mary",70)
mary.eat()
mary 是吃货,吃完这顿再减肥
  • type第一个参数:类名
  • type第二个参数:当前类的基类
  • type第三个参数:类的成员

类的封装

对象方法的细节都被封装在类的内部,调用很容易,但却不用知道内部实现的细节

多个对象属性之间互不干扰

class Person(object):
    def __init__(self, name, weight):
        self.name = name
        self.weight = weight

    def __str__(self):
        return "我的名字叫 %s 体重是 %.2f 公斤" % (self.name, self.weight)

    def run(self):
        print("%s 爱跑步,跑步锻炼身体" % self.name)
        self.weight -= 0.5

    def eat(self):
        print("%s 是吃货,吃完这顿再减肥" % self.name)
        self.weight += 1
        

这里又提到一个特殊方法__str__,如果一个类中定义了__str__方法,那么在打印 对象 时,默认输出该方法的返回值

nina = Person("nina",55)
print(nina)
nina.run()
print(nina)
我的名字叫 nina 体重是 55.00 公斤
nina 爱跑步,跑步锻炼身体
我的名字叫 nina 体重是 54.50 公斤

类的继承

继承概念:

子类 拥有 父类 的所有 方法 和 属性

继承语法:

class 类名(父类名):
    pass

实例演示

  • 不使用继承开发
class Animal(object):

    def eat(self):
        print("吃")

    def drink(self):
        print("喝")

    def run(self):
        print("跑")

    def sleep(self):
        print("睡")


class Dog(object):

    def eat(self):
        print("吃")

    def drink(self):
        print("喝")

    def run(self):
        print("跑")

    def sleep(self):
        print("睡")

    def bark(self):
        print("汪汪叫")


wangcai = Dog()
wangcai.eat()
wangcai.drink()
wangcai.run()
wangcai.sleep()
wangcai.bark()
吃
喝
跑
睡
汪汪叫
  • 使用继承开发,修改上面的例子
class Animal(object):

    def eat(self):
        print("吃")

    def drink(self):
        print("喝")

    def run(self):
        print("跑")

    def sleep(self):
        print("睡")


class Dog(Animal):

    def bark(self):
        print("汪汪叫")

        
wangcai = Dog()
wangcai.eat()
wangcai.drink()
wangcai.run()
wangcai.sleep()
wangcai.bark()

吃
喝
跑
睡
汪汪叫

专业术语:

  • Dog 类 是 Animal 类的 子类 ,Animal类是Dog类的 父类,Dog类 从 Aniaml类继承
  • Dog 类 是 Animal 类的 派生类,Animal类是Dog类的 基类,Dog类 从 Aniaml类派生

继承的传递性

class Animal(object):

    def eat(self):
        print("吃")

    def drink(self):
        print("喝")

    def run(self):
        print("跑")

    def sleep(self):
        print("睡")


class Dog(Animal):

    def bark(self):
        print("汪汪叫")

        
class XiaoTianQuan(Dog):

    def fly(self):
        print("我会飞")
        
xtq = XiaoTianQuan()
xtq.fly()
xtq.bark()
xtq.eat()
我会飞
汪汪叫
吃
class Animal(object):

    def eat(self):
        print("吃")

    def drink(self):
        print("喝")

    def run(self):
        print("跑")

    def sleep(self):
        print("睡")

class Dog(Animal):

    def bark(self):
        print("汪汪叫")


class XiaoTianQuan(Dog):

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


class Cat(Animal):

    def catch(self):
        print("抓老鼠")


xtq = XiaoTianQuan()
xtq.fly()
xtq.bark()
xtq.eat()

xtq.catch()

我会飞
汪汪叫
吃



---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-45-c9f717720499> in <module>
     36 xtq.eat()
     37 
---> 38 xtq.catch()


AttributeError: 'XiaoTianQuan' object has no attribute 'catch'

覆盖父类的方法

如果子类中,重写了父类的方法

在使用子类对象调用方法时,会调用子类中重写的方法

class Dog(Animal):

    def bark(self):
        print("汪汪叫")


class XiaoTianQuan(Dog):

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

    def bark(self):
        print("叫得跟神一样...")


xtq = XiaoTianQuan()
xtq.bark()
叫得跟神一样...

多态

不同对象对同一方法响应不同的行动

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

    def talk(self): 
        pass 

    @staticmethod
    def animal_talk(obj):
        obj.talk()

class Cat(Animal):
    def talk(self):
        print('miao miao')


class Dog(Animal):
    def talk(self):
        print('wang wang')

d = Dog("哮天犬")
d.talk()

c = Cat("加菲")
c.talk()

# def animal_talk(obj):
# obj.talk()

Animal.animal_talk(c)
Animal.animal_talk(d)

e= Animal('xxx')
e.animal_talk(c)
e.animal_talk(d)
wang wang
miao miao
miao miao
wang wang
miao miao
wang wang

对父类方法进行拓展

两种解决方法:

  • 调用未绑定的父类方法(通过父类名调用父类的方法)
  • 使用super().父类方法
import random as r

class Fish(Animal):
    def __init__(self):
        self.x = r.randint(0, 10)
        self.y = r.randint(0, 10)

    def move(self):
        self.x -= 1
        print('我的位置是:', self.x, self.y)

class Goldfish(Fish):
    pass

class Carp(Fish):
    pass

class Salmon(Fish):
    pass

shark在保留自身的构造方法__init__同时也继承父类构造方法

super() -> same as super(__class__, <first argument>)

class Shark(Fish): 
    def __init__(self):
        self.hungry = True
#         super().__init__() 
        super(Shark, self).__init__()
    def eat(self):
        if self.hungry:
            print('吃货的梦想就是天天有的吃')
            self.hungry = False
        else:
            print('太撑了,吃不下了')

shark = Shark()
shark.eat()
shark.move()
吃货的梦想就是天天有的吃
我的位置是: 6 7
class Shark(Fish):
    def __init__(self):
        self.hungry = True
        Fish.__init__(self)
        
    def eat(self):
        if self.hungry:
            print('吃货的梦想就是天天有的吃')
            self.hungry = False
        else:
            print('太撑了,吃不下了')

shark = Shark()
shark.eat()
shark.move()
吃货的梦想就是天天有的吃
我的位置是: 0 2

访问限制

内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问

在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__这样的变量名。

class Fish(Animal):
    def __init__(self,name):
        self._name = name
        self.x = r.randint(0, 10)
        self.y = r.randint(0, 10)

    def _move(self):
        self.x -= 1
        print('我的位置不可知')
        
    def move(self):
        self.x -= 1
        print('我的位置是:', self.x, self.y)
        
fish = Fish('美人鱼')
print(fish._name)
fish._move()
美人鱼
我的位置不可知

有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”

class Fish(Animal):
    def __init__(self,name):
        self.__name = name
        self.x = r.randint(0, 10)
        self.y = r.randint(0, 10)
    
    def __move(self):
        self.x -= 1
        print('我的位置不可知')
        
    def move(self):
        self.x -= 1
        print('我的位置是:', self.x, self.y)
        
fish = Fish('美人鱼')
print(fish.__name)
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-116-6f92b2f7d0cd> in <module>
     14 
     15 fish = Fish('美人鱼')
---> 16 print(fish.__name)


AttributeError: 'Fish' object has no attribute '__name'

双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为Python解释器对外把__name变量改成了_Fish__name,所以,仍然可以通过_Fish__name来访问__name变量

print(fish._Fish__name)
fish._Fish__move()
美人鱼
我的位置不可知

但是如果外部代码要获取name怎么办?可以给Fish类增加get_name这样的方法:

class Fish(Animal):
    def __init__(self,name):
        self.__name = name
        self.x = r.randint(0, 10)
        self.y = r.randint(0, 10)
    
    def get_name(self):
        print('我是%s'% self.__name)
    
    def move(self):
        self.x -= 1
        print('我的位置是:', self.x, self.y)
        

fish = Fish('美人鱼')
fish.get_name()
我是美人鱼
fish.get_name()
fish.__name = '人鱼小姐'
print(fish.__name)
我是美人鱼
人鱼小姐

表面上看,外部代码“成功”地设置了__name变量,但实际上这个__name变量和class内部的__name变量不是一个变量!内部的__name变量已经被Python解释器自动改成了_Fish__name,而外部代码给fish新增了一个__name变量。不信试试

fish.get_name()
我是美人鱼

对父类的访问限制

class Fish(Animal):
    def __init__(self,name):
        self.__name = name
        self.x = r.randint(0, 10)
        self.y = r.randint(0, 10)
    
    def get_name(self):
        print('我是%s'% self.__name)
    
    def __move(self):
        self.x -= 1
        print('我的位置不可知')
    
    def move(self):
        self.x -= 1
        self.__move()
        print('我的位置是:', self.x, self.y)

class Shark(Fish):
    
    def eat(self):
        self.__move()

fish = Shark("大白鲨")
fish.eat()

---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-121-859ae082132f> in <module>
     23 
     24 fish = Shark("大白鲨")
---> 25 fish.eat()


<ipython-input-121-859ae082132f> in eat(self)
     20 
     21     def eat(self):
---> 22         self.__move()
     23 
     24 fish = Shark("大白鲨")


AttributeError: 'Shark' object has no attribute '_Shark__move'

在子类的对象方法中,不能调用父类的私有方法

class Shark(Fish):
    
    def eat(self):
        self.move()

fish = Shark("大白鲨")
fish.eat()

我的位置不可知
我的位置是: -2 10

在子类的对象方法中,调用父类的公有方法

获取对象信息

type()

基本类型都可以用type()判断:

print(type(123))
print(type('str'))
print(type(None))
<class 'int'>
<class 'str'>
<class 'NoneType'>

如果一个变量指向函数或者类,也可以用type()判断:

print(type(abs))
print(type(fish))

<class 'builtin_function_or_method'>
<class '__main__.Shark'>

但是type()函数返回的是什么类型呢?它返回对应的Class类型。如果我们要在if语句中判断,就需要比较两个变量的type类型是否相同:

print(type(123)==type(456))
print(type(123)==int)
print(type(123)==str)
True
True
False

判断基本数据类型可以直接写intstr等,但如果要判断一个对象是否是函数怎么办?可以使用types模块中定义的常量

import types

def fn():
    pass

print(type(fn)==types.FunctionType)
print(type(abs)==types.BuiltinFunctionType)
print(type(lambda x: x)==types.LambdaType)
print(type((x for x in range(10)))==types.GeneratorType)
True
True
True
True

isinstance()

对于class的继承关系来说,使用type()就很不方便。我们要判断class的类型,可以使用isinstance()函数。

我们回顾上次的例子,如果继承关系是:

根类object---->Animal---->Fish---->Shark
根类object---->Animal---->Dog---->XiaoTianQuan

那么,isinstance()就可以告诉我们,一个对象是否是某种类型。先创建2种类型的对象:

a = Fish('x')
b = Shark('xx')
d = Dog("哮天犬")

然后,判断:

print(isinstance(b, Shark))
True

没有问题,因为b变量指向的就是Shark对象。

再判断:

print(isinstance(b, Fish))
True

b虽然自身是Shark类型,但由于Shark是从Fish继承下来的,所以,b也还是Fish类型。换句话说,isinstance()判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上。

因此,我们可以确信,b还是Animal类型:

print(isinstance(b, Animal))
print(isinstance(d, Animal))
True
True
print(isinstance(d, Dog))
print(isinstance(d, Fish))
True
False

能用type()判断的基本类型也可以用isinstance()判断:

print(isinstance('a', str))
print(isinstance(123, int))
True
True

并且还可以判断一个变量是否是某些类型中的一种,比如下面的代码就可以判断是否是list或者tuple:

print(isinstance([1, 2, 3], (list, tuple)))
print(isinstance((1, 2, 3), (list, tuple)))
True
True

总是优先使用isinstance()判断类型,可以将指定类型及其子类“一网打尽”

dir()

如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法:

print(dir('ABC'))
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

类似__xxx__的属性和方法在Python中都是有特殊用途的,比如__len__方法返回长度。在Python中,如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的__len__()方法,所以,下面的代码是等价的:

print(len('ABC'))
print('ABC'.__len__())
3
3

我们自己写的类,如果也想用len(myObj)的话,就自己写一个__len__()方法:

class Mylen(str):
    def __len__(self):
        return 1
    
mylen = Mylen()
len(mylen)


1

反射

仅仅把属性和方法列出来是不够的,配合getattr()setattr()delattr()以及hasattr(),我们可以直接操作一个对象的状态

class MyObject(object):
    def __init__(self):
        self.x = 9
    def power(self):
        return self.x * self.x

obj = MyObject()

紧接着,可以测试该对象的属性:

print(hasattr(obj, 'x'))
print(hasattr(obj, 'y'))

setattr(obj, 'y', 19) 
print(hasattr(obj, 'y'))

print(getattr(obj, 'y'))
print(obj.y)

delattr(obj, 'y')
print(getattr(obj, 'y'))
True
False
True
19
19



---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-165-f0feb18a0533> in <module>
      9 
     10 delattr(obj, 'y')
---> 11 print(getattr(obj, 'y'))


AttributeError: 'MyObject' object has no attribute 'y'

如果试图获取不存在的属性,会抛出AttributeError的错误:

getattr(obj, 'z') 
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-166-12ff9bb1afd4> in <module>
----> 1 getattr(obj, 'z')


AttributeError: 'MyObject' object has no attribute 'z'

可以传入一个default参数,如果属性不存在,就返回默认值:

print(getattr(obj, 'z', 404))
404

反射实例演示

def bulk(self):
    print("%s is yelling...." %self.name)

class Dog(object):
    def __init__(self,name):
        self.name = name

    def eat(self,food):
        print("%s is eating..."%self.name,food)


d = Dog("xiaotianquan")
choice = input(">>:").strip()


if hasattr(d,choice):   
    func= getattr(d,choice) #获取成员对象方法的内存地址
    func('apple')
else:
    setattr(d,choice,bulk) #d.talk = bulk
    func = getattr(d, choice)
    func(d)
>>:talk
xiaotianquan is yelling....

多重继承

py3 经典类和新式类都是统一按广度优先来继承的

按广度,D继承B,没有再继承C,没有最后继承A

class A(object):
    def __init__(self):
        print("A")


class B(A):

    def __init__(self):
        print("B")

    # def test(self):
    #     print("B --- test 方法")
    #
    # def demo(self):
    #     print("B --- demo 方法")


class C(A):
    def __init__(self):
        print("C")

    def test(self):
        print("C --- test 方法")

    def demo(self):
        print("C --- demo 方法")


class D(B, C):
    """多继承可以让子类对象,同时具有多个父类的属性和方法"""
    pass


# 创建子类对象
d = D()

# 确定D类对象调用方法的顺序
print(D.__mro__)
B
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
d.test()
d.demo()
C --- test 方法
C --- demo 方法

py2 经典类是按深度优先来继承的,新式类是按广度优先来继承的

新式类经典类 在多继承时 —— 会影响到方法的搜索顺序

为了保证编写的代码能够同时在 Python 2.xPython 3.x 运行!
今后在定义类时,如果没有父类,建议统一继承自 object

类的属性

复习概念:

  1. 创建出来的 对象 叫做 类 的 实例

  2. 创建对象的 动作 叫做 实例化

  3. 对象的属性 叫做 实例属性, 定义时使用self.属性名

  4. 对象调用的方法 叫做 实例方法

  5. 类属性 就是针对 类对象 定义的属性, 同一个类的所有实例所共有的,引用时要使用 类名.类变量名

    • 类属性 用于记录 与这个类相关 的特征
  6. 类方法 就是针对 类对象 定义的方法

    • 类方法 内部可以直接访问 类属性 或者调用其他的 类方法

实例变量(实例属性)与类变量(类属性)的区别:

实例变量存在每个实例的内存里,每个实例之间相互不影响

类变量存在类的内存里,相当于所有的实例共享这个内存

实例演示

class Property(object):
    class_name = 'Property'
    def __init__(self, x=0):
        self.x = x
        print('初始化实例')
    
    def class_info(self):
        print("类变量值:", Property.class_name)
        print('实例变量值:',self.x)
        
    def change(self, x):
        print('修改Property实例变量为', x)
        self.x = x
        
    def change_property(self, name):
        print('修改Property类变量为', name)
        Property.class_name = name

sun = Property() 
初始化实例
sun.class_info()
类变量值: changed
实例变量值: 0
sun.change(3)
修改Property实例变量为 3
sun.class_info()
类变量值: changed
实例变量值: 3
sun.change_property("changed")
修改Property类变量为 changed
sun.class_info()
类变量值: changed
实例变量值: 3

假设类变量与实例变量重名,会出现什么情况?

class Property(object):
    name = 'Property'
    def __init__(self, x=0):
        self.x = x
        print('初始化实例')
    
    def class_info(self):
        print("类变量值:", Property.name)
        print('实例变量值:',self.x)
        
    def change(self, x):
        print('修改Property实例变量为', x)
        self.x = x
        
    def change_property(self, name):
        print('修改Property类变量为', name)
        Property.name = name
p = Property()
初始化实例

打印name属性,因为实例并没有name属性,所以会继续查找class的name属性

print(p.name)
Property

打印类的name属性

print(Property.name) 
Property

给实例绑定name属性

p.name = 'richard' 

由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性

print(p.name) 
richard

但是类属性并未消失,用Property.name仍然可以访问

print(Property.name) 
Property

如果删除实例的name属性

del p.name 

再次调用p.name,由于实例的name属性没有找到,类的name属性就显示出来了

print(p.name)
Property

在编写程序的时候,千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性

实例练习

为了统计学生人数,可以给Student类增加一个类属性,每创建一个实例,该属性自动增加:

class Student(object):
    count = 0

    def __init__(self, name):
        self.name = name
        Student.count += 1     
        

测试

if Student.count != 0:
    print('测试失败!')
else:
    bart = Student('Bart')
    if Student.count != 1:
        print('测试失败!')
    else:
        lisa = Student('Bart')
        if Student.count != 2:
            print('测试失败!')
        else:
            print('Students:', Student.count)
            print('测试通过!')
Students: 2
测试通过!

类的方法

__init__

初始化方法(构造方法)

class Cat(object):
    def __init__(self, new_name):
        print("这是一个初始化方法")
        self.name = new_name

tom = Cat("Tom")
这是一个初始化方法

__del__

析构方法

应用场景

  • __init__ 改造初始化方法,可以让创建对象更加灵活
  • __del__ 如果希望在对象被销毁前,再做一些事情,可以考虑一下 __del__ 方法
class Cat(object):

    def __init__(self, new_name):
        self.name = new_name
        print("%s 来了" % self.name)

    def __del__(self):
        print("%s 我去了" % self.name)
tom = Cat("Tom")
# del tom
Tom 来了
Tom 我去了

__doc__

表示类的描述信息

class Cat(object):
    """描述类信息,这是一个喵喵"""
    def __init__(self, new_name):
        self.name = new_name
        print("%s 来了" % self.name)

    def __del__(self):
        print("%s 我去了" % self.name)
print(Cat.__doc__)
描述类信息,这是一个喵喵

__str__

Python 中,使用 print 输出 对象变量,默认情况下,会输出这个变量 引用的对象由哪一个类创建的对象,以及 在内存中的地址十六进制表示

如果在开发中,希望使用 print 输出 对象变量 时,能够打印 自定义的内容,就可以利用 __str__ 这个内置方法了

class Cat(object):
    """描述类信息,这是一个喵喵"""
    def __init__(self, new_name):
        self.name = new_name
        print("%s 来了" % self.name)

    def __del__(self):
          print("%s 我去了" % self.name)
            
    def __str__(self):
         # 必须返回一个字符串
        return "我是小猫[%s]" % self.name
tom = Cat("Tom")
print(tom)
Tom 来了
Tom 我去了
我是小猫[Tom]

__call__

对象后面加括号,触发执行,可调用

注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()

class Cat(object):
    """描述类信息,这是一个喵喵"""
    def __init__(self, new_name):
        self.name = new_name
        print("%s 来了" % self.name)

    def __del__(self):
          print("%s 我去了" % self.name)
            
    def __str__(self):
         # 必须返回一个字符串
        return "我是小猫[%s]" % self.name
    
    def __call__(self):
        print("执行__call__方法")
        

tom = Cat("Tom") 
tom()
Tom 来了
Tom 我去了
执行__call__方法

__class__

表示当前操作的对象的类是什么

print(tom.__class__)
<class '__main__.Cat'>

__module__

表示当前操作的对象在那个模块

__main__ 表示没有从别的模块中导入

print(tom.__module__) 
__main__
lib = __import__('lib.test')
tom = lib.test.Person()
print(tom.__module__) 
lib.test

__dict__

查看类或对象中的所有成员 ,输出结果是什么格式?

# 获取类的成员,即:静态字段、方法、
print(Cat.__dict__)
{'__module__': '__main__', '__doc__': '描述类信息,这是一个喵喵', '__init__': <function Cat.__init__ at 0x7f5733fbe620>, '__del__': <function Cat.__del__ at 0x7f5733fbe510>, '__str__': <function Cat.__str__ at 0x7f5733fbe1e0>, '__call__': <function Cat.__call__ at 0x7f5733fbe268>, '__dict__': <attribute '__dict__' of 'Cat' objects>, '__weakref__': <attribute '__weakref__' of 'Cat' objects>}
# 获取 对象tom 的成员
tom = Cat("Tom") 
print(tom.__dict__)
Tom 来了
Tom 我去了
{'name': 'Tom'}

__iter__

如果类加上__iter__, 实例化后的对象是一个可迭代对象

from collections import Iterable

print(isinstance('abc',Iterable))
print(isinstance([],Iterable))
print(isinstance({},Iterable))
True
True
True
class Cat(object):
    """描述类信息,这是一个喵喵"""
    def __init__(self, new_name):
        self.name = new_name
        print("%s 来了" % self.name)

    def __del__(self):
          print("%s 我去了" % self.name)
            
    def __str__(self):
         # 必须返回一个字符串
        return "我是小猫[%s]" % self.name
    
    def __call__(self):
        print("执行__call__方法")
        
    def __iter__(self):
        return self 

tom = Cat("Tom")
Tom 来了
Tom 我去了
print(isinstance(tom ,Iterable))
print(isinstance(tom ,Iterator))
True
False

__next__

把一个类作为一个迭代器使用需要在类中实现两个方法 __iter__()__next__()

class Cat(object):
    """描述类信息,这是一个喵喵"""
    def __init__(self, new_name):
        self.name = new_name
        self.num = 0
        print("%s 来了" % self.name)

    def __del__(self):
          print("%s 我去了" % self.name)
            
    def __str__(self):
         # 必须返回一个字符串
        return "我是小猫[%s]" % self.name
    
    def __call__(self):
        print("执行__call__方法")
        
    def __iter__(self):
        return self 

    def __next__(self):
        self.num += 1
        if self.num > 6:
            raise StopIteration()
        print('%s 产下了第%d个小猫'%(self.name,self.num))
        
tom = Cat("Tom")

Tom 来了
Tom 我去了
from collections import Iterable
from collections import Iterator

print(isinstance(tom ,Iterable))
print(isinstance(tom ,Iterator))
True
True
next(tom)
Tom 产下了第1个小猫
next(tom)
Tom 产下了第2个小猫
for i in tom:
    pass
Tom 产下了第3个小猫
Tom 产下了第4个小猫
Tom 产下了第5个小猫
Tom 产下了第6个小猫
tom[0]
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-299-fb6fc0384b04> in <module>
----> 1 tom[0]


TypeError: 'Cat' object does not support indexing

__getitem__

要表现得像list那样按照下标取出元素,需要实现__getitem__()方法:

class Cat(object):
    """描述类信息,这是一个喵喵"""
    def __init__(self, new_name):
        self.name = new_name
        self.num = 0
        print("%s 来了" % self.name)

    def __del__(self):
          print("%s 我去了" % self.name)
            
    def __str__(self):
         # 必须返回一个字符串
        return "我是小猫[%s]" % self.name
    
    def __call__(self):
        print("执行__call__方法")
        
    def __iter__(self):
        return self 

    def __next__(self):
        self.num += 1
        if self.num > 6:
            raise StopIteration()
        print('%s 产下了第%d小猫'%(self.name,self.num))
        
    def __getitem__(self, n):
        a = "第1个小猫"
        for x in range(n):
            a = '第%d个小猫'% (x+2)
        return a
        
tom = Cat("Tom")
Tom 来了
Tom 我去了
print(tom[0])
第1个小猫
print(tom[8])
第9个小猫
tom[0:2]
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-305-ca1631d2d9b8> in <module>
----> 1 tom[0:2]


<ipython-input-301-07ecfbc21c8b> in __getitem__(self, n)
     27     def __getitem__(self, n):
     28         a = "第1个小猫"
---> 29         for x in range(n):
     30             a = '第%d个小猫'% (x+2)
     31         return a


TypeError: 'slice' object cannot be interpreted as an integer

报错,原因是__getitem__()传入的参数可能是一个int,也可能是一个切片对象slice,所以要做判断:

class Cat(object):
    """描述类信息,这是一个喵喵"""
    def __init__(self, new_name):
        self.name = new_name
        self.num = 0
        print("%s 来了" % self.name)

    def __del__(self):
          print("%s 我去了" % self.name)
            
    def __str__(self):
         # 必须返回一个字符串
        return "我是小猫[%s]" % self.name
    
    def __call__(self):
        print("执行__call__方法")
        
    def __iter__(self):
        return self 

    def __next__(self):
        self.num += 1
        if self.num > 6:
            raise StopIteration()
        print('%s 产下了第%d小猫'%(self.name,self.num))
        
    def __getitem__(self, n):
        if isinstance(n, int): # n是索引
            a = "第1个小猫"
            for x in range(n):
                a = '第%d个小猫'% (x+2)
            return a
        elif isinstance(n, slice): # n是切片
            start = n.start
            stop = n.stop
            if start is None:
                start = 0
            a = "第1个小猫"
            L = []
            for x in range(stop):
                a = '第%d个小猫'% (x+2)
                L.append(a)
            return L
    
tom = Cat("Tom")
print(tom[8])
print(tom[:2])
Tom 来了
Tom 我去了
第9个小猫
['第2个小猫', '第3个小猫']

__setitem____delitem__

__setitem__()方法,把对象视作list或dict来对集合赋值。最后,还有一个__delitem__()方法,用于删除某个元素

class Cat(object):
    """描述类信息,这是一个喵喵"""
    def __init__(self, new_name):
        self.name = new_name
        self.num = 0
        print("%s 来了" % self.name)

    def __del__(self):
          print("%s 我去了" % self.name)
            
    def __str__(self):
         # 必须返回一个字符串
        return "我是小猫[%s]" % self.name
    
    def __call__(self):
        print("执行__call__方法")
        
    def __iter__(self):
        return self 

    def __next__(self):
        self.num += 1
        if self.num > 6:
            raise StopIteration()
        print('%s 产下了第%d小猫'%(self.name,self.num))
        
    def __getitem__(self, n):
        if isinstance(n, int): # n是索引
            return self.__dict__[n]
        elif isinstance(n, slice): # n是切片
            start = n.start
            stop = n.stop
            if start is None:
                start = 0
            L = [self.__dict__[i] for i in range(start,stop)]
            return L
    
    def __setitem__(self, key, value):
        self.__dict__[key] = value
        print("添加", value, "成功")
        
    def __delitem__(self, key):
        if key in self.__dict__:
            print("删除", self.__dict__[key], "成功")
            self.__dict__.pop(key)
        
        
tom = Cat("Tom")      
        
Tom 来了
for x in range(8):
    tom[x+1] = '第%d个小猫'% (x+1)

添加 第1个小猫 成功
添加 第2个小猫 成功
添加 第3个小猫 成功
添加 第4个小猫 成功
添加 第5个小猫 成功
添加 第6个小猫 成功
添加 第7个小猫 成功
添加 第8个小猫 成功
for i in range(1,9):
    print(tom[i])
第1个小猫
第2个小猫
第3个小猫
第4个小猫
第5个小猫
第6个小猫
第7个小猫
第8个小猫
print(tom[6:8])

['第6个小猫', '第7个小猫']
del tom[6]
print(tom[7])
print(tom[6])
删除 第6个小猫 成功
第7个小猫



---------------------------------------------------------------------------

KeyError                                  Traceback (most recent call last)

<ipython-input-410-20201facdff5> in <module>
      1 del tom[6]
      2 print(tom[7])
----> 3 print(tom[6])


<ipython-input-405-9dc9bf9cea6c> in __getitem__(self, n)
     27     def __getitem__(self, n):
     28         if isinstance(n, int): # n是索引
---> 29             return self.__dict__[n]
     30         elif isinstance(n, slice): # n是切片
     31             start = n.start


KeyError: 6

__getattr__

如果属性查找(attribute lookup)在实例以及对应的类中(通过__dict__)失败, 那么会调用到类的__getattr__函数, 如果没有定义这个函数,那么抛出AttributeError异常。由此可见,__getattr__一定是作用于属性查找的最后一步,兜底

tom.sun
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-94-4ce2bf3c8da4> in <module>
----> 1 tom.sun


AttributeError: 'Cat' object has no attribute 'sun'

class Cat(dict):
    """描述类信息,这是一个喵喵"""
    def __init__(self,name, *args, **kwargs):
        super(Cat, self).__init__(*args, **kwargs)
        self.name = name
        self.num = 0
        self.value = ''

    def __del__(self):
          pass

    
    def __call__(self):
        print("执行__call__方法")
        
    def __iter__(self):
        return self 

    def __next__(self):
        self.num += 1
        if self.num > 6:
            raise StopIteration()
        print('%s 产下了第%d小猫'%(self.name,self.num))
        
    def __getitem__(self, n):
        if isinstance(n, int): # n是索引
            return self.__dict__[n]
        elif isinstance(n, slice): # n是切片
            start = n.start
            stop = n.stop
            if start is None:
                start = 0
            L = [self.__dict__[i] for i in range(start,stop)]
            return L
    
    def __setitem__(self, key, value):
        self.__dict__[key] = value
        print("添加", value, "成功")
        
    def __delitem__(self, key):
        if key in self.__dict__:
            print("删除", self.__dict__[key], "成功")
            self.__dict__.pop(key)
    
    def __getattr__(self, name):
        self.value =  self.get(name,'sunchengquan')
        if isinstance(self.value, dict):
            self.value =Cat(self.name ,self.value)
        return self.value
     
        
tom = Cat("Tom")  
print(tom.sun)
sunchengquan

可以像访问属性一样访问dict中的键值对

tom = Cat("Tom",ab={'a': 1,'b':2}, c=True)  
print(tom.ab)
print(tom.ab.b)
print(tom.c)
{'a': 1, 'b': 2}
2
True

__add__; __sub__ ; __mul____truediv__ ; __mod__

加运算,减运算,乘运算, 除运算,: 求余运算

class Cat(dict):
    """描述类信息,这是一个喵喵"""
    def __init__(self,name, a, b, *args, **kwargs):
        super(Cat, self).__init__(*args, **kwargs)
        self.name = name
        self.num = 0
        self.value = ''
        self.a = a
        self.b = b

    def __del__(self):
          pass
    
    def __call__(self):
        print("执行__call__方法")
        
    def __iter__(self):
        return self 

    def __next__(self):
        self.num += 1
        if self.num > 6:
            raise StopIteration()
        print('%s 产下了第%d小猫'%(self.name,self.num))
        
    def __getitem__(self, n):
        if isinstance(n, int): # n是索引
            return self.__dict__[n]
        elif isinstance(n, slice): # n是切片
            start = n.start
            stop = n.stop
            if start is None:
                start = 0
            L = [self.__dict__[i] for i in range(start,stop)]
            return L
    
    def __setitem__(self, key, value):
        self.__dict__[key] = value
        print("添加", value, "成功")
        
    def __delitem__(self, key):
        if key in self.__dict__:
            print("删除", self.__dict__[key], "成功")
            self.__dict__.pop(key)
    
    def __getattr__(self, name):
        self.value =  self.get(name,'sunchengquan')
        if isinstance(self.value, dict):
            self.value =Cat(self.name ,self.a, self.b, self.value)
        return self.value
    
    def __add__(self,other):
        return Cat(self.name, self.a + other.a, self.b + other.b)
        
    def __sub__(self,other):
        return Cat(self.name, self.a - other.a, self.b - other.b)
        
    def __mul__(self,other):
        return Cat(self.name, self.a * other.a, self.b * other.b)
        
    def __truediv__(self,other):
        return Cat(self.name, self.a / other.a, self.b / other.b)
    
    def __mod__(self,other):
        return Cat(self.name, self.a % other.a, self.b % other.b)
    
    def __str__(self):
        return '(%d, %d)' % (self.a, self.b)
tom1 = Cat("Tom",8, 4)  
tom2 = Cat("Tom",3, 4)  
print(tom1+tom2)
print(tom1-tom2)
print(tom1*tom2)
print(tom1/tom2)
print(tom1%tom2)
(11, 8)
(5, 0)
(24, 16)
(2, 1)
(2, 0)

__cmp__

对 int、str 等内置数据类型排序时,Python的 sorted() 按照默认的比较函数 cmp 排序,但是,如果对一组 Student 类的实例排序时,就必须提供我们自己的特殊方法 __cmp__()

class Cat(object):
    """描述类信息,这是一个喵喵"""
    def __init__(self,name, a, b, *args, **kwargs):
        super(Cat, self).__init__(*args, **kwargs)
        self.name = name
        self.num = 0
        self.value = ''
        self.a = a
        self.b = b

    def __del__(self):
          pass
    
    def __call__(self):
        print("执行__call__方法")
        
    def __iter__(self):
        return self 

    def __next__(self):
        self.num += 1
        if self.num > 6:
            raise StopIteration()
        print('%s 产下了第%d小猫'%(self.name,self.num))
    
    def __add__(self,other):
        return Cat(self.name, self.a + other.a, self.b + other.b)
        
    def __sub__(self,other):
        return Cat(self.name, self.a - other.a, self.b - other.b)
        
    def __mul__(self,other):
        return Cat(self.name, self.a * other.a, self.b * other.b)
        
    def __truediv__(self,other):
        return Cat(self.name, self.a / other.a, self.b / other.b)
    
    def __mod__(self,other):
        return Cat(self.name, self.a % other.a, self.b % other.b)
    
    def __cmp__(self, s):
        if(self.a<s.a):
            return 1
        if(self.a>s.a):
            return -1
        if(self.a==s.a):
            if(self.name>s.name):
                return 1;
            if(self.name<s.name):
                return -1
            return 0
    
    def __str__(self):
        return '(%d, %d)' % (self.a, self.b)
    
L = [Cat('Tim', 99,1), Cat('Bob', 88,1), Cat('Alice', 99,1),100 ,"sun"]
print(sorted(L))
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-303-29f737bd28d8> in <module>
      1 L = [Cat('Tim', 99,1), Cat('Bob', 88,1), Cat('Alice', 99,1),100 ,"sun"]
----> 2 print(sorted(L))


TypeError: '<' not supported between instances of 'Cat' and 'Cat'

__lt__; __le__;__gt__; __ge__;__eq__;__ne__

上面没有解决的问题:

TypeError: '<' not supported between instances of 'Cat' and 'Cat'
class Cat(object):
    """描述类信息,这是一个喵喵"""
    def __init__(self,name, a, b, *args, **kwargs):
        self.name = name
        self.num = 0
        self.value = ''
        self.a = a
        self.b = b

    def __del__(self):
          pass
    
    def __call__(self):
        print("执行__call__方法")
        
    def __iter__(self):
        return self 

    def __next__(self):
        self.num += 1
        if self.num > 6:
            raise StopIteration()
        print('%s 产下了第%d小猫'%(self.name,self.num))
        
    
    def __add__(self,other):
        return Cat(self.name, self.a + other.a, self.b + other.b)
        
    def __sub__(self,other):
        return Cat(self.name, self.a - other.a, self.b - other.b)
        
    def __mul__(self,other):
        return Cat(self.name, self.a * other.a, self.b * other.b)
        
    def __truediv__(self,other):
        return Cat(self.name, self.a / other.a, self.b / other.b)
    
    def __mod__(self,other):
        return Cat(self.name, self.a % other.a, self.b % other.b)
    
    def __lt__(self,other):
        if self.a < other.a:
            return True
        else:
            return False
    
    def __le__(self,other):
        if self.a <= other.a:
            return True
        else:
            return False
        
    def __ge__(self,other):
        if self.a >= other.a:
            return True
        else:
            return False
        
    def __gt__(self,other):
        if self.a > other.a:
            return True
        else:
            return False
        
    def __eq__(self,other):
        if self.a == other.a:
            return True
        else:
            return False
    def __ne__(self,other):
        if self.a != other.a:
            return True
        else:
            return False
    
    def __cmp__(self, other):
        if(self.a < other.a):
            return 1
        if(self.a > other.a):
            return -1
        if(self.a == other .a):
            if(self.name > other.name):
                return 1;
            if(self.name < other.name):
                return -1
            return 0
    
    def __str__(self):
        return '(%d, %d)' % (self.a, self.b)
    
c1 = Cat('Tim', 97, 1)
c2 = Cat('Bob', 88, 1)
c3 = Cat('Alice', 99, 1)
print(c1 < c2)
print(c1 <= c2)
print(c1 == c2)
print(c1 != c2)
False
False
False
True
l = [c1,c2,c3]
print(sorted(l))
[<__main__.Cat object at 0x7f3cbc124f28>, <__main__.Cat object at 0x7f3cbc124208>, <__main__.Cat object at 0x7f3cbc124128>]

__repr__

上面没有解决的问题

[<__main__.Cat object at 0x7f3cbc124f28>, <__main__.Cat object at 0x7f3cbc124208>, <__main__.Cat object at 0x7f3cbc124128>]

__repr____str__这两个方法都是用于显示的,__str__是面向用户的,而__repr__面向程序员

重构__repr__方法后,不管直接输出对象还是通过print打印的信息都按我们__repr__方法中定义的格式进行显示了

重构__str__ 你会发现,直接输出对象ts时并没有按我们__str__方法中定义的格式进行输出,而用print输出的信息却改变了

class Cat(object):
    """描述类信息,这是一个喵喵"""
    def __init__(self,name, a, b, *args, **kwargs):
        self.name = name
        self.num = 0
        self.value = ''
        self.a = a
        self.b = b

    def __del__(self):
          pass
    
    def __call__(self):
        print("执行__call__方法")
        
    def __iter__(self):
        return self 

    def __next__(self):
        self.num += 1
        if self.num > 6:
            raise StopIteration()
        print('%s 产下了第%d小猫'%(self.name,self.num))
        
    
    def __add__(self,other):
        return Cat(self.name, self.a + other.a, self.b + other.b)
        
    def __sub__(self,other):
        return Cat(self.name, self.a - other.a, self.b - other.b)
        
    def __mul__(self,other):
        return Cat(self.name, self.a * other.a, self.b * other.b)
        
    def __truediv__(self,other):
        return Cat(self.name, self.a / other.a, self.b / other.b)
    
    def __mod__(self,other):
        return Cat(self.name, self.a % other.a, self.b % other.b)
    
    def __lt__(self,other):
        if self.a < other.a:
            return True
        else:
            return False
    
    def __le__(self,other):
        if self.a <= other.a:
            return True
        else:
            return False
        
    def __ge__(self,other):
        if self.a >= other.a:
            return True
        else:
            return False
        
    def __gt__(self,other):
        if self.a > other.a:
            return True
        else:
            return False
        
    def __eq__(self,other):
        if self.a == other.a:
            return True
        else:
            return False
    def __ne__(self,other):
        if self.a != other.a:
            return True
        else:
            return False
    
    def __cmp__(self, other):
        if self.a < other.a:
            return 1
        if self.a > other.a:
            return -1
        if self.a == other.a:
            return 0
    
    def __str__(self):
        return '(%s %d, %d)' % (self.name, self.a, self.b)
    
    __repr__ = __str__
l = [Cat('Tim', 97, 1), Cat('Bob', 88, 1),  Cat('Mary', 99, 1 ), Cat('Alice', 99, 2)]
print(sorted(l))
[(Bob 88, 1), (Tim 97, 1), (Mary 99, 1), (Alice 99, 2)]

__bool__

对类进行布尔值判断

class Cat(object):
    """描述类信息,这是一个喵喵"""
    def __init__(self,name, a, b, *args, **kwargs):
        self.name = name
        self.a = a
        self.b = b
    
    def __le__(self,other):
        if self.a <= other.a:
            return True
        else:
            return False
        
    def __ge__(self,other):
        if self.a >= other.a:
            return True
        else:
            return False
        
    def __gt__(self,other):
        if self.a > other.a:
            return True
        else:
            return False
        
    def __eq__(self,other):
        if self.a == other.a:
            return True
        else:
            return False
    def __ne__(self,other):
        if self.a != other.a:
            return True
        else:
            return False
    
    def __str__(self):
        return '(%s %d, %d)' % (self.name, self.a, self.b)
    
    __repr__ = __str__
    
    def __bool__(self):
        return bool(self.a > 5)
    
    
    
tom1 = Cat("Tom",1,2)
tom2 = Cat("Tom",8,2)

print(tom1)
print(tom2)
    
if tom1:
    print("tom1")
if tom2:
    print("tom2")
(Tom 1, 2)
(Tom 8, 2)
tom2

@staticmethod

静态方法

只是名义上归类管理, 实际上在静态方法里访问不了类或实例中的任何属性

class Cat(object):
    """描述类信息,这是一个喵喵"""
    def __init__(self,name, *args, **kwargs):
        self.name = name
    
    @staticmethod #实际上跟类没什么关系了
    def eat(self):
        print("%s is eating %s" %(self.name,'dd'))

    def talk(self):
        print("%s is talking"% self.name)
        
tom = Cat("Tom")
tom.eat()
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-344-688b08415254> in <module>
     12 
     13 tom = Cat("Tom")
---> 14 tom.eat()


TypeError: eat() missing 1 required positional argument: 'self'

怎么解决上面的问题?

调用时主动传递实例本身给eat方法,即tom.eat(tom)

tom.eat(tom)
Tom is eating dd

在eat方法中去掉self参数,但这也意味着,在eat中不能通过self.调用实例中的其它变量了

class Cat(object):
    """描述类信息,这是一个喵喵"""
    def __init__(self,name, *args, **kwargs):
        self.name = name
    
    @staticmethod #实际上跟类没什么关系了
    def eat():
        print("%s is eating %s" %(self.name,'dd'))

    def talk(self):
        print("%s is talking"% self.name)
tom = Cat("Tom")
tom.eat()
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-347-116f9837bbbc> in <module>
     11         print("%s is talking"% self.name)
     12 tom = Cat("Tom")
---> 13 tom.eat()


<ipython-input-347-116f9837bbbc> in eat()
      6     @staticmethod #实际上跟类没什么关系了
      7     def eat():
----> 8         print("%s is eating %s" %(self.name,'dd'))
      9 
     10     def talk(self):


NameError: name 'self' is not defined
class Cat(object):
    """描述类信息,这是一个喵喵"""
    def __init__(self,name, *args, **kwargs):
        self.name = name
    
    @staticmethod #实际上跟类没什么关系了
    def eat():
        print("eating %s" %('dd'))

    def talk(self):
        print("%s is talking"% self.name)
tom = Cat("Tom")
tom.eat()
eating dd

@classmethod

类方法:只能访问类变量,不能访问实例变量

class Cat(object):
    
    name = "huazai"
    def __init__(self,name):
        self.name = name
   
    @classmethod
    def eat(self):
        print("%s is eating %s" %(self.name,'dd'))

    def talk(self):
        print("%s is talking"% self.name)


cat = Cat("Tom")
cat.eat()
huazai is eating dd
class Cat(object):
    
    name = "huazai"
    def __init__(self,name):
        self.name = name
   
    @staticmethod
    def show_info():
        print("帮助信息:\n\t这是一个大肥喵\n\t主人是xxxxxxxx")

    @classmethod
    def eat(cls):
        print("%s is eating %s" %(cls.name,'dd'))

    def talk(self):
        print("%s is talking"% self.name)


cat = Cat("Tom")
cat.eat()
cat.show_info()
huazai is eating dd
帮助信息:
	这是一个大肥喵
	主人是xxxxxxxx

@property

属性方法

把一个方法变成一个静态属性

调用会出以下错误, 说NoneType is not callable, 因为eat此时已经变成一个静态属性了, 不是方法了, 想调用已经不需要加()号了,直接cat.eat就可以了

class Cat(object):
    
    name = "huazai"
    def __init__(self,name):
        self.name = name
   
    @property
    def eat(self):
        print("%s is eating %s" %(self.name,'dd'))

    def talk(self):
        print("%s is talking"% self.name)

cat = Cat("Tom")
cat.eat()     
Tom is eating dd



---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-352-254f28ff15c1> in <module>
     13 
     14 cat = Cat("Tom")
---> 15 cat.eat()


TypeError: 'NoneType' object is not callable
cat = Cat("Tom")
cat.eat
Tom is eating dd

修改属性值

class Cat(object):
    '''这个类是描述狗这个对象的'''

    def __init__(self,name):

        self.name = name
        self.__food = None

    @property 
    def eat(self):
        print("%s is eating %s" %(self.name,self.__food))
    
    @eat.setter
    def eat(self,food):
        print("set to food:",food)
        self.__food = food
    
    @eat.deleter
    def eat(self):
        del self.__food
        print("删完了")

    def talk(self):
        print("%s is talking"% self.name)
        
cat = Cat("Tom")
cat.eat
Tom is eating None
cat.eat = 'fish'
set to food: fish
cat.eat
Tom is eating fish

案例演示

把一个方法变成静态属性有什么卵用呢?既然想要静态变量,那直接定义成一个静态变量不就得了么?well, 以后你会需到很多场景是不能简单通过 定义 静态属性来实现的, 比如 ,你想知道一个航班当前的状态,是到达了、延迟了、取消了、还是已经飞走了, 想知道这种状态你必须经历以下几步:

  1. 连接航空公司API查询
  2. 对查询结果进行解析
  3. 返回结果给你的用户

因此这个status属性的值是一系列动作后才得到的结果,所以你每次调用时,其实它都要经过一系列的动作才返回你结果,但这些动作过程不需要用户关心, 用户只需要调用这个属性就可以

class Flight(object):
    def __init__(self,name):
        self.flight_name = name

    def checking_status(self):
        print("checking flight %s status " % self.flight_name)
        return 0

    @property
    def flight_status(self):
        status = self.checking_status() #相当于调航空公司的API
        if status == 0 :
            print("flight got canceled...")
        elif status == 1 :
            print("flight is arrived...")
        elif status == 2:
            print("flight has departured already...")
        else:
            print("cannot confirm the flight status...,please check later")

f = Flight("CA980")
f.flight_status
checking flight CA980 status 
flight got canceled...

这个flight_status已经是个属性了, 那我能否给它赋值呢?

需要通过@proerty.setter装饰器再装饰一下,此时 你需要写一个新方法, 对这个flight_status进行更改

class Flight(object):
    def __init__(self,name):
        self.flight_name = name
        self.status = self.checking_status() #相当于调航空公司的API
        
    def checking_status(self):
        print("checking flight %s status " % self.flight_name)
        return 0

    @property
    def flight_status(self):
        
        if self.status == 0 :
            print("flight got canceled...")
        elif self.status == 1 :
            print("flight is arrived...")
        elif self.status == 2:
            print("flight has departured already...")
        else:
            print("cannot confirm the flight status...,please check later")
    
    @flight_status.setter
    def flight_status(self,status):
        status_dic = {
            0: "canceled",
            1: "arrived",
            2: "departured"
            }
        self.status = status
        print("\033[31;1mHas changed the flight status to \033[0m", status_dic.get(status))

    @flight_status.deleter 
    def flight_status(self):
        print("status got removed...")
        self.status = None

f = Flight("CA980")
f.flight_status


checking flight CA980 status 
flight got canceled...
f.flight_status = 2 #触发@flight_status.setter
f.flight_status

[31;1mHas changed the flight status to [0m departured
flight has departured already...
del f.flight_status #触发@flight_status.deleter

status got removed...
f.flight_status
cannot confirm the flight status...,please check later

__new__

__new__ 是一个 由 object 基类提供的 内置的静态方法,主要作用有两个:
* 1) 在内存中为对象 分配空间
* 2) 返回 对象的引用

  • Python 的解释器获得对象的 引用 后,将引用作为 第一个参数,传递给 __init__ 方法

重写 __new__ 方法 的代码非常固定!

  • 重写 __new__ 方法 一定要 return super().__new__(cls)
  • 否则 Python 的解释器 得不到 分配了空间的 对象引用就不会调用对象的初始化方法
  • 注意:__new__ 是一个静态方法,在调用时需要 主动传递 cls 参数
class Cat(object):
    def __new__(cls, *args, **kwargs):
        print("创建对象,分配空间")
        instance = super().__new__(cls)
        return instance

    def __init__(self):
        print("初始化")



cat = Cat()

创建对象,分配空间
初始化
cat1 = Cat()
print(cat1)
cat2 = Cat()
print(cat2)


print('-'*50)
class Cat(object):
    pass


cat1 = Cat()
print(cat1)
cat2 = Cat()
print(cat2)
创建对象,分配空间
初始化
<__main__.Cat object at 0x7f3cbc0dff60>
创建对象,分配空间
初始化
<__main__.Cat object at 0x7f3cbc0dfcf8>
--------------------------------------------------
<__main__.Cat object at 0x7f3cbc0dff98>
<__main__.Cat object at 0x7f3cbc0dff60>

单例设计模式

  • 单例设计模式
    • 目的 —— 让 创建的对象,在系统中 只有 唯一的一个实例
    • 每一次执行 类名() 返回的对象,内存地址是相同的

单例设计模式的应用场景

  • 音乐播放 对象
  • 回收站 对象
  • 打印机 对象

单例流程

1. 定义一个 **类属性**,初始值是 `None`,用于记录 **单例对象的引用**
2. 重写 `__new__` 方法
3. 如果 **类属性** `is None`,调用父类方法分配空间,并在类属性中记录结果
4. 返回 **类属性** 中记录的 **对象引用**
class Cat(object):

    # 定义类属性记录单例对象引用
    instance = None

    def __new__(cls, *args, **kwargs):

        # 1. 判断类属性是否已经被赋值
        if cls.instance is None:
            cls.instance = super().__new__(cls)
            print("创建对象,分配空间")
        # 2. 返回类属性的单例引用
        return cls.instance
    
    def __init__(self):
        print("初始化")
        
        
    
cat1 = Cat()
print(cat1)
cat2 = Cat()
print(cat2)
创建对象,分配空间
初始化
<__main__.Cat object at 0x7f3cbc0c44e0>
初始化
<__main__.Cat object at 0x7f3cbc0c44e0>

只执行一次初始化工作

  • 在每次使用 类名() 创建对象时,Python 的解释器都会自动调用两个方法:
    • __new__ 分配空间
    • __init__ 对象初始化
  • 在上一小节对 __new__ 方法改造之后,每次都会得到 第一次被创建对象的引用
  • 但是:初始化方法还会被再次调用

需求

  • 初始化动作 只被 执行一次

解决办法

  1. 定义一个类属性 init_flag 标记是否 执行过初始化动作,初始值为 False
  2. __init__ 方法中,判断 init_flag,如果为 False 就执行初始化动作
  3. 然后将 init_flag 设置为 True
  4. 这样,再次 自动 调用 __init__ 方法时,初始化动作就不会被再次执行
class Cat(object):
    instance = None
    init_flag = False

    def __new__(cls, *args, **kwargs):
        if cls.instance is None:
            cls.instance = super().__new__(cls)
            print("创建对象,分配空间")
        # 2. 返回类属性的单例引用
        return cls.instance
    
    def __init__(self):

        if not Cat.init_flag:
            print("初始化")
            Cat.init_flag = True

cat1 = Cat()
print(cat1)
cat2 = Cat()
print(cat2)
创建对象,分配空间
初始化
<__main__.Cat object at 0x7f3cbc0c4be0>
<__main__.Cat object at 0x7f3cbc0c4be0>

metaclass

metaclass,直译为元类,简单的解释就是:

当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。

但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。

连接起来就是:先定义metaclass,就可以创建类,最后创建实例。

所以,metaclass允许你创建类或者修改类。换句话说,你可以把类看成是metaclass创建出来的“实例”。

metaclass是Python面向对象里最难理解,也是最难使用的魔术代码。正常情况下,你不会碰到需要使用metaclass的情况,所以,以下内容看不懂也没关系,因为基本上你不会用到。

我们先看一个简单的例子,这个metaclass可以给我们自定义的MyList增加一个add方法:

定义ListMetaclass,按照默认习惯,metaclass的类名总是以Metaclass结尾,以便清楚地表示这是一个metaclass:

# metaclass是类的模板,所以必须从`type`类型派生:
class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)
    
    

有了ListMetaclass,我们在定义类的时候还要指示使用ListMetaclass来定制类,传入关键字参数metaclass

class MyList(list, metaclass=ListMetaclass):
    pass

当我们传入关键字参数metaclass时,魔术就生效了,它指示Python解释器在创建MyList时,要通过ListMetaclass.__new__()来创建,在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。

__new__()方法接收到的参数依次是:

当前准备创建的类的对象;

类的名字;

类继承的父类集合;

类的方法集合。

测试一下MyList是否可以调用add()方法:

L = MyList()
L.add(1)
L.add(1)
print(L)
[1, 1]

而普通的list没有add()方法:

L2 = list()
L2.add(1)
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-12-e9551a7fbae7> in <module>
      1 L2 = list()
----> 2 L2.add(1)


AttributeError: 'list' object has no attribute 'add'

动态修改有什么意义?直接在MyList定义中写上add()方法不是更简单吗?正常情况下,确实应该直接写,通过metaclass修改纯属变态。

class MyList1(list):
    def add(self, value):
        self.append(value)  
        
        
        
L = MyList1()
L.add(1)
L.add(1)
print(L)


[1, 1]

但是,总会遇到需要通过metaclass修改类定义的。ORM就是一个典型的例子。

ORM全称“Object Relational Mapping”,即对象-关系映射,就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码更简单,不用直接操作SQL语句。

要编写一个ORM框架,所有的类都只能动态定义,因为只有使用者才能根据表的结构定义出对应的类来。

让我们来尝试编写一个ORM框架。

编写底层模块的第一步,就是先把调用接口写出来。比如,使用者如果使用这个ORM框架,想定义一个User类来操作对应的数据库表User,我们期待他写出这样的代码:

class User(Model):
    # 定义类的属性到列的映射:
    id = IntegerField('id')
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')

# 创建一个实例:
u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
# 保存到数据库:
u.save()

其中,父类Model和属性类型StringField、IntegerField是由ORM框架提供的,剩下的魔术方法比如save()全部由metaclass自动完成。虽然metaclass的编写会比较复杂,但ORM的使用者用起来却异常简单。

现在,我们就按上面的接口来实现该ORM。

首先来定义Field类,它负责保存数据库表的字段名和字段类型:

class Field(object):

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

    def __str__(self):
        return '<%s:%s>' % (self.__class__.__name__, self.name)
    
    
    
    

Field的基础上,进一步定义各种类型的Field,比如StringField,IntegerField等等:

class StringField(Field):

    def __init__(self, name):
        super(StringField, self).__init__(name, 'varchar(100)')

class IntegerField(Field):

    def __init__(self, name):
        super(IntegerField, self).__init__(name, 'bigint')
        
        
        

下一步,就是编写最复杂的ModelMetaclass了:

class ModelMetaclass(type):

    def __new__(cls, name, bases, attrs):
        if name=='Model':
            return type.__new__(cls, name, bases, attrs)
        print('Found model: %s' % name)
        mappings = dict()
        for k, v in attrs.items():
            if isinstance(v, Field):
                print('Found mapping: %s ==> %s' % (k, v))
                mappings[k] = v
        for k in mappings.keys():
            attrs.pop(k)
        attrs['__mappings__'] = mappings # 保存属性和列的映射关系
        attrs['__table__'] = name # 假设表名和类名一致
        return type.__new__(cls, name, bases, attrs)

以及基类Model:

class Model(dict, metaclass=ModelMetaclass):

    def __init__(self, **kw):
        super(Model, self).__init__(**kw)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Model' object has no attribute '%s'" % key)

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

    def save(self):
        fields = []
        params = []
        args = []
        for k, v in self.__mappings__.items():
            fields.append(v.name)
            params.append('?')
            args.append(getattr(self, k, None))
        sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))
        print('SQL: %s' % sql)
        print('ARGS: %s' % str(args))

当用户定义一个class User(Model)时,Python解释器首先在当前类User的定义中查找metaclass,如果没有找到,就继续在父类Model中查找metaclass,找到了,就使用Model中定义的metaclassModelMetaclass来创建User类,也就是说,metaclass可以隐式地继承到子类,但子类自己却感觉不到。

ModelMetaclass中,一共做了几件事情:

1. 排除掉对Model类的修改;

2. 在当前类(比如User)中查找定义的类的所有属性,如果找到一个Field属性,就把它保存到一个__mappings__的dict中,同时从类属性中删除该Field属性,否则,容易造成运行时错误(实例的属性会遮盖类的同名属性);

3. 把表名保存到__table__中,这里简化为表名默认为类名。

Model类中,就可以定义各种操作数据库的方法,比如save(),delete(),find(),update等等。

我们实现了save()方法,把一个实例保存到数据库中。因为有表名,属性到字段的映射和属性值的集合,就可以构造出INSERT语句。

编写代码试试:

class User(Model):
    # 定义类的属性到列的映射:
    id = IntegerField('id')
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')

# 创建一个实例:
u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
# 保存到数据库:
u.save()

Found model: User
Found mapping: id ==> <IntegerField:id>
Found mapping: name ==> <StringField:username>
Found mapping: email ==> <StringField:email>
Found mapping: password ==> <StringField:password>
SQL: insert into User (id,username,email,password) values (?,?,?,?)
ARGS: [12345, 'Michael', 'test@orm.org', 'my-pwd']
posted @ 2020-09-18 10:22  傻白甜++  阅读(314)  评论(0编辑  收藏  举报
TOP