使用Python实现地理经纬度

144 阅读14分钟

一个新手的自学笔记,通过案例来帮助自己加深理解,通篇采用大白话编写,本篇用到的模块代码存放在码云上。

模块的使用

from PyStyleClass import GeoCoordinates, getHemisphere
print(GeoCoordinates.doc())  # 查看模块介绍
G = GeoCoordinates(160.07, 180.02)
print(G)  # 打印对象
print(repr(G))  # 打印对象
G1 = GeoCoordinates(160.07, 180.02)
print(G == G1)  # 判断两个对象是否相等
octets = bytes(G)  # 将对象转为bytes存储
print(octets)
G2 = GeoCoordinates.formBytes(octets)  # 通过bytes重新生成对象
print(G2)
print(getHemisphere(G))
用来保存经纬度的类
东经160.07度, 北纬180.02度
(160.07, 180.02)
True
b'd\n\xd7\xa3p=\x02d@q=\n\xd7\xa3\x80f@'
东经160.07度, 北纬180.02度
东北半球

目录

对象的表示形式

在Python中,提供了两种关于对象表示形式的方法,分别是`__str__`和`__repr__`,接下来将详细介绍关于这两个表达形式的相同点和不同点。 假设我们有这样一个对象,用来创造一个人类。
class people:
    def __init__(self, age, name):
        self.name = name
        self.age = age

当我们尝试创建找个对象并直接打印找个对象的时候,会发现打印的内容对我们而言其实并没有太大的作用。

p = people(18, "张三")
p
<__main__.people at 0x24efd446320>

我们自己设计的类,如果没有添加对象表现形式的两种方法时,默认的打印就是这样,可以看到,这样的打印其实并不符合我们的直观感受,因为无法确认究竟是怎样的一个类, 也不知道类中都有哪些数据。假设我们在编写日志的时候,要保存这个数据,会发现没有任何意义。

import logging
logging.warning(p)
WARNING:root:<__main__.people object at 0x0000024EFD446320>

为了方便我们查看和日志的存储,我们可以给我们的类添加一个__str__方法,通过这个方法我们可以打印出我们所需要内容

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

    def __str__(self) -> str:
        """返回值必须是字符串"""
        return f"people({self.name}, {self.age})"
p = people(18, "张三")
print(p)
people(张三, 18)
logging.warning(p)
WARNING:root:people(张三, 18)

此时我们可以看到,我们创建的实例,在打印和日志保存方面更符合我们的直观感受,且打印的内容更有意义,相对于<__main__.people object at 0x0000024EFD446320>。但是此时其实还存在一个问题,假如我们并不是通过print打印,而是在调试过程中,在控制台打印呢?

p
<__main__.people at 0x24efd446dd0>

此时,打印在控制台上的内容实际上没有任何的变化,这也就说明__str__是无法实现控制台的打印输出的,那么__repr__可不可以呢?

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

    def __repr__(self) -> str:
        """返回值也必须是字符串对象"""
        return f"P({self.name}, {self.age})"
p = people(18, "张三")
p
P(张三, 18)

可以看到,当我们实现了__repr__方法后,可以在控制台打印出我们想要的内容了,那么缺少了__str__,我们的print是否还能正常输出呢?

print(p)
P(张三, 18)

可以看到,即便没有__str__方法,我们的对象仍然可以正常的输出,这个原因就在于Python对特殊方法的调用是隐式的,当我们打印某个对象的时候,会先找这个对象的__str__方法,如果找不到,会重新查找__repr__方法,但是如果找不到__repr__方法,是不会去找__str__方法的,不然就容易形成循环套用的情况,所有,如果我们只想实现一种打印方式的话,推荐实现__repr__

classmethod与staticmethod

这个是Python在定义类时常用到的两个装饰器,通过这两个装饰,我们可以直接通过类调用`相关的方法`,而不是必须进行实例化后才能调用。**classmethod**我们通常称为`构造方法`。我们这里还是以`people`类为例。
class people:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def AgeAddOne(self):
        """对年龄加一岁"""
        self.age += 1
        return True

people.AgeAddOne()

当我们没有实例化而直接使用内部函数时,会出现上述的异常,虽然大多数时候,我们也基本上不会说直接通过类去调用里面的方法,而是通过构造相关的对象后再去使用。但是在某些情况下我们确实可能只想用到类中的某些方法,如果没有这种方案,在某些情况也确实会让人感到难受。

classmethod

我们一般称为构造方法,一般会通过找个方法通过类直接定义出新的对象,第一个参数默认使用cls,代表类本身。

class people:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    @classmethod
    def fromStr(cls, String:str):
        name, age = String.split(',')
        return cls(name, age)
    def __repr__(self):
        return f"P({self.name}, {self.age})"
s = "李四,20"
P = people.fromStr(s)
P
P(李四, 20)

