zl程序教程

您现在的位置是:首页 >  后端

当前栏目

python-面向对象基础

Python基础 面向对象
2023-09-14 08:56:53 时间

原文地址:https://www.cnblogs.com/-beyond/p/9749996.html
转载请先获得允许!!!

面向对象基础

定义类

定义类的格式比较简单,在Python中使用的class关键字,类名一般使用首字母大写的驼峰形式,比如PersonUserAccount...

class Person:
	pass
	
	
class UserAccount:
	pass

当然,除了上面的写法,还有下面这种写法,

class Person(object):
	pass
	

class UserAccount(object):
	pass

这种写法表示的是,Person类和UserAccount类继承自object类,这个object是所有类的基类(父类),虽然第一种方式没有写继承object类,其实默认会继承object类。

类属性与类方法

类属性,是类级别的属性,属于这个类,这个类的所有对象实例共享的

类方法是类级别的方法,使用@classmethod来标注,第一个参数通常命名为cls,表示当前类;

在使用类属性或者类方法时,通过类名.类属性名类名.类方法名的形式来调用。

class RateLimiter:
    """
    限流-计数器限流
    """

    # 计数器-类变量
    counter = 0

    # 计数器-类变量(此处可以理解为常量)
    MAX_COUNTER = 3

    # 获取许可-类方法,使用@classmethod标注,第一个参数为cls,表示当前类
    @classmethod
    def acquire(cls, count):
        if not count:
            count = 1

        if cls.counter + count <= cls.MAX_COUNTER:
            cls.counter = cls.counter + count
            return True
        else:
            return False


if __name__ == '__main__':
    for i in range(1, 5):
        if RateLimiter.acquire(1):
            print("request %d, pass,counter value is %d" % (i, RateLimiter.counter))
        else:
            print("request %d, blocked,counter value is %d" % (i, RateLimiter.counter))

输出

request 1, pass,counter value is 1
request 2, pass,counter value is 2
request 3, pass,counter value is 3
request 4, blocked,counter value is 3

静态方法

静态方法,可以理解为定义在类的内部,但是与类没有直接关系的方法,一般比如工具类中的各种静态方法;

定义静态方法的格式:使用@staticmethod进行标注,然后方法参数不需要像类方法一样有一个cls变量,静态方法直接是参数列表即可,示例如下:

class Computer:
    
    @staticmethod
    def sum(*args):
        if not args:
            return 0
        else:
            tot = 0
            for item in args:
                tot += item
            return tot


tot = Computer.sum(1, 2, 3, 4, 5)
print("sum:%d" % tot)  # sum:15

对象实例

类可以理解为一个模板,根据模板创建出来的,叫做对象,也就是“实例”;

python创建对象的方式与其他编程语言不同,没有用new关键字来创建对象。

python创建对象实例的方式也很简单,直接通过类名加括号即可,示例如下:

class Person:
	pass
	

# 创建Person类的实例
person = Person()

实例属性

创建的每一个对象实例,都可以有自己的实例属性,不同的对象实例,数据是不一样,且不相互影响的。

定义实例属性的方式也很简单,需要用到__init__()方法,这个方法是在创建对象后,完成对象的初始化赋值操作的,在这个方法中可以自定义实例属性。

class Person:

    # __init__方法用于完成对象的初始化操作,在内部定义对象的实例属性
    def __init__(self, nam):
        self.name = nam


p1 = Person("abc")
p2 = Person("xyz")
print(p1.name)  # abc
print(p2.name)  # xyz

实例方法

实例方法,一般来说,操作的都是当前实例的数据;

定义实例方法的格式也比较简单,和定义函数类似,只不过实例方法的第一个参数是self,一般命名为self,表示当前对象;然后后面再加上实例方法的参数;

调用实例方法的格式,是使用对象.实例方法([参数列表])

示例如下:

