绑定方法及面向对象小结

绑定方法

对象的绑定方法

在类中没有被任何装饰器修饰的方法就是 绑定到对象的方法,这类方法专门为对象定制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person:
country = "China"

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

def speak(self):
print(self.name + ', ' + str(self.age))


p = Person('Kitty', 18)


print(p.__dict__) # {'name': 'Kitty', 'age': 18}
print(Person.__dict__['speak']) # <function Person.speak at 0x10f0dd268>

speak即为绑定到对象的方法,这个方法不在对象的名称空间中,而是在类的名称空间中。

通过对象调用绑定到对象的方法,会有一个自动传值的过程,即自动将当前对象传递给方法的第一个参数(self,一般都叫self,也可以写成别的名称);若是使用类调用,则第一个参数需要手动传值。

1
2
3
4
5
6
7
p = Person('Kitty', 18)
# 通过对象调用
p.speak() # Kitty, 18


# 通过类调用
Person.speak(p) # Kitty, 18

类的绑定方法

类中使用 @classmethod 修饰的方法就是绑定到类的方法。这类方法专门为类定制。通过类名调用绑定到类的方法时,会将类本身当做参数传给类方法的第一个参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Operate_database():
host = '192.168.0.5'
port = '3306'
user = 'abc'
password = '123456'

@classmethod
def connect(cls): # 约定俗成第一个参数名为cls,也可以定义为其他参数名
print(cls)
print(cls.host + ':' + cls.port + ' ' + cls.user + '/' + cls.password)


Operate_database.connect()
'''
<class '__main__.Operate_database'>
192.168.0.5:3306 abc/123456
'''

通过对象也可以调用,只是默认传递的第一个参数还是这个对象对应的类。

1
2
3
4
5
Operate_database().connect()  # 输出结果一致
'''
<class '__main__.Operate_database'>
192.168.0.5:3306 abc/123456
'''

非绑定方法

在类内部使用 @staticmethod 修饰的方法即为非绑定方法,这类方法和普通定义的函数没有区别,不与类或对象绑定,谁都可以调用,且没有自动传值的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import hashlib


class Operate_database():
def __init__(self, host, port, user, password):
self.host = host
self.port = port
self.user = user
self.password = password

@staticmethod
def get_passwrod(salt, password):
m = hashlib.md5(salt.encode('utf-8')) # 加盐处理
m.update(password.encode('utf-8'))
return m.hexdigest()


hash_password = Operate_database.get_passwrod('lala', '123456') # 通过类来调用
print(hash_password) # f7a1cc409ed6f51058c2b4a94a7e1956


p = Operate_database('192.168.0.5', '3306', 'abc', '123456')
hash_password = p.get_passwrod(p.user, p.password) # 也可以通过对象调用
print(hash_password) # 0659c7992e268962384eb17fafe88364

简而言之,非绑定方法就是将普通方法放到了类的内部。

练习

假设我们现在有一个需求,需要让Mysql实例化出的对象可以从文件settings.py中读取数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# settings.py
IP = '1.1.1.10'
PORT = 3306
NET = 27

# test.py
import uuid

class Mysql:
def __init__(self, ip, port, net):
self.uid = self.create_uid()
self.ip = ip
self.port = port
self.net = net

def tell_info(self):
"""查看ip地址和端口号"""
print('%s:%s' % (self.ip, self.port))

@classmethod
def from_conf(cls):
return cls(IP, NET, PORT)

@staticmethod
def func(x, y):
print('不与任何人绑定')

@staticmethod
def create_uid():
"""随机生成一个字符串"""
return uuid.uuid1()


# 默认的实例化方式:类名()
obj = Mysql('10.10.0.9', 3307, 27)
obj.tell_info() # 10.10.0.9:3307

绑定方法小结

如果函数体代码需要用外部传入的类,则应该将该函数定义成绑定给类的方法

如果函数体代码需要用外部传入的对象,则应该将该函数定义成绑定给对象的方法

1
2
3
4
5
6
7
# 一种新的实例化方式:从配置文件中读取配置完成实例化
obj1 = Mysql.from_conf()
obj1.tell_info() # 1.1.1.10:27

print(obj.tell_info) # <bound method Mysql.tell_info of <__main__.Mysql object at 0x10f469240>>

print(obj.from_conf) # <bound method Mysql.from_conf of <class '__main__.Mysql'>>

非绑定方法小结

如果函数体代码既不需要外部传入的类也不需要外部传入的对象,则应该将该函数定义成非绑定方法/普通函数

1
2
3
4
5
6
7
8
9
10
obj.func(1, 2)
# 不与任何人绑定
Mysql.func(3, 4)
# 不与任何人绑定

print(obj.func) # <function Mysql.func at 0x10f10e620>

print(Mysql.func) # <function Mysql.func at 0x10f10e620>

print(obj.uid) # a78489ec-92a3-11e9-b4d7-acde48001122

面向对象进阶小结

类的继承

继承父类,则会有父类的所有属性和方法

1
2
3
4
5
6
7
8
class ParentClass1():
pass