可以看到,通过classmethod装饰器可以帮助我们实现一些特殊的构造方法,进一步加强了类的茁壮性,了解了这一点,我们接下来将详细说明clsself的区别,cls指代的是类本身,self指代的是实例本身,二者都是类方法中的第一个参数(也可以用其他参数名代替这两个,但是约定俗称用这两个)。

class people:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    @classmethod
    def fromStr(cls, String:str):
        print("cls", cls)
        name, age = String.split(',')
        return cls(name, age)
    def PrintSelf(self):
        print("self", self)
s = "李四,20"
print("people", people)
P = people.fromStr(s)
P.PrintSelf()
people <class '__main__.people'>
cls <class '__main__.people'>
self <__main__.people object at 0x0000024EFD61F580>

<class '__main__.people'>说明cls指代的就是类本身,由于是类本身,所以cls无法调用cls.namecls.age,因为二者在类的实例构造方法__init__中实现的绑定,但是可以调用类方法,<__main__.people object at 0x0000024EFE77AEC0>说明self指代的是people类生成的对象,我们可以通过self调用类属性和实例属性(类方法和实例方法)。

class people:
    doc = "我是人类"  # 类属性
    def __init__(self, name, age):
        self.name = name  # 实例属性
        self.age = age    # 实例属性
    @classmethod
    def fromStr(cls, String:str):  # 类方法
        print("cls", cls.doc)
        try:
            print("cls", cls.name)
        except Exception as e:
            print(e)
        finally:
            name, age = String.split(',')
            return cls(name, age)
    def PrintSelf(self):  # 实例方法
        print("self", self.doc)
        print("self", self.name)
s = "李四,20"
print("people", people)
P = people.fromStr(s)
P.PrintSelf()
people <class '__main__.people'>
cls 我是人类
type object 'people' has no attribute 'name'
self 我是人类
self 李四

staticmethod

staticmethod也可以将实例方法变为类方法(可以直接通过类调用),但是这个方法,默认是不能调用类属性实例属性的

class people:
    doc = "我是人类"  # 类属性
    def __init__(self, name, age):
        self.name = name  # 实例属性
        self.age = age    # 实例属性

    @staticmethod
    def run(Strint):
        print(Strint)
        return True
    @staticmethod
    def AddAge(Obj:people):
        Obj.age += 1
    def __repr__(self):
        return f"P({self.name}, {self.age})"
people.run("hello")
P = people("王五", 13)
print(P)
P.AddAge(P)
print(P)
hello
P(王五, 13)
P(王五, 14)

虽然staticmethod可以通过类直接调用,但是实际功能上还是显得比较鸡肋的,因为我们完全可以通过在外部定义函数来实现相同的效果

class people:
    doc = "我是人类"  # 类属性
    def __init__(self, name, age):
        self.name = name  # 实例属性
        self.age = age    # 实例属性
    def __repr__(self):
        return f"P({self.name}, {self.age})"
def AddAge(Obj:people):
    Obj.age += 1
P = people("王五", 13)
print(P)
AddAge(P)
print(P)
P(王五, 13)
P(王五, 14)

classmethod与staticmethod

主要区别在于classmethodstaticmethod默认多提供一个类本身的参数。

class people:
    doc = "我是人类"  # 类属性
    def __init__(self, name, age):
        self.name = name  # 实例属性
        self.age = age    # 实例属性
    def __repr__(self):
        return f"P({self.name}, {self.age})"
    @classmethod
    def Fire(*args):
        print("classmethod", args)
    @staticmethod
    def Sec(*args):
        print("staticmethod", args)
people.Fire()
people.Fire("hello")
people.Sec()
people.Sec("hello")
classmethod (<class '__main__.people'>,)
classmethod (<class '__main__.people'>, 'hello')
staticmethod ()
staticmethod ('hello',)

对象可哈希

可哈希是指可以对象有一个`固定且唯一的值`,一般来说,我们可以通过`__eq__`和`__hash__`来实现。
class people:
    def __init__(self, name, age):
        self.name = name
        self.age = age
p = people("张三", 18)
hash(p)
158643747141
s = set()
s.add(p)
s
{<__main__.people at 0x24efe779450>}

可以看到,对象默认是可哈希的,哈希值用的是对象的内存id,我们可以保存多个不同的实例对象,但是这样也会出现下面这样的问题

P1 = people("李四", 18)
P2 = people("李四", 18)
print(hash(P1) == hash(P2))
S1 = set()
S1.add(P1)
S1.add(P2)
print(S1)
False
{<__main__.people object at 0x0000024EFEAC8BE0>, <__main__.people object at 0x0000024EFEACBC70>}

明明P1和P2保存的是同一份内容,但是如果通过集合去重,却仍旧会保留这两个内容一样,哈希值不同(每次实例化都会重新分配一个内存地址,所以每次创建的新的对象都是不同的)

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

    def __hash__(self):
        return hash((self.name, self.age))