class Person:

    # __init__方法用于完成对象的初始化操作,在内部定义对象的实例属性
    def __init__(self, name):
        self.name = name

    # 定义一个实例方法,第一个参数self,表示调用该方法的实例对象
    def intro_self(self):
        print("my name is %s" % self.name)

    # 定义一个实例方法
    def modify_name(self, new_name):
        self.name = new_name

    # 实例方法可以有返回值,也可以没有返回值
    def get_name(self):
        return self.name


p1 = Person("abc")

# 调用实例方法
p1.intro_self()  # my name is abc
p1.modify_name("xyz")
p1.intro_self()  # my name is xyz
print(p1.get_name())  # xyz

方法重载

python中是不支持方法或者方法重载的,其他的静态语言中可以使用方法或者函数重载,方法名相同,但是方法的参数或者类型不一样,以此来实现重载;但是Python中,参数的类型是任意的,所以无法实现重载。

如果在Python中定义了多个同名的方法,那么只有最后一个方法生效,调用的时候也只能按照最后一个方法的定义进行调用,而不能调用被覆盖的方法。

访问控制

如何进行访问控制

其他的编程语言在语法上面就能限制类内部的访问控制,比如privateprotectedpublicPython不能使用这些关键字来实现访问控制,而是使用一种比较特殊的方式:

  1. 以两个下划线开头的方法或者字段被认为是私有的(private),这些数据只有当前对象可以存取;
  2. 以一个下划线开头的方法或者字段被认为是受保护的(protected),可以将protected等价于public,因为可以通过对象直接存取protected属性和方法;
  3. 没有以下划线开头的方法或者字段被认为是公开的(public),这些数据可以被所有外部的对象直接获取;

比如下面定义的Person类,内部就有__name__age__id_card_no字段,以及__think()都是私有的方法,不能在外部直接通过对象访问,否则就会抛出异常;

如果要存取私有数据,比如私有属性,可以定义对应的setter/getter,私有方法的话,既然都定义为私有的了,肯定是不能给外部调用的了,一般使用在类的内部。

class Person:
    def __init__(self, name, age, id_card_no):
        self.name = name  # public
        self._age = age  # protected
        self.__id_card_no = id_card_no  # private

    def set_age(self, age):
        if age < 0 or age > 1000:
            raise SystemError("age is out of range")
        self._age = age

    def get_age(self):
        return self._age

    # private方法
    def __show_id_card(self):
        print("%s is thinking self" % self)

    # public方法
    def intro_self(self):
        print("public method, intro_self:%s" % self)

    # protected
    def _thinking(self):
        print("protected method, _thinking:%s" % self)

    # Python内建的方法
    def __str__(self):
        return 'Person={name: %s, age:%d, id_card_no: %s}' % (self.name, self._age, self.__id_card_no)


p1 = Person("abc", 99, "123456789")
print(p1.name)  # ABC,直接访问public属性

print(p1._age)  # 99,访问protected属性没有异常,但是IDE会提示警告
p1._age = 100  # 直接修改protected属性,没有异常,但是IDE会提示警告
print(p1._age)  # 100

print(p1.__id_card_no)  # 访问私有属性,报错AttributeError: Person instance has no attribute '__id_card_no'

如何实现访问控制的

上面的Person类,可以创建一个对象,然后使用dir()函数来打印一下对象的所有属性和方法列表。

可以使用obj.__dict__来获取对象的属性列表。

class Person:
    def __init__(self, name, age, id_card_no):
        self.name = name  # public
        self._age = age  # protected
        self.__id_card_no = id_card_no  # private

    def set_age(self, age):
        if age < 0 or age > 1000:
            raise SystemError("age is out of range")
        self._age = age

    def get_age(self):
        return self._age

    # private方法
    def __show_id_card(self):
        return "%s is thinking self" % self

    # public方法
    def intro_self(self):
        print("public method, intro_self:%s" % self)

    # protected
    def _thinking(self):
        print("protected method, _thinking:%s" % self)

    # Python内建的方法
    def __str__(self):
        return 'Person={name: %s, age:%d, id_card_no: %s}' % (self.name, self._age, self.__id_card_no)


p1 = Person("abc", 99, "123456789")
print(dir(p1))

输出如下

