python-面向对象基础
原文地址:https://www.cnblogs.com/-beyond/p/9749996.html
转载请先获得允许!!!
面向对象基础
定义类
定义类的格式比较简单,在Python
中使用的class
关键字,类名一般使用首字母大写的驼峰形式,比如Person
、UserAccount
...
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
中定义了多个同名的方法,那么只有最后一个方法生效,调用的时候也只能按照最后一个方法的定义进行调用,而不能调用被覆盖的方法。
访问控制
如何进行访问控制
其他的编程语言在语法上面就能限制类内部的访问控制,比如private
、protected
、public
,Python
不能使用这些关键字来实现访问控制,而是使用一种比较特殊的方式:
- 以两个下划线开头的方法或者字段被认为是私有的(private),这些数据只有当前对象可以存取;
- 以一个下划线开头的方法或者字段被认为是受保护的(protected),可以将protected等价于public,因为可以通过对象直接存取protected属性和方法;
- 没有以下划线开头的方法或者字段被认为是公开的(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
上面的getter
和setter
的方式,在调用时,其实都是通过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")
,具体的过程就是:
- 先调用
__new__()
创建Person类的对象,这个方法我们一般不需要关心。 - 然后调用
__init__()
,将传入的数据来初始化对象信息,给实例属性赋值。
需要注意的是:
python
中不支持构造器重载,也就是说不能定义多个构造器,因为python
是不支持方法或者函数重载的,如果显式地重写__init__()
,那么会使用默认的构造方法,也就是什么也不做;- 不要尝试在
__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++
的析构方法、Java
的finalize()
,也就是在对象被销毁时调用__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
需要注意的是,如果对象要作为dict
的key
时,如果自定义的类型重写了__eq__()
,那么也必须重写__hash__()
,这是因为这两个方法共同确定元素在dict
中的位置以及元素的匹配。
__hash__
Python
的dict
,和Java
的HashMap
一样,都能提供O(1)
的平均存取时间复杂度,其中很重要的一个概念就是hash
。
Java
的HashMap
在确定元素应该放到哪个位置时,就会用到对象的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'
# }
上面输出上面的结果,其实p1
和p2
被认为是相同的对象,所以后面p2
的value
会覆盖p1
的value
。
对象比较的方法
数值可以使用>、<、==、>=、<=
这些运算法进行比较,其实对象也是可以使用这些运算符比较的,只不过自定义的类型默认没有定义比较的逻辑,那么比较的逻辑是未知的,结果也就是未知的了。
上面的__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
转载请先获得允许!!!
相关文章
- Python-基础06-文件操作
- python中bool函数_bool()函数以及Python中的示例
- python挖矿脚本代码_一个挖矿脚本
- 【说站】python图像二值化处理
- 【说站】python动量交易策略的四个步骤
- Python 反转字符串_python输出字符串
- python attrs_Python attrs作用是什么?
- python部分基础
- Python模块下载工具pip和easy_install
- Python基础-1 从一行代码开始运行Python程序
- Python 基础篇 (五)
- Python:基础&爬虫
- Python基础(十九):函数加强
- Python基础(二十三):面向对象之继承介绍
- Python基础(二十六):模块和包简单介绍
- Python基础语法-内置函数和模块-datetime模块
- python-数据库编程-SQL的基础语法和命令
- Python len()函数详解:获取字符串长度或字节数
- python提取页面内的url列表详解编程语言
- python上传ftp文件详解编程语言
- Python面向对象基础详解编程语言
- python的定时任务模块–schedule详解编程语言
- Python如何使用MySQL构建立连接(python怎么连接mysql)
- python使用正则搜索字符串或文件中的浮点数代码实例
- Python-基础-入门简介