Python 面向对象

发布时间 2023-03-22 21:16:18作者: f_carey

Python 面向对象

1 编程范式介绍

编程是程序员用特定的语法+数据结构+算法组成的代码来告诉计算机如何执行任务的过程 ,实现一个任务的方式有很多种不同的方式, 对这些不同的编程方式的特点进行归纳总结得出来的编程方式类别,即为编程范式。
两种最重要的编程范式:面向过程编程和面向对象编程。

1.1 面向过程编程(Procedural Programming)

Procedural programming uses a list of instructions to tell the computer what to do step-by-step.基本设计思路就是程序一开始是要着手解决一个大的问题,然后把一个大问题分解成很多个小问题或子过程,这些子过程再执行的过程再继续分解直到小问题足够简单到可以在一个小步骤范围内解决。

# 面向过程的例子:数据库备份, 分三步,连接数据库,备份数据库,测试备份文件可用性。
def db_conn():
    print("connecting db...")
 
 
def db_backup(dbname):
    print("导出数据库...",dbname)
    print("将备份文件打包,移至相应目录...")
 
def db_backup_test():
    print("将备份文件导入测试库,看导入是否成功")
 
def main():
    db_conn()
    db_backup('my_db')
    db_backup_test()
 
if __name__ == '__main__':
    main()

1.2 面向对象编程

面向对象的几个核心特性如下

  • Class 类
    在类中定义了这些对象的通用行为(具备的属性(variables(data))、共同的方法)

  • Object 对象
    基于类创建对象时,每个对象都自动具备这种通用行为,然后可根据需要赋予每个对象独特的个性。

  • Encapsulation 封装
    在类中对数据的赋值、内部调用对外部用户是透明的,这使类变成了一个胶囊或容器,里面包含着类的数据和方法

  • Inheritance 继承
    一个类可以派生出子类,在这个父类里定义的属性、方法自动被子类继承

  • Polymorphism 多态
    多态是面向对象的重要特性,简单点说:“一个接口,多种实现”,指一个基类中派生出了不同的子类,且每个子类在继承了同样的方法名的同时又对父类的方法做了不同的实现,这就是同一种事物表现出的多种形态。

2 面向对象编程(Object-Oriented Programming )

  • 编写编程原则:
    写重复代码是非常不好的低级行为
    你写的代码需要经常变更

2.1 Class 类

属性:类变量,实例变量,私有属性(var)
方法:构造方法,析构函数,私有方法
对象:实例化一个类后得到的对象

class Dog:  # 定义一个名称为'Dog'的class,类的首字母要大写,根据类创建的实例小写
    """A simple attempt to model a dog."""

    # 类中的函数成为方法,方法命名时在前后各加一个下划线,例如_init_(),称为特殊方法;
    # 旨在避免Python默认方法与普通方法发生名称冲突。
    # 描述self属性,每次通过类创建新实例时,都会调用特殊方法。
    def __init__(self, name, age):  # 方法__init__() 定义成了包含三个形参:self 、name 和age 。形参self 必不可少,还必须位于其他形参的前面。
        """Initialize name and age attributes."""
        self.name = name  # self.name = name 获取存储在形参name中的值,并将其存储到变量name中,然后该变量被关联到当前创建的实例。
        self.age = age

    def sit(self):
        """Simulate a dog sitting in response to a command."""
        print(self.name.title() + " is now sitting.")

    def roll_over(self):
        """Simulate a dog rolling over in response to a command."""
        print(self.name.title() + " is now rolling over.")


# 根据类创建一个表示特定小狗的实例
my_dog = Dog('lucky', 6)

# 1. 访问属性编写了my_dog.name来访问my_dog的属性name的值
print("My dog's name is " + my_dog.name.title() + ".")
print("My dog is " + str(my_dog.age) + " years old.")
# 输出:My dog's name is Lucky.
# 输出:My dog is 6 years old.