[
    "_Person__id_card_no",
    "_Person__show_id_card",
    "__doc__",
    "__init__",
    "__module__",
    "__str__",
    "_age",
    "_thinking",
    "get_age",
    "intro_self",
    "name",
    "set_age"
]

可以看到,Person类中定义的方法名和属性名,生成的字段名称发生了变化,对应关系如下:

原始字段名 分类 控制权限 生成的字段名
__id_card_no 字段 Private _Person__id_card_no
_age 字段 Protected _age
name 字段 public name
_show_id_card 方法 Private _Person__show_id_card
_thinking 方法 Protected _thinking
intro_self 方法 public intro_self

从上面的对应关系可以看到,私有的属性和方法,生成的引用其实是_Class__fieldName,所以知道了这个属性,就可以通过对象直接调用私有属性了,示例如下:

print(p1._Person__id_card_no)
print(p1._Person__show_id_card())

# 输出如下
# 123456789
# Person={name: abc, age:99, id_card_no: 123456789} is thinking self

@property装饰器

在介绍@property之前,先看一下怎么存取私有的实例属性;如果有其他编程语言的基础,那么肯定就知道有种东西叫getter/setter,举个例子就是下面这样:

class Person:

    def __init__(self, name):
        # 定义一个私有的属性__name
        self.__name = name

    def get_name(self):
        return self.__name

    def set_name(self, new_name):
        if not new_name:
            raise RuntimeError("姓名不能为空")
        self.__name = new_name


p = Person("abc")
# 不能直接访问私有属性,需要通过getter和setter进行访问
print(p.get_name())  # abc
p.set_name("xyz")
print(p.get_name())  # xyz

上面的gettersetter的方式,在调用时,其实都是通过obj.set_xxx()obj.get_xxx()来使用,不能直接通过obj.xxx来调用,Python提供了@property注解,来支持这种调用方式

class Person:

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

    # 使用@property标注后,可以在外部像访问属性一样来调用方法
    @property
    def name(self):
        print("调用name()方法")
        return self.__name


p = Person("abc")
print(p.name)
# 调用name()方法
# abc

上面的代码中,已经对getter进行了简化,可以直接通过obj.xxx来访问私有属性了(虽然访问的属性名和私有属性名不完全一样,只是一个对应关心)。

下面,可以接着对setter进行简化,简化的方式也很简单,因为上面已经使用@property标注了name方法,那就在定义一个name方法,使用@name.setter来标注即可;

其中@name.setter@name就是前面使用@property标注后name方法后,就会创建一个装饰器@name,使用这个@name装饰器的setter即可。

class Person:

    def __init__(self, name):
        # 定义一个私有的属性__name
        self.__name = name

    # 使用@property标注后,可以在外部像访问属性一样来调用方法
    @property
    def name(self):
        print("调用name()方法-getter")
        return self.__name

    # 使用@name.setter进行标注,接收一个参数,格式与正常setter一致
    @name.setter
    def name(self, new_name):
        print("调用name()方法-setter")
        if not new_name:
            raise RuntimeError("姓名不能为空")
        self.__name = new_name


p = Person("abc")
print(p.name)
# 调用name()方法
# abc
print("-----------")

# 直接设置即可,调用的是@name.setter注解的方法
p.name = 'xyz'
# 调用name()方法-setter
print(p.name)
# 调用name()方法-getter
# xyz

下面来增加一个age属性示例,加深理解:

class Person:

    def __init__(self, name, age):
        self.__name = name
        self.__age = age

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

    @name.setter
    def name(self, new_name):
        self.__name = new_name

    @property
    def age(self):
        return self.__age

    @age.setter
    def age(self, new_age):
        self.__age = new_age


p = Person("abc", 66)
print("name:%s, age:%s" % (p.name, p.age))  # name:abc, age:66

p.name = "xyz"
p.age = 99
print("name:%s, age:%s" % (p.name, p.age))  # name:xyz, age:99

类内部的特殊方法

__new__

__new__()方法,在创建对象的时候会调用,我们一般不用关心或者重写这个方法。