class ParentClass2():
pass

class SubClass(ParentClass1,ParentClass2):
pass

类的派生

继承父类的同时自己有init,然后也需要父类的init

1
2
3
4
5
6
7
8
9
10
class ParentClass1():
def __init__(self,name):
pass


class SubClass(ParentClass):
def __init__(self,age):
# 1. ParentClass1.__init__(self,name)
# 2. super(SubClass,self).__init__(name)
self.age = age

类的组合

类对象可以引用/当做参数传入/当做返回值/当做容器元素,类似于函数对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ParentClass1():
count = 0
def __init__(self,name):
pass

class SubClass(ParentClass):
def __init__(self,age):
self.age = age

pc = ParentClass1()
sc = SubClass()

sc.parent_class = pc # 组合
sc.parent_class.count # 0

菱形继承问题

新式类:继承object的类,python3中全是新式类

经典类:没有继承object的类,只有python2中有

在菱形继承的时候,新式类是广度优先(老祖宗最后找);经典类深度优先(一路找到底,再找旁边的)

多态与多态性

一种事物的多种形态,动物–>人/猪/狗

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 多态
import abc

class Animal(metaclass=abc.ABCmeta):
@abc.abstractmethod
def eat():
print('eat')

class People(Animal):
def eat():
pass

class Pig(Animal):
def eat():
pass
def run():
pass

class Dog(Animal): # 报错
def run():
pass

# 多态性
peo = People()
peo.eat()
peo1 = People()
peo1.eat()
pig = Pig()
pig.eat()

def func(obj):
obj.eat()

class Cat(Animal):
def eat():
pass
cat = Cat()

func(cat)

鸭子类型:只要长得像鸭子,叫的像鸭子,游泳像鸭子,就是鸭子

1
2
3
4
5
6
7
8
9
# 继承一个父类,父类中有方法,在子类中重写方法
# 鸭子类型:不需要显示继承一个类,只要多个类中有同样的属性或方法,我们把它们称之为一种类,python,go
# 非鸭子类类型语言:如果要属于同一类,必须显示的继承某个基类,这样才属于基类这个类型,java

# python语言建议使用鸭子类型(约定),但在实际开发中,我们经常不使用鸭子类型这种特性,出错概率低
# 实际编码:要么认为约定必须有哪些方法(符合鸭子类型),可控性低;要么强制约定有哪些方法(abc模块,使用抛异常)

# java中:重写:重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变
# java中:重载:是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同

类的封装

隐藏属性,只有类内部可以访问,类外部不可以访问

1
2
3
4
5
6
7
8
9
class Foo():
__count = 0

def get_count(self):
return self.__count

f = Foo()
f.__count # 报错
f._Foo__count # 不能这样做

类的property特性

把方法变成属性引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class People():
def __init__(self,height,weight):
self.height = height
self.weight = weight

@property
def bmi(self):
return weight/(height**2)

@bmi.setter
def bmi(self,value)
print('setter')

@bmi.deleter
def bmi(self):
print('delter')

peo = People
peo.bmi

类与对象的绑定方法和非绑定方法

没有任何装饰器装饰的方法就是对象的绑定方法, 类能调用, 但是必须得传参给self

被 @classmethod 装饰器装饰的方法是类的绑定方法,参数写成cls, cls是类本身, 对象也能调用, 参数cls还是类本身

被 @staticmethod 装饰器装饰的方法就是非绑定方法, 就是一个普通的函数

isinstance与type

在游戏项目中,我们会在每个接口验证客户端传过来的参数类型,如果验证不通过,返回给客户端“参数错误”错误码。

这样做不但便于调试,而且增加健壮性。因为客户端是可以作弊的,不要轻易相信客户端传过来的参数。

验证类型用type函数,非常好用,比如

1
2
3
print(type('foo') == str)  # True

print(type(2.3) in (int, float)) # True

既然有了type()来判断类型,为什么还有isinstance()呢?

一个明显的区别是在判断子类。

type()不会认为子类是一种父类类型;isinstance()会认为子类是一种父类类型。

千言不如一码。

1
2
3
4
5
6
7
8
9
10
11
12
class Foo(object):
pass

class Bar(Foo):
pass

print(type(Foo()) == Foo) # True

print(type(Bar()) == Foo) # False

# isinstance参数为对象和类
print(isinstance(Bar(),Foo)) # True

需要注意的是,旧式类跟新式类的type()结果是不一样的。旧式类都是<type ‘instance’>。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# python2.+
class A:
pass

class B:
pass

class C(object):
pass

print('old style class',type(A())) # old style class <type 'instance'>

print('old style class',type(B())) # old style class <type 'instance'>

print('new style class',type(C())) # new style class <class '__main__.C'>

print(type(A()) == type(B())) # True

注意:不存在说isinstance比type更好。只有哪个更适合需求。

issubclass

1
2
3
4
5
6
7
8
9
class Parent:
pass

class Sub(Parent):
pass


print(issubclass(Sub, Parent)) # True
print(issubclass(Parent, object)) # True