# 2. 调用方法
my_dog.sit()
my_dog.roll_over()
# 输出:Lucky is now sitting.
# 输出:Lucky is now rolling over.

# 3. 创建多个实例
your_dog = Dog('小黄', 1)
print("Your dog's name is " + your_dog.name.title() + ".")
print("Your dog is " + str(your_dog.age) + " years old.")
your_dog.sit()
your_dog.roll_over()
# 输出:Your dog's name is 小黄.
# 输出:Your dog is 1 years old.
# 输出:小黄 is now sitting.
# 输出:小黄 is now rolling over.

** 为何必须在方法定义中(def init(self, name, age):)包含形参self 呢?**

因为Python调用这个__init__() 方法来创建Dog 实例时,将自动传入实参self 。
每个与类相关联的方法调用都自动传递实参self ,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。
我们创建Dog 实例时,Python将调用Dog 类的方法__init__() 。我们将通过实参向Dog() 传递名字和年龄;self 会自动传递,因此我们不需要传递它。
每当我们根据Dog 类创建实例时,都只需给最后两个形参(name 和age )提供值。

2.2 给属性指定默认值

类中的每个属性都必须有初始值,哪怕这个值是0或空字符串。
也可以在方法__init__() 内指定某个属性设置默认值;如果你这样做了,就无需包含为它提供初始值的形参。

  • 添加一个名为bark_times_reading的属性
    def __init__(self, name, age):
        --snip--
        self.bark_times_reading = 0

    def read_bark_times(self):
        """Print the bark times of the dog."""
        print(self.name + " has barked " + str(self.bark_times_reading) + " times.")

my_dog = Dog('lucky', 6)
my_dog.read_bark_times()
# 输出:
# lucky has barked 0 times.

2.3 修改属性的值

# 1. 直接修改属性的值
my_dog.bark_times_reading = 3
my_dog.read_bark_times()
# 输出:lucky has barked 3 times.

# 2. 通过方法修改属性的值
    def update_bark_times(self, times):
        """Set the bark_times_reading to the given value."""
        if times > self.bark_times_reading:
            self.bark_times_reading = times
        else:
            print("You cannot roll back an bark timer!")


my_dog.update_bark_times(6)
my_dog.read_bark_times()
# 输出:lucky has barked 6 times.

# 3. 通过方法对属性的值进行递增
# 有时候需要将属性值递增特定的量,而不是将其设置为全新的值。假设已知小狗上午叫的6数,下午又叫了5次,统计整天的狗叫次数;
    def increment_bark_time(self, number):
        """Set the bark times reading to the given number"""
        self.bark_times_reading += number

my_dog.increment_bark_time(5)
my_dog.read_bark_times()
# 输出:lucky has barked 11 times.

3 继承

子类继承了其父类的所有属性和方法,同时还可以定义自己的属性和方法。

  • 多继承:3.x 为广度优先; 2.7 中经典类深度优先,新式类广度优先;
  • 组合:self.classfunc = Classfunc(self, obj)

3.1 子类的方法__init__()

创建子类时,父类必须包含在当前文件中,且位于子类前面。定义子类时,必须在括号内指定父类的名称。
super() : 将父类和子类关联起来。这行代码让Python调用ElectricCar 的父类的方法__init__() ,让ElectricCar 实例包含父类的所有属性。父类也称为超超类类 (superclass),名称super因此而得名

class Dog: 
      --snip--

class ChineseDog(Dog):
    """Represent aspects of a dog, specific to Chinese dogs."""

    def __init__(self, name, age):
        """Initialize attributes of the parent class."""
        super().__init__(name, age)

my_dog = ChineseDog('heihu', 5)
print(my_dog.sit())

3.2 给子类定义属性和方法

让一个类继承另一个类后,可添加区分子类和父类所需的新属性和方法。

class Dog:
    --snip--