创建的对象,属性信息是有__init__()方法进行赋值设置。

__init__

__init__()方法,是对象的初始化方法,需要注意的是初始化,而不是创建对象,创建对象是由__new__()完成的。

创建对象语句:p = Person("xyz", 66, "123456789"),具体的过程就是:

  1. 先调用__new__()创建Person类的对象,这个方法我们一般不需要关心。
  2. 然后调用__init__(),将传入的数据来初始化对象信息,给实例属性赋值。

需要注意的是:

  1. python中不支持构造器重载,也就是说不能定义多个构造器,因为python是不支持方法或者函数重载的,如果显式地重写__init__(),那么会使用默认的构造方法,也就是什么也不做;
  2. 不要尝试在__init__()中返回任何数据,这样会报错(无法通过语法校验),其实__init__()默认会返回新创建的对象引用。

代码示例:

#! /usr/bin/python
# -*- coding:utf-8 -*-

class Person:
    
    def __init__(self):
        # 默认的构造器
        pass

    # 注意,构造器不能进行重写,如果定义了两个构造器,则最后一个生效
    def __init__(self, name, age, id_card_no):
        # 做一些初始化操作
        pass


# 调用无参的构造方法,会抛异常,因为只有最后一个构造器生效
# p1 = Person()

# 下面创建person对象,正常
p1 = Person("abc", 99, "123456789")
print(p1)  # <__main__.Person instance at 0x10fb8ea28>

__str__

像上面输出一个Person的对象,内容没有太大的价值:<__main__.Person instance at 0x10fb8ea28>

格式就是:package.ClassName instance at 内存地址>

如果要想输出对象时,能够更直观的了解对象的信息,比如对象的某些属性信息,就可以通过重写__str__()方法来实现;

print()在打印一个对象时,其实是打印对象__str__()方法的返回值,所以__str__()必须返回一个字符串

示例如下:

#! /usr/bin/python
# -*- coding:utf-8 -*-

class Person:
    
    def __init__(self, name, age, id_card_no):
        self.__name = name
        self.__age = age
        self.__id_card_no = id_card_no

    def __str__(self):
        # __str__()方法必须返回一个字符串
        return 'Person={name: %s, age:%d, id_card_no: %s}' % (self.__name, self.__age, self.__id_card_no)


p1 = Person("abc", 99, "123456789")
print(p1)  # Person = {name: abc, age: 99, id_card_no: 123456789}

__repr__

__repr__()__str__()的功能基本一样,都是为了提高对象内容的可读性,会有一些细微的差别,但是可以直接将这两个方法等级看待即可,使用方式也和__str__()一样,示例如下:

#! /usr/bin/python
# -*- coding:utf-8 -*-

class Person:
    def __init__(self, name, age, id_card_no):
        self.__name = name
        self.__age = age
        self.__id_card_no = id_card_no

    def __str__(self):
        # __str__()方法必须返回一个字符串
        return 'Person={name: %s, age:%d, id_card_no: %s}' % (self.__name, self.__age, self.__id_card_no)

    # 方式一:定义__repr__具体操作
    def __repr__(self):
        return 'Person={name: %s, age:%d, id_card_no: %s}' % (self.__name, self.__age, self.__id_card_no)

    # 方式二:直接将__repr__ 指向 __str__,保持两者相同
    # __repr__ = __str__


p1 = Person("abc", 99, "123456789")
print(p1)  # Person = {name: abc, age: 99, id_card_no: 123456789}
print(p1.___repr__())  # Person={name: abc, age:99, id_card_no: 123456789}

__del__

__del__()可以理解为C++的析构方法、Javafinalize(),也就是在对象被销毁时调用__del__()

python是基于引用计数的算法,以及一些其他的策略来实现内存的垃圾回收,也就是说每个对象都会有一个引用计数,可以是用del obj语句来减少一个obj对象的引用计数,当obj对象的引用计数为0的时候,垃圾回收器会调用obj对象的__del__()方法。

需要注意的是,当对象的引用计数为0时,垃圾收集器并不会立即进行垃圾回收,可能会有一定延迟,等到下一次GC时回收。