P = people("张三", 18)
P1 = people("张三", 18)
print(hash(P))
print(hash(P1))
print(hash(P) == hash(P1))
-1588918259230332048
-1588918259230332048
True

通过对象的__hash__的实现,我们发现目前可以保障哈希值是根据对象的内容进行生成,而不是用默认的内存id,但是只有__hash__就够了吗?答案是NO

S = set()
S.add(P)
S.add(P1)
print(S)
{<__main__.people object at 0x0000024EFEB3A5F0>, <__main__.people object at 0x0000024EFEB3B490>}

还是有两个元素,也就是说我们还是没办法通过集合进行去重,这是因为两个对象只是哈希值相等,但是两个对象直接判断是否相等呢?

P == P1
False

看起来好像并不相等呢,那么我们该如何实现当两个对象内容一样值,则判断两个对象为同一个对象呢,答案是__eq__,在对对象进行相等性判断的时候,会调用__eq__方法。

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

    def __hash__(self):
        return hash((self.name, self.age))

    def __eq__(self, other):
        if isinstance(other, people):
            return self.name == other.name and self.age == other.age
        else:
            return False
P = people("张三", 18)
P1 = people("张三", 18)
S = set()
S.add(P)
S.add(P1)
print(S)
print(P == P1)
print(P == [1, 2, 3])
{<__main__.people object at 0x0000024EFECB6E00>}
True
False

目前我们已经可以通过集合进行去重了,同时在__eq__中我们添加了isinstance(other, people)这样的一个判断,这样也是为了预防在判定是将pople对象和其他对象进行判定(千万分之一可能成功)的误差。但是这样就一定安全了吗?答案是No。

P.name = "李四"
S.add(P)
print(S)
{<__main__.people object at 0x0000024EFECB6E00>, <__main__.people object at 0x0000024EFECB6E00>}

之所以出现这样的问题,是因为我们的对象目前仍然是可变的,我们可以通过对象.属性的方式去改变对象,那么,如何让我们的对象不可变呢?请看下一节。

私有属性?

为什么要加一个`?`,因为Python所谓的私有属性,完全就是`防君子不防小人`,
class people:
    def __init__(self, name, age):
        self._name = name
        self._age = age
    def __repr__(self):
        return f"P({self._name}, {self._age})"
p = people("张三", 18)
print(p._name)
print(p)
p._name = "李四"
print(p)
张三
P(张三, 18)
P(李四, 18)

可以看到如果我们只用一个_是没办法私有化变量的,当然,常见的也并不是使用单个_而是使用两个_来实现的变量私有化。

class people:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age
    def __repr__(self):
        return f"P({self.__name}, {self.__age})"
p = people("张三", 18)
print(p.__name)
print(p)
p.__name = "李四"
print(p)
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

Cell In[1], line 8
      6         return f"P({self.__name}, {self.__age})"
      7 p = people("张三", 18)
----> 8 print(p.__name)
      9 print(p)
     10 p.__name = "李四"


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

看起来,加了双_的变量确实是不能直接访问和修改的,但是这是足够安全的吗?

print(p.__dict__)
print(p._people__name)
p._people__name = "李四"
print(p)
{'_people__name': '张三', '_people__age': 18}
张三
P(李四, 18)

可以看到,当我们在类中加入两个_修饰变量的情况下,python会将这个变量自动添加类的表示,变成_类__变量的形式,知道这一点的我们其实是完全可以修改所谓的私有变量的,所以如前面所说,Python的私有变量是防君子不防小人,正规的程序员如果在程序中看到两个_修饰的变量,应该知道这是不希望在外部被直接更改的变量,但是有些时候我们希望能够在外部对变量做一些操作,有不像做小人,该怎么办呢?

使用property来访问私有变量

class people:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age
    @property
    def name(self):
        return self.__name
    @property
    def count(self):
        return self.__age
    def __repr__(self):
        return f"P({self.__name}, {self.__age})"
p = people("张三", 18)
print(p.name)
print(p.count)
张三
18

使用setter修改私有变量

class people:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age
    @property
    def name(self):
        return self.__name
    @property
    def count(self):
        return self.__age
    @name.setter
    def name(self, value):
        self.__name = value
    @count.setter
    def count(self, value):
        self.__age = value
    def __repr__(self):
        return f"P({self.__name}, {self.__age})"
p = people("张三", 18)
print(p.name)
p.name = "李四"
print(p.name)
张三
李四

想要添加setter,需要先设置property(get)方法

使用__slots__节省内存