class ChineseDog(Dog):
    """Represent aspects of a dog, specific to Chinese dogs."""

    def __init__(self, name, age):
        """Initialize attributes of the parent class."""
        super().__init__(name, age)
        self.loyalty_index = 100

    def describe_loyalty(self):
        """Print the dog's loyalty index."""
        print(self.name + "is loyalty index is " + str(self.loyalty_index))

my_dog = ChineseDog('heihu', 5)
my_dog.describe_loyalty()
# 输出:heihuis loyalty index is 100

3.3 重写父类的方法

在子类中定义一个与要重写的父类方法同名的方法

class Dog:
    --snip--
class ChineseDog(Dog):
    --snip--
    # 假设Dog类有一个名为Chinese_Dog_not()的方法,它对中华田园犬来说毫无意义,因此你可能想重写它
    def Chinese_Dog_not(self):
        print("chinese dog dose not have")

3.4 将实例用作属性

使用代码模拟实物时,你可能会发现自己给类添加的细节越来越多:属性和方法清单以及文件都越来越长。在这种情况下,可能需要将类的一部分作为一个独立的类提取出来。你可以将大型类拆分成多个协同工作的小类。

不断给ChineseDog类添加细节时,其中包含许多专门针对狗叫的属性和方法,在这种情况下,可以将这些属性和方法提取出来,放到一个类(Bark)中,并将一个Bark实例用作ChineseDog类的一个属性。

class Dog:  # 定义一个名称为'Dog'的class
    --snip--
class Bark:
    """A simple attempts to model dog bark."""

    def __init__(self, bark_times=3):
        self.bark_times_reading = bark_times

    def update_bark_times(self, times):
        """Set the bark_times_reading to the given value."""
        if times > self.bark_times_reading:
            self.bark_times_reading = times
        else:
            print("You cannot roll back an bark timer!")

    def increment_bark_time(self, number):
        """Set the bark times reading to the given number"""
        self.bark_times_reading += number

    def bark_reading(self):
        """Print the dog bark times"""
        print("This dog has barked " + str(self.bark_times_reading))

class ChineseDog(Dog):
    """Represent aspects of a dog, specific to Chinese dogs."""

    def __init__(self, name, age):
        """Initialize attributes of the parent class."""
        super().__init__(name, age)
        self.loyalty_index = 100
        self.bark = Bark()
    --snip--

my_dog = ChineseDog('heihu', 5)

# 创建一个ChineseDog类,并将其存储在变量my_dog中,要读取bark时,需要使用ChineseDog的属性bark
my_dog.bark.bark_reading()
# 输出:This dog has barked 3

4 经典类与新式类写法

经典类与新式类的区别主要体现在继承上