class Person:
    def __init__(self, name, age, id_card_no):
        self.__name = name
        self.__age = age
        self.__id_card_no = id_card_no

    def __str__(self):
        return 'Person={name: %s, age:%d, id_card_no: %s}' % (self.__name, self.__age, self.__id_card_no)

    def __del__(self):
        print("run __del__() of object: %s" % self)


p1 = Person("abc", 99, "123456789")
print(p1)  # Person = {name: abc, age: 99, id_card_no: 123456789}
# run __del__() of object: Person={name: abc, age:99, id_card_no: 123456789}

上面是python解释器执行完成后,隐式地调用p1.___del__()

下面是演示使用del命令来删除对象引用的(引用计数减少1)

p1 = Person("abc", 99, "123456789")
print("before del")
del p1
print("after del")

# 控制台输出
# before del
# run __del__() of object: Person={name: abc, age:99, id_card_no: 123456789}
# after del

上面这个可以看到,只有p1引用了新创建的对象,新对象的引用计数为1,当执行del命令后,引用计数变为0,也就会执行__del__()方法了。

下面这种情况,可以对比下面这种情况,有两个变量引用了新创建的对象,当删除两个引用后,计数器变为0,这时候才执行__del__()方法:

p2 = p1 = Person("abc", 99, "123456789")
print("before del p1")
del p1
print("after del p1, before del p2")
del p2
print("after del p2")

# 控制台输出
# before del p1
# after del p1, before del p2
# run __del__() of object: Person={name: abc, age:99, id_card_no: 123456789}
# after del p2

__call__

python中,变量、函数都认为是对象,比如使用def func():定义了一个名为func的函数,func其实是一个引用,引用的是定义的方法对象,通过func()来调用方法。

那么前面通过p1 = Person()创建的对象,能不能也加一个括号呢,比如p1()?

class Person:

    # __init__方法用于完成对象的初始化操作,在内部定义对象的实例属性
    def __init__(self, name):
        self.name = name


p1 = Person("abc")
p1()

上面的运行结果时会报错的:TypeError: 'Person' object is not callable,报错信息说了,Person的对象是不可调用的。

那是不是就不能调用对象呢?其实是可以的,对象加括号,这种操作叫做“可调用对象”,要支持这种操作,必须重写类中的__call__()方法,因为这个时候调用的__call__()方法,如果没有重写的话,就会默认调用父类object的__call__(),也就抛出了上面那个错误。

使用示例如下:

class Person:

    # __init__方法用于完成对象的初始化操作,在内部定义对象的实例属性
    def __init__(self, name):
        self.name = name

    # 执行对象调用时,执行的是__call__()方法
    def __call__(self, *args, **kwargs):
        print("执行__call__()方法,\n obj:%s \n args:%s,\n kwargs:%s" % (self, args, kwargs))

    def __str__(self):
        return "Person={name:%s}" % self.name

    __repr__ = __str__


# 创建对象
p1 = Person("abc")
# 像调用函数一样,在对象后面加括号,并传入徐国淡出
p1([1, 2, 3, 4], {"addr": "北京", "age": 20}, gender="male", job="worker")

输出如下

执行__call__()方法了,
 obj:Person={name:abc} 
 args:([1, 2, 3, 4], {'addr': '北京', 'age': 20}),
 kwargs:{'gender': 'male', 'job': 'worker'}

__eq__

__eq__(),也就是用来判断两个对象是否相等的(使用==比较),这个相等并不是简单的比较id或者比较value,可以自定义比较逻辑,比如只比较某几个属性,返回True或者False来确定两个对象是否相等。

class Person:
    def __init__(self, name, age, id_card_no):
        self.__name = name
        self.__age = age
        self.__id_card_no = id_card_no

    def __str__(self):
        return 'Person={name: %s, age:%d, id_card_no: %s}' % (self.__name, self.__age, self.__id_card_no)

    def get_name(self):
        return self.__name

    # 直接将__repr__指向__str__
    __repr__ = __str__

    # 重写方法,自定义对象的比较逻辑
    def __eq__(self, other):
        # 只要name相同,就认为两个对象相同
        if not other:
            return False
        else: 
            return self.__name == other.get_name()