当我们创建一个对象是,Python会将对象中的变量和相关的值存放到`__dict__`这个默认字典里面,但是众所周知,字典时一种极其消耗内存的数据结构。当我们一个类里面对对象中的属性操作较少,且要存储很多的对象实例,那么我们完全可以通过`__slots__`来降低我们的内存消耗`(但是现在用的很少,因为服务器的内存多比较多,存储这些数据还是绰绰有余的)`,首先,先来看一下正常情况下,我们创建的类实例中变量是怎样存放的。
class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
V = Vector(5, 6)
print(V.__dict__)
try:
    print(V.__slots__)
except Exception as e:
    print(e)
V.__dict__["z"] = 100  # __dict__支持运行时修改
print(V.__dict__)

{'x': 5, 'y': 6}
'Vector' object has no attribute '__slots__'
{'x': 5, 'y': 6, 'z': 100}

我们的类对象中,默然将属性和相关的值存放在__dict__这个字典中,且默认是没有__slots__这个属性的。

class Vector:
    __slots__ = ("x", "y") # 需要设置为类属性,且不支持运行时在修改
    def __init__(self, x, y):
        self.x = x
        self.y = y
V = Vector(5, 6)
try:
    print(V.__dict__)
except Exception as e:
    print(e)
print(V.__slots__)
V.__slots__ = ("x", "y", "z")
print(V.__slots__)
'Vector' object has no attribute '__dict__'
('x', 'y')



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

AttributeError                            Traceback (most recent call last)

Cell In[15], line 12
     10     print(e)
     11 print(V.__slots__)
---> 12 V.__slots__ = ("x", "y", "z")
     13 print(V.__slots__)


AttributeError: 'Vector' object attribute '__slots__' is read-only

可以看到,当我们在对象中设置了__slots__属性后,对象将不存在__dict__这个内容,接下来就该到了验证__slots__节省内存的环节了

使用__slots__节省内存案例

import time
import tracemalloc
def check(Func, *args, num = 1000):
    """检查对象生成n个消耗的时间和内存"""
    tracemalloc.start()
    start_time = time.time()
    l = [Func(*args) for i in range(num)]
    end_time = time.time()
    current, peak = tracemalloc.get_traced_memory()
    time_taken = end_time - start_time
    current_memory = current / (1024 * 1024)  # Convert to MB
    peak_memory = peak / (1024 * 1024)  # Convert to MB

    print(f"耗时:{time_taken} 秒")
    print(f"当前内存使用:{current_memory} MB")
    print(f"峰值内存使用:{peak_memory} MB")
class V1:
    def __init__(self, x, y):
        self.x = x
        self.y = y
check(V1, 100, 100, num=1000000)
耗时:1.8717374801635742 秒
当前内存使用:153.36623096466064 MB
峰值内存使用:153.3664026260376 MB
class V2:
    __slots__ = ("x", "y")
    def __init__(self, x, y):
        self.x = x
        self.y = y
check(V2, 100, 100, num=1000000)
耗时:1.162348747253418 秒
当前内存使用:54.17479705810547 MB
峰值内存使用:54.17496871948242 MB

关于__slots__的子类问题

class A:
    __slots__ = ("X", "Y")
    def __init__(self, X, Y):
        self.X = X
        self.Y = Y
class B(A):
    def __init__(self, X, Y, Z):
        self.X = X
        self.Y = Y
        self.Z = Z
b = B(4, 5, 6)
print(b.__dict__)
print(b.__slots__)
{'Z': 6}
('X', 'Y')

可以看到子类会继承父类的__slots__属性,但是由于子类没有设置__slots__,导致__dict__仍旧存在(与Z变量无关)

class A:
    __slots__ = ("X", "Y")
    def __init__(self, X, Y):
        self.X = X
        self.Y = Y
class B(A):
    def __init__(self, X, Y):
        self.X = X
        self.Y = Y
b = B(4, 5)
print(b.__dict__)
print(b.__slots__)
{}
('X', 'Y')

所以,为了实现节省内存这件事,我们应该在相关子类中也通过__slots__中添加多出来的变量(因为会从父类中继承一部分)

__slots__的对象添加弱引用?

import weakref
class P:
    __slots__ = ("X", "Y")
    def __init__(self, X, Y):
        self.X = X
        self.Y = Y
p = P(5, 6)
result = weakref.ref(p)
print(result)
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

Cell In[30], line 8
      6         self.Y = Y
      7 p = P(5, 6)
----> 8 result = weakref.ref(p)
      9 print(result)


TypeError: cannot create weak reference to 'P' object

可以看到,使用__slots__修饰的对象实例无法添加弱引用,解决方法也很简单,就是在__slots__的元组中添加__weakref__属性就可以

import weakref
class P:
    __slots__ = ("X", "Y", "__weakref__")
    def __init__(self, X, Y):
        self.X = X
        self.Y = Y
p = P(5, 6)
result = weakref.ref(p)
print(result())
<__main__.P object at 0x00000161B61F4CC0>