# 经典类: class People:
# 新式类:class People(object):
class People(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
    --snip--

class Relation(object):
    def make_fri(self, obj):
        print('%s is making friend with %s' % (self.name, obj.name))
        self.fri.append(obj)  # 此处obj所指的是内存地址,不能写obj.name, 原因是如果修改了对象名称,此处名称还是之前的对象名称,便丢失了数据之间的联系。

class Man(People, Relation):
    def __init__(self, name, age, money):
        # People.__init__(self, name, age)  # 经典类写法
        super().__init__(name, age)  # 新式类写法
        self.money = money
        self.fri = []

    def fri_reading(self):
        print(self.fri)  

class Woman(People, Relation):
    def get_birth(self):
        print('%s is born a baby' % self.name)
m1 = Man('carey', 22, 10)
w1 = Woman('elaine', 23)

m1.make_fri(w1)
# 输出:carey is making friend with elaine
m1.fri_reading()
[<__main__.Woman object at 0x000001F15B0650D0>]
w1.name = 'hahaha'
print(m1.fri[0].name)
# 输出:hahaha

# 修改为obj.name时
class Relation(object):
    def make_fri(self, obj):
        print('%s is making friend with %s' % (self.name, obj.name))
        self.fri.append(obj.name)

print(m1.fri[0])
# 输出:elaine

5 多态

一个接口,多个实现

# 目前接触不深,后期补充:
class Animal(object):
    def talk(self):
        self.talk()

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

class Cat(Animal):
    def talk(self):
        print("Meow!")

class Dog(Animal):
    def talk(self):
        print("woof~")

cat = Cat()
dog = Dog()

Animal.talk(cat)
Animal.animal_talk(dog)

6 静态方法

普通方法:可以在实例化后直接调用,并且在方法里可以通过self.调用实例变量或类变量;
静态方法:通过类直接调用,不需要创建对象,不会隐式传递self;通过@staticmethod装饰器即可把其装饰的方法变为一个静态方法

class Dog(object):
    """A simple attempt to represent a dog"""
    def __init__(self, name):
        self.name = name

    @staticmethod  # @staticmethod装饰器将bulk()函数变为静态方法
    def bulk(self):
        print("%s: wang wang" % self.name)

d = Dog('tony')
d.bulk()

# 输出:
TypeError: bulk() missing 1 required positional argument: 'self'

# 原因:当bulk函数变为静态方法后,通过实例调用无法将本身当作一个参数传给self。

## 解决方法:
1. 调用时主动传递实例本身给bulk方法,即d.bulk(d)
2. 在bulk方法中不调用实例其他变量

7 类方法

类方法和普通方法的区别是, 类方法只能访问类变量,不能访问实例变量

class Dog(object):
    """A simple attempt to represent a dog."""
    def __init__(self, name):
        self.name = name

    @classmethod
    def bulk(self):
        print("%s: wang wang" % self.name)

d = Dog('tony')
d.bulk()

# 输出:
AttributeError: type object 'Dog' has no attribute 'name'

# 原因:name是实例变量,类方法是不能访问实例变量的
## 解决方法:
1. 定义一个名为name的类变量
class Dog(object):
    """A simple attempt to represent a dog."""
    name = "class value"
    def __init__(self, name):
        self.name = name
    @classmethod
    def bulk(self):
        print("%s: wang wang" % self.name)

d = Dog('tony')
d.bulk()

8 属性方法

属性方法:把一个方法变成一个静态属性
@property

class Dog(object):
    """A simple attempt to represent a dog."""
    def __init__(self, name):
        self.name = name
        self.__bulk = None

    @property
    def bulk(self):
        print("%s is bulking: %s " % (self.name, self.__bulk))

d = Dog('tony')
d.bulk()

# 输出:
TypeError: 'NoneType' object is not callable
# 原因:bulk函数此时已经变成一个静态属性了, 不是方法了, 想调用已经不需要加()号了,直接d.bulk就可以了


# 修改属性参数,在bulk方法后面,添加以下内容
    @bulk.setter
    def bulk(self, bulking):
        print("%s is bulking: %s " % (self.name, bulking))
        self.__bulking = bulking

# 删除属性
    @bulk.deleter
    def bulk(self):
        del self.__bulking
        print("self.__bulking has been deleted! ")

# 调用方法
d = Dog('tony')
d.bulk
d.bulk = "wangwang"
# 输出:tony is bulking: wangwang
d.bulk  # 触发@bulk.setter
# 输出:tony is bulking: wangwang
del d.bulk  # 触发@bulk.deleter
# 输出:self.__bulking has been deleted!
d.bulk
# 输出:AttributeError: 'Dog' object has no attribute '_Dog__bulking'

9 类的特殊成员方法

  1. __doc__  表示类的描述信息
class Dog(object):
    """A simple attempt to represent a dog."""
    pass
print(Dog.__doc__)

# 输出:
A simple attempt to represent a dog.
  1. moduleclass
    module 表示当前操作的对象在那个模块
      __class__ 表示当前操作的对象的类是什么
aa.py
class A(object):

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

类的特殊方法.py
from aa import A

obj = A()
print(obj.__module__)
print(obj.__class__)
# 输出:
aa  # 输出模块
<class 'aa.A'>  # 输出类

  1. init 构造方法,通过类创建对象时,自动触发执行。
  2. del 析构方法,当对象在内存中被释放时,自动触发执行。
  3. call 对象后面加括号,触发执行。
    注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 call 方法的执行是由对象后加括号触发的,即:对象() 或者 类()
class Foo:
    def __init__(self):
        print('__init__')
    def __call__(self, *args, **kwargs):
        print('__call__')

obj = Foo()  # 执行 __init__
obj()  # 执行 __call__
  1. dict 查看类或对象中的所有成员
class Dog(object):
    """A simple attempt to represent a dog"""
    def __init__(self, name):
        self.name = name

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

d = Dog('tony')

# 获取类的成员,即:静态字段、方法、
print(Dog.__dict__)
# 获取 对象d的成员
print(d.__dict__)
# 输出:
{'__module__': '__main__', '__doc__': 'A simple attempt to represent a dog', '__init__': <function Dog.__init__ at 0x0000024C5C888A60>, 'bulk': <function Dog.bulk at 0x0000024C5C888B80>, '__dict__': <attribute '__dict__' of 'Dog' objects>, '__weakref__': <attribute '__weakref__' of 'Dog' objects>}

{'name': 'tony'}
  1. str 如果一个类中定义了__str__方法,那么在打印 对象 时,默认输出该方法的返回值。
class Foo:
    def __str__(self):
        return 'foo'

obj = Foo()  # 执行 __init__
print(obj)
# 输出:
foo
  1. getitemsetitemdelitem
    用于索引操作,如字典。以上分别表示获取、设置、删除数据
class Foo(object):
    def __init__(self):
        self.data = {}
    def __getitem__(self, key):
        print('__getitem__', key)
        return self.data.get(key)

    def __setitem__(self, key, value):
        print('__setitem__', key, value)
        self.data[key] = value

    def __delitem__(self, key):
        print('__delitem__', key)


obj = Foo()
obj['name'] = 'tony'  # 自动触发执行 __setitem__
# 输出:__setitem__ name tony
print(obj['name'])   # 自动触发执行 __getitem__
# 输出:__getitem__ name
# tony

print(obj.data)
# 输出:{'name': 'tony'}
del obj['name']
# 输出:__delitem__ name
print(obj['name'])
# 输出:__getitem__ name
# tony

del obj['abc']
# 输出:__delitem__ abc

  1. new \ metaclass
class Foo(object):

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

obj = Foo("dog")

print(type(obj))
print(type(Foo))
# 输出:
<class '__main__.Foo'>  # 表示,obj 对象由Foo类创建
<class 'type'>  # 表示,Foo类对象由 type 类创建
# 原因:不仅 obj 是一个对象,Foo类本身也是一个对象,因为在Python中一切事物都是对象。obj对象是Foo类的一个实例,Foo类对象是 type 类的一个实例

有两种方式创建
a). 普通方式 
class Foo(object):
    def func(self):
        pass