p1 = Person("abc", 99, "123456789")
p2 = Person("abc", 66, "987654321")
p3 = Person("xyz", 66, "987654321")
print(p1 == p2)  # True
print(p2 == p3)  # False

需要注意的是,如果对象要作为dictkey时,如果自定义的类型重写了__eq__(),那么也必须重写__hash__(),这是因为这两个方法共同确定元素在dict中的位置以及元素的匹配。

__hash__

Pythondict,和JavaHashMap一样,都能提供O(1)的平均存取时间复杂度,其中很重要的一个概念就是hash

JavaHashMap在确定元素应该放到哪个位置时,就会用到对象的hash值,也就是调用obj.hashCode()

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

# 确定位置
index = (capacity-1) & hash(key);
table[index] = newNode;

Python也是类似的,只不过在Python中,方法名不叫hashCode(),而是叫__hash__(),每个对象都会有一个默认的__hash__()实现,可以通过重写__hash__()方法来自定义计算hash的逻辑,一般在自定义的类型作为dict的元素时会用到。

示例如下:

class Person:
    def __init__(self, name, age, id_card_no):
        self.__name = name
        self.__age = age
        self.__id_card_no = id_card_no

    def __str__(self):
        return 'Person={name: %s, age:%d, id_card_no: %s}' % (self.__name, self.__age, self.__id_card_no)

    def get_name(self):
        return self.__name

    __repr__ = __str__

    def __eq__(self, other):
        if not other:
            return False
        else:
            return self.__name == other.get_name()

    def __hash__(self):
        # 自定义hash的计算逻辑,这里为了简单,直接返回1,也就是所有对象的hash相同
        return 1


p1 = Person("abc", 99, "123456789")
p2 = Person("abc", 66, "987654321")
p3 = Person("xyz", 66, "987654321")

d = {p1: "p1-value", p2: "p2-value", p3: "p3-value"}
print(d)
# 输出
# {
#     Person={name: abc, age:99, id_card_no: 123456789}: 'p2-value', 
#     Person={name: xyz, age:66, id_card_no: 987654321}: 'p3-value'
# }

上面输出上面的结果,其实p1p2被认为是相同的对象,所以后面p2value会覆盖p1value

对象比较的方法

数值可以使用>、<、==、>=、<=这些运算法进行比较,其实对象也是可以使用这些运算符比较的,只不过自定义的类型默认没有定义比较的逻辑,那么比较的逻辑是未知的,结果也就是未知的了。

上面的__eq__()已经介绍了,其他的也是相似的用法。

方法名 完整拼写 对应的运算符
__ne__() Not Tquals !=
__eq__() Equals ==
__lt__() Less Than <
__le__() Less Equals <=
__gt__() Greater Than >
__ge__() Greater Equals >=

在进行对象的大小比较、对象排序的时候,就会用到这些方法,示例如下:

class Person:
    def __init__(self, name, age, id_card_no):
        self.__name = name
        self.__age = age
        self.__id_card_no = id_card_no

    def __str__(self):
        return 'Person={name: %s, age:%d, id_card_no: %s}' % (self.__name, self.__age, self.__id_card_no)

    __repr__ = __str__

    def get_age(self):
        return self.__age

    def __lt__(self, other):
        return self.__age < other.get_age()


p1 = Person("abc", 99, "123456789")
p2 = Person("jqk", 66, "987654321")
p3 = Person("xyz", 77, "000000000")

print(p1 <= p2)  # True
print(p2 <= p3)  # True

data = [p1, p2, p3]
data.sort()
print(data)
# [
#     Person={name: jqk, age:66, id_card_no: 987654321},
#     Person={name: xyz, age:77, id_card_no: 000000000},
#     Person={name: abc, age:99, id_card_no: 123456789}
# ]

原文地址:https://www.cnblogs.com/-beyond/p/9749996.html
转载请先获得允许!!!