b). 特殊方式
def func(self):
    print('this is func')
  
Foo = type('Foo',(object,), {'func': func})
#type第一个参数:类名
#type第二个参数:当前类的基类
#type第三个参数:类的成员

# 加上构造方法
def func(self):
    print("hello %s" % self.name)

def __init__(self, name, age):
    self.name = name
    self.age = age
Foo = type('Foo',(object,),{'func':func,'__init__':__init__})

f = Foo("jack", 22)
f.func()

type类中如何实现的创建类?类又是如何创建对象?

python2中
答:类中有一个属性 __metaclass__,其用来表示该类由 谁 来实例化创建,所以,我们可以为 __metaclass__ 设置一个type类的派生类,从而查看 类 创建的过程。

 类的生成 调用 顺序依次是class Foo --> __new__ --> __init__ --> __call__
* new 是用来创建实例的

 class MyType(type):
      def __init__(self,*args,**kwargs):
  
          print("Mytype __init__",*args,**kwargs)
  
      def __call__(self, *args, **kwargs):
          print("Mytype __call__", *args, **kwargs)
          obj = self.__new__(self)
          print("obj ",obj,*args, **kwargs)
         print(self)
         self.__init__(obj,*args, **kwargs)
         return obj
 
     def __new__(cls, *args, **kwargs):
         print("Mytype __new__",*args,**kwargs)
         return type.__new__(cls, *args, **kwargs)
 
 print('here...')
 class Foo(object,metaclass=MyType):
 
 
     def __init__(self,name):
         self.name = name
         print("Foo __init__")
 
     def __new__(cls, *args, **kwargs):
         print("Foo __new__",cls, *args, **kwargs)
         return object.__new__(cls)   # 继承父类的__new__方法
 
 f = Foo("tony")
 print("f",f)
 print("f.name", f.name)

10 反射

通过字符串映射修改程序运行时的状态、属性、方法, 有以下4个方法

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

    def bulk(self, bulking):
        print('%s is bulking: %s' % (self.name, bulking))

d = Dog('tony')
choice = input('>>:').strip()
 
# #### 判断类中是否有该方法 ####
print(hasattr(d, choice))
# 输出: bulk:True; eat:False
 
# #### 映射出类中该方法的内存地址 ####
print(getattr(d, choice))
# 输出:bulk: 方法存在:<bound method Dog.bulk of <__main__.Dog object at 0x000002476D278610>>
# 输出:方法不存在:eat: AttributeError: 'Dog' object has no attribute 'eat'

# #### 修改存在的属性 ####
setattr(d, choice, None)
print(getattr(d, choice))
# 输出:age:None
setattr(d, choice, 22)
print(getattr(d, choice))
# 输出:age:22

修改存在的属性:
attr = getattr(d, choice)
setattr(d, choice, 'tooom')
print(d.name)
# 输出:name:tooom

# #### 删除属性 ####
delattr(d, choice)
print(d.name)
# 输出:name:AttributeError: 'Dog' object has no attribute 'name'

11 知识点总结

  1. 类加括号代表是创建对象;
  2. 函数加括号代表执行当前函数
  3. 打印f对象代表执行当前对象的__str__方法
  4. f对象加括号代表执行当前对象的__call__方法
  5. f.name对象代表执行当前对象的__getattr__方法
  6. f['key'] = 'value'对象代表执行当前对象的__setitem__方法
  7. f['key']对象代表执行当前对象__getitem__的方法
# object所提供的方法
class Foo(object):
    def __str__(self):
        return 'it is __str__.'

    def __add__(self, other):
        return 'it is __add__.'

    def __call__(self, *args, **kwargs):
        return 'it is __call__.'

    @property
    def top(self):
        return 'top'

    def __getattr__(self, item):
        return f'it is __getattr__, the item is {item}.'

    def __getattribute__(self, item):
        return f'it is __getattribute__, the item is {item}.'

    def __setitem__(self, key, value):
        return 'it is __setitem__.'

    def __getitem__(self, item):
        return f'it is __getitem__, the item is {item}.'


# 实例化Foo对象:类加括号代表是创建对象,函数加括号代表执行当前函数
f = Foo()

# 打印f对象代表执行当前对象的__str__方法
# 没有__str__时输出:<__main__.Foo object at 0x000002B10EC47FD0>
print(f)

ff = f + f
print('ff: ', ff)

# f对象加括号代表执行当前对象的__call__方法
f()
print('f()', f())

# 对象f.name代表执行当前对象的__getattr__方法
# f.name = "fcarey"
print('f.name: ', f.name)

# property属性装饰器
print('f.top: ', f.top)

# 对象f['key'] = 'value'代表执行当前对象的__setitem__方法
f['key'] = 'value'

# 对象f['key']代表执行当前对象__getitem__的方法
print("f['key']: ", f['key'])