• 关于

    python基类

    的搜索结果

回答

一个类继承自另一个类,也可以说是一个孩子类/派生类/子类,继承自父类/基类/超类,同时获取所有的类成员(属性和方法)。 继承使我们可以重用代码,并且还可以更方便地创建和维护代码。Python 支持以下类型的继承: 单继承- 一个子类类继承自单个基类 多重继承- 一个子类继承自多个基类 多级继承- 一个子类继承自一个基类,而基类继承自另一个基类 分层继承- 多个子类继承自同一个基类 混合继承- 两种或两种以上继承类型的组合

天枢2020 2020-03-11 09:24:35 0 浏览量 回答数 0

回答

用python3 type是object的实例 object是所有python对象的基类

珍宝珠 2019-12-02 03:11:58 0 浏览量 回答数 0

回答

元类是类的类。类定义了类的实例(即对象)的行为,而元类定义了类的行为方式。类是元类的实例。 虽然在Python中你可以为元类使用任意的callables(比如Jerub节目),但更好的方法是让它成为一个真正的类本身。type是Python中常用的元类。type它本身就是一个类,它是它自己的类型。你将无法type在Python中重新创建纯粹的东西,但是Python会有所作为。要在Python中创建自己的元类,你真的只想要子类type。 元类最常用作类工厂。当您通过调用类创建对象时,Python通过调用元类创建一个新类(当它执行'class'语句时)。结合法线__init__和__new__方法,元类因此允许您在创建类时执行“额外的事情”,例如使用某些注册表注册新类或者完全替换其他类。 当class被执行的语句,Python的首先执行的主体class声明为代码的正常块。生成的命名空间(dict)保存了将要进行的类的属性。元类是通过查看待定类的基类(继承的元类),在待定类的__metaclass__属性(如果有)或__metaclass__全局变量来确定的。然后使用类的名称,基数和属性调用元类来实例化它。 但是,元类实际上定义了类的类型,而不仅仅是它的工厂,所以你可以用它们做更多的事情。例如,您可以在元类上定义常规方法。这些元类方法就像类方法一样,它们可以在没有实例的类上调用,但它们也不像类方法,因为它们不能在类的实例上调用。type.subclasses()是type元类的方法示例。您还可以定义常规的“魔术”方法,例如__add__,__iter__以及__getattr__实现或更改类的行为方式。

游客gsy3rkgcdl27k 2019-12-02 02:12:08 0 浏览量 回答数 0

阿里云试用中心,为您提供0门槛上云实践机会!

0元试用32+款产品,最高免费12个月!拨打95187-1,咨询专业上云建议!

问题

基础语言百问-Python

薯条酱 2019-12-01 20:12:27 56807 浏览量 回答数 30

回答

类 StructTupleMeta 获取到类属性 _fields 中的属性名字列表, 然后将它们转换成相应的可访问特定元组槽的方法。函数 operator.itemgetter() 创建一个访问器函数, 然后 property() 函数将其转换成一个属性。 本节最难懂的部分是知道不同的初始化步骤是什么时候发生的。 StructTupleMeta 中的 init() 方法只在每个类被定义时被调用一次。 cls 参数就是那个被定义的类。实际上,上述代码使用了 _fields 类变量来保存新的被定义的类, 然后给它再添加一点新的东西。 StructTuple 类作为一个普通的基类,供其他使用者来继承。 这个类中的 new() 方法用来构造新的实例。 这里使用 new() 并不是很常见,主要是因为我们要修改元组的调用签名, 使得我们可以像普通的实例调用那样创建实例。就像下面这样: s = Stock('ACME', 50, 91.1) # OK s = Stock(('ACME', 50, 91.1)) # Error 跟 init() 不同的是,new() 方法在实例被创建之前被触发。 由于元组是不可修改的,所以一旦它们被创建了就不可能对它做任何改变。而 init() 会在实例创建的最后被触发, 这样的话我们就可以做我们想做的了。这也是为什么 new() 方法已经被定义了。 尽管本节很短,还是需要你能仔细研读,深入思考Python类是如何被定义的,实例是如何被创建的, 还有就是元类和类的各个不同的方法究竟在什么时候被调用。 PEP 422 提供了一个解决本节问题的另外一种方法。 但是,截止到我写这本书的时候,它还没被采纳和接受。 尽管如此,如果你使用的是Python 3.3或更高的版本,那么还是值得去看一下的。

景凌凯 2020-04-17 17:37:11 0 浏览量 回答数 0

问题

Python简单使用插件结构,python报错

python小菜菜 2020-05-27 14:48:21 3 浏览量 回答数 1

回答

隐式的基类——object Python是面向对象程序设计语言,有一个类似root的基础类object类。任何自定义的类,都会隐式继承object。 class X: pass print(X.__class__) print(X.__class__.__base__) 基类中的初始化方法 延迟赋值这是指先创建类模板,然后在实例中定义属性并赋值。在Python中,延迟赋值的合法的,但是会存在潜在问题,因此要尽量避免这样的用法。 在基类中实现__init__()方法 每当创建一个对象,Python会先创建一个空对象,然后调用该对象的__init__()函数。 一个常见的多态设计 class Card: def __init__(self,rank,suit): self.suit = suit self.rank = rank self.hard, self.soft = self._points() class NumberCard(Card): def _points(self): return int(self.rank), int(self.rank) class AceCard(Card): def _points(self): return 1, 11 class FaceCard(Card): def _points(self): return 10, 10 使用__init__()方法创建常量清单 可以把创建好的花色对象做缓存,构建一个常量池,使得在调用时对象可被重用,那么性能将得到显著的提升。 class Suit: def __init__(self, name, symbol): self.name = name self.symbol = symbol Club, Diamond, Heart, Spade = Suit('Club','♠'), Suit('Diamond','♦'), Suit('Heart','♥'), Suit('Spade','♣') Cards = [AceCard('A', Spade), NumberCard('2',Spade), NumberCard('3',Spade)]使用工厂函数调用__init__() 可以使用工厂函数来完成所有的Card对象的创建。在Python中实现工厂有两种途径 定义一个函数,返回不同类的对象定义一个类,包括了创建对象的方法一个用来生成Card子类对象的工厂函数例子 def card(rank, suit): if rank == 1: return AceCard('A',suit) elif 2 <= rank < 11: return NumberCard(str(rank),suit) elif 11 <= rank < 14: name = {11: 'J', 12: 'Q', 13: 'K'}[rank] return FaceCard(name, suit) else: raise Exception('Rank out of range.') deck = [card(rank,suit) for rank in range(1,14) for suit in (Club, Diamond, Heart, Spade)] 这里需要注意的是if语句的结构,else语句没有做一些其他步骤,而只是单纯地抛出了一个异常。像这样的catch-all else语句的使用方式是有争议的。 使用elif简化设计来获得一致性 工厂方法card()中包括了两个很常见的结构 if-elif序列映射这是一个没有使用映射Card工厂类的例子 def card3(rank, suit): if rank == 1: return AceCard('A',suit) elif 2 <= rank < 11: return NumberCard(str(rank),suit) elif rank == 11: return FaceCard('J', suit) elif rank == 12: return FaceCard('Q', suit) elif rank == 13: return FaceCard('K', suit) else: raise Exception('Rank out of range.') 相比上一个版本,这个函数在实现上获得了更好的一致性。 使用映射和类来简化设计 下面这个例子用映射来实现,把rank映射为对象,然后又把rank值和suit值作为参数传入Card构造函数来创建Card实例。 def card4(rank, suit): class_ = {1: AceCard, 11: FaceCard, 12: FaceCard, 13: FaceCard}.get(rank, NumberCard) return class_(rank, suit) 实现两部分映射 并行映射不推荐使用 映射到一个牌面值的元组 def card5(rank, suit): class_, rank_str = {1: (AceCard,'A'), 11: (FaceCard,'J'), 12: (FaceCard,'Q'), 13: (FaceCard,'K')}.get(rank,(NumberCard,str(rank))) return class_(rank_str, suit) partial函数设计partial()函数在面向对象编程中不是很常用。 工厂模式的流畅API设计class CardFactory: def rank(self, rank): self.class_, self.rank_str = {1: (AceCard,'A'), 11: (FaceCard,'J'), 12: (FaceCard,'Q'), 13: (FaceCard,'K')}.get(rank,(NumberCard,str(rank))) return self def suit(self, suit): return self.class_(self.rank_str, suit) card8 = CardFactory()deck8 = [card8.rank(r+1).suit(s) for r in range(13) for s in (Club, Diamond, Heart, Spade)] 这种方法并没有利用__init__()在Card类层次结构中的作用,改变的是调用者创建对象的方式。 在每个子类中实现__init__()方法 class Card: def __init__(self, rank, suit, hard, soft): self.suit = suit self.rank = rank self.hard = hard self.soft = soft class NumberCard(Card): def __init__(self, rank, suit): super().__init__(str(rank), suit, rank, rank) class AceCard(Card): def __init__(self, rank, suit): super().__init__('A', suit, 1, 11) class FaceCard(Card): def __init__(self, rank, suit): super().__init__({11: 'J', 12: 'Q', 13: 'K'}[rank], suit, 10, 10) 使用__init__()方法和工厂函数之间存在一些权衡。通常直接调用比“程序员友好”的__init__()函数并把复杂性分发给工厂函数更好。 简单的组合对象 一个组合对象也可以称作容器。 设计集合类,通常有如下3种策略: 封装扩展创建封装集合类 定义Deck类,内部实际调用的是list对象。Deck类的pop()方法只是对list对象响应函数的调用。 class Deck: def __init__(self): self._cards = [card8.rank(r+1).suit(s) for r in range(13) for s in (Club, Diamond, Heart, Spade)] random.shuffle(self._cards) def pop(self): return self._cards.pop() 扩展集合类 pop()函数只需继承自list集合就可以很好地工作,其他函数也一样。 class Deck2(list): def __init__(self): super().__init__(card8.rank(r+1).suit(s) for r in range(13) for s in (Club, Diamond, Heart, Spade)) random.shuffle(self) 完成组合对象的初始化 __init__()初始化方法应当返回一个完整的对象,这是理想的情况。 不带__init__()方法的无状态对象 对于策略模式的对象来说这是常见的设计。一个策略对象以插件的形式符合在主对象上来完成一种算法或逻辑,例如GameStrategy类。 作者:plutoese链接:https://www.jianshu.com/p/a5cb4070e733來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

xuning715 2019-12-02 01:10:28 0 浏览量 回答数 0

回答

在定义类的时候,Python允许我们使用 ``metaclass``关键字参数来指定特定的元类。 例如使用抽象基类: from abc import ABCMeta, abstractmethod class IStream(metaclass=ABCMeta): @abstractmethod def read(self, maxsize=None): pass @abstractmethod def write(self, data): pass 然而,在自定义元类中我们还可以提供其他的关键字参数,如下所示: class Spam(metaclass=MyMeta, debug=True, synchronize=True): pass 为了使元类支持这些关键字参数,你必须确保在 __prepare__() , __new__() 和 __init__() 方法中 都使用强制关键字参数。就像下面这样: class MyMeta(type): # Optional @classmethod def __prepare__(cls, name, bases, *, debug=False, synchronize=False): # Custom processing pass return super().__prepare__(name, bases) # Required def __new__(cls, name, bases, ns, *, debug=False, synchronize=False): # Custom processing pass return super().__new__(cls, name, bases, ns) # Required def __init__(self, name, bases, ns, *, debug=False, synchronize=False): # Custom processing pass super().__init__(name, bases, ns)

哦哦喔 2020-04-17 16:19:04 0 浏览量 回答数 0

问题

Python简单使用插件结构 - python报错

montos 2020-06-04 14:01:19 3 浏览量 回答数 1

问题

如何在Python中键入提示通用数字类型?

is大龙 2020-03-24 12:29:50 0 浏览量 回答数 1

问题

【精品问答】Python二级考试题库

珍宝珠 2019-12-01 22:03:38 1146 浏览量 回答数 2

问题

SDK 的错误如何处理?

轩墨 2019-12-01 22:02:39 1247 浏览量 回答数 0

问题

Python简单使用插件结构:报错 

kun坤 2020-06-04 21:16:30 3 浏览量 回答数 1

回答

创建一个TCP服务器的一个简单方法是使用 socketserver 库。例如,下面是一个简单的应答服务器: from socketserver import BaseRequestHandler, TCPServer class EchoHandler(BaseRequestHandler): def handle(self): print('Got connection from', self.client_address) while True: msg = self.request.recv(8192) if not msg: break self.request.send(msg) if __name__ == '__main__': serv = TCPServer(('', 20000), EchoHandler) serv.serve_forever() 在这段代码中,你定义了一个特殊的处理类,实现了一个 handle() 方法,用来为客户端连接服务。 request 属性是客户端socket,client_address 有客户端地址。 为了测试这个服务器,运行它并打开另外一个Python进程连接这个服务器: >>> from socket import socket, AF_INET, SOCK_STREAM >>> s = socket(AF_INET, SOCK_STREAM) >>> s.connect(('localhost', 20000)) >>> s.send(b'Hello') 5 >>> s.recv(8192) b'Hello' >>> 很多时候,可以很容易的定义一个不同的处理器。下面是一个使用 StreamRequestHandler 基类将一个类文件接口放置在底层socket上的例子: from socketserver import StreamRequestHandler, TCPServer class EchoHandler(StreamRequestHandler): def handle(self): print('Got connection from', self.client_address) # self.rfile is a file-like object for reading for line in self.rfile: # self.wfile is a file-like object for writing self.wfile.write(line) if __name__ == '__main__': serv = TCPServer(('', 20000), EchoHandler) serv.serve_forever()

哦哦喔 2020-04-17 17:14:20 0 浏览量 回答数 0

回答

在程序中引入自定义异常可以使得你的代码更具可读性,能清晰显示谁应该阅读这个代码。 还有一种设计是将自定义异常通过继承组合起来。在复杂应用程序中, 使用基类来分组各种异常类也是很有用的。它可以让用户捕获一个范围很窄的特定异常,比如下面这样的: try: s.send(msg) except ProtocolError: ... 你还能捕获更大范围的异常,就像下面这样: try: s.send(msg) except NetworkError: ... 如果你想定义的新异常重写了 __init__() 方法, 确保你使用所有参数调用 Exception.__init__() ,例如: class CustomError(Exception): def __init__(self, message, status): super().__init__(message, status) self.message = message self.status = status 看上去有点奇怪,不过Exception的默认行为是接受所有传递的参数并将它们以元组形式存储在 .args 属性中. 很多其他函数库和部分Python库默认所有异常都必须有 .args 属性, 因此如果你忽略了这一步,你会发现有些时候你定义的新异常不会按照期望运行。 为了演示 .args 的使用,考虑下下面这个使用内置的 RuntimeError` 异常的交互会话, 注意看raise语句中使用的参数个数是怎样的: >>> try: ... raise RuntimeError('It failed') ... except RuntimeError as e: ... print(e.args) ... ('It failed',) >>> try: ... raise RuntimeError('It failed', 42, 'spam') ... except RuntimeError as e: ... print(e.args) ... ('It failed', 42, 'spam') >>>

景凌凯 2020-04-17 18:00:58 0 浏览量 回答数 0

回答

我觉得要理解django的class-based-view(以下简称cbv),首先要明白django引入cbv的目的是什么。在django1.3之前,generic view也就是所谓的通用视图,使用的是function-based-view(fbv),亦即基于函数的视图。有人认为fbv比cbv更pythonic,窃以为不然。python的一大重要的特性就是面向对象。而cbv更能体现python的面向对象。cbv是通过class的方式来实现视图方法的。class相对于function,更能利用多态的特定,因此更容易从宏观层面上将项目内的比较通用的功能抽象出来。关于多态,不多解释,有兴趣的同学自己Google。总之可以理解为一个东西具有多种形态(的特性)。cbv的实现原理通过看django的源码就很容易明白,大体就是由url路由到这个cbv之后,通过cbv内部的dispatch方法进行分发,将get请求分发给cbv.get方法处理,将post请求分发给cbv.post方法处理,其他方法类似。怎么利用多态呢?cbv里引入了mixin的概念。Mixin就是写好了的一些基础类,然后通过不同的Mixin组合成为最终想要的类。 所以,理解cbv的基础是,理解Mixin。 我们以1.5为例简单讲解一下Mixin。 在python-path/Lib/site-packages/django/view/generic文件夹下,包含了django自带的几个基于类的通用视图。 base.py: ContextMixin: 提供get_context_data方法,给cbv提供context_data View: cbv的基类,提供视图分发等功能 TemplateResponseMixin: 提供渲染模板等功能 TemplateView(TemplateResponseMixin, ContextMixin, View): 从类的构造上就可以看出,这个类是由TemplateResponseMixin,ContextMixin,View三个类共同继承而来的,所以同时具有这三个类的特定,因此,这个类完整的提供了一个cbv应该具有的所有动作(除了处理数据)。 RedirectView(View): 这是View的一个子类,实现的是重定向的功能。 base中已经提供了构成cbv最最基础的几个Mixin,以及cbv的基类View。 以下django又提供了detail,list,edit,dates四个模块,这四个模块分别用来处理detail数据(比如显示日志的某一篇的明细信息),list(比如显示某user的所有日志列表),edit(比如为用户提供新增日志和修改日志的功能),dates(比如显示2014年10月的日志)。想一下,从数据维度上讲,默认的django cbv提供了按照数据维度处理的两个不同的cbv,分别是detail和list。detail显示一个数据对象,list显示数据列表。 下面先分析detail.py: SimpleObjectMixin(ContextMixin): 这是ContextMixin的一个子类,提供最基础的取回单个对象的功能。 BaseDetailView(SimpleObjectMixin, View): 提供显示单个对象的功能。 SimpleObjectTemplateResponseMixin(TemplateResponseMixin): 这是对TemplateResponseMixin的再次封装,为了实现单个对象的模板显示。 DetailView(SimpleObjectTemplateResponseMixin, BaseDetailView): 这就是完整的detail view了。 从以上类的继承上就可以大致猜出,detail模块中的相关cbv其实是对base中提供的mixin的再度继承。从而实现更精细复杂的功能。 所以剩下几个模块题主完全可以自己分析了。 所以分析完了各个模块提供的功能就完了吗?如果到这里止步,那么还是不了解cbv的好处。上文说过,cbv的一大好处就是多态。因此可以把通用的功能抽象出来做成mixin给其他cbv用。 比如,想实现restful API。最简单的,想实现返回json数据。写一个mixin就好了。 class JSONResponseMixin(object): """JSON mixin""" def render_to_response(self, context): return self.get_json_response(self.convert_context_to_json(context)) def get_json_response(self, content, **httpresponse_kwargs): return HttpResponse(content, content_type='application/json', **httpresponse_kwargs) def convert_context_to_json(self, context): return json.dumps(context) 怎么用呢? class CheckRemindUtilView(JSONResponseMixin, ListView): """ Check if there is reminder need to be reminded. This view should be called every minute. """ def get_queryset(self): start = timezone.now() end = start + datetime.timedelta(minutes=1) return Reminder.objects.filter(next_t__gte=start, next_t__lte=end, is_valid=True) def get(self, request, *args, **kwargs): self.object_list = self.get_queryset() if (self.get_paginate_by(self.object_list) is not None and hasattr(self.object_list, 'exists')): is_empty = not self.object_list.exists() else: is_empty = len(self.object_list) == 0 if is_empty: ret = {'code': 42, 'msg': 'empty'} else: for object_ in self.object_list: code = exec_remind(object_) object_.previous_t = object_.next_t update_reminder(object_) ret = {'code': code, 'msg': 'reminded.'} return self.render_to_response(ret) 再从另一个方向举个栗子。比如需要对日志进行用户过滤,用户私有的日志只能用户自己看到,其他人看不到。那么只需要写一个PrivateObjectMixin,然后其他DetailView,ListView继承这个就好了。 class PrivateObjectMixin(object): ''' Filter private object for request.user ''' def filte_private(self, queryset): ''' Filte private object for authenticated user. ''' ordering = getattr(self, 'ordering', '-date_created') if not hasattr(self, 'request'): return queryset if not hasattr(self.request, 'user'): return queryset if self.request.user.is_authenticated(): queryset = queryset.filter(Q(is_valid=True), Q(is_private=True) & Q(user__id=self.request.user.id) | Q(is_private=False)) else: queryset = queryset.filter(is_valid=True, is_private=False) try: result = queryset.order_by(ordering) except FieldError: # The model doesnot have an `ordering` field. return queryset return result class NoteListView(PrivateObjectMixin, BaseNoteListView): ''' Show note list. ''' def get_queryset(self): ''' Get notes. ''' queryset = Note.objects.all() return self.filte_private(queryset) 上面这两个例子只是简单的应用而已,完全可以借助多态实现更复杂的cbv。 以下是建议部分: 1,建议翻阅django cbv的源码,自己画个图了解cbv的实现原理,继承流程。 2,自己写几个简单的cbv。 “答案来源于网络,供您参考”

牧明 2019-12-02 02:15:02 0 浏览量 回答数 0

回答

对任何涉及到操作函数调用签名的问题,你都应该使用 inspect 模块中的签名特性。 我们最主要关注两个类:Signature 和 Parameter 。下面是一个创建函数前面的交互例子: >>> from inspect import Signature, Parameter >>> # Make a signature for a func(x, y=42, *, z=None) >>> parms = [ Parameter('x', Parameter.POSITIONAL_OR_KEYWORD), ... Parameter('y', Parameter.POSITIONAL_OR_KEYWORD, default=42), ... Parameter('z', Parameter.KEYWORD_ONLY, default=None) ] >>> sig = Signature(parms) >>> print(sig) (x, y=42, *, z=None) >>> 一旦你有了一个签名对象,你就可以使用它的 bind() 方法很容易的将它绑定到 *args 和 **kwargs 上去。 下面是一个简单的演示: >>> def func(*args, **kwargs): ... bound_values = sig.bind(*args, **kwargs) ... for name, value in bound_values.arguments.items(): ... print(name,value) ... >>> # Try various examples >>> func(1, 2, z=3) x 1 y 2 z 3 >>> func(1) x 1 >>> func(1, z=3) x 1 z 3 >>> func(y=2, x=1) x 1 y 2 >>> func(1, 2, 3, 4) Traceback (most recent call last): ... File "/usr/local/lib/python3.3/inspect.py", line 1972, in _bind raise TypeError('too many positional arguments') TypeError: too many positional arguments >>> func(y=2) Traceback (most recent call last): ... File "/usr/local/lib/python3.3/inspect.py", line 1961, in _bind raise TypeError(msg) from None TypeError: 'x' parameter lacking default value >>> func(1, y=2, x=3) Traceback (most recent call last): ... File "/usr/local/lib/python3.3/inspect.py", line 1985, in _bind '{arg!r}'.format(arg=param.name)) TypeError: multiple values for argument 'x' >>> 可以看出来,通过将签名和传递的参数绑定起来,可以强制函数调用遵循特定的规则,比如必填、默认、重复等等。 下面是一个强制函数签名更具体的例子。在代码中,我们在基类中先定义了一个非常通用的 __init__() 方法, 然后我们强制所有的子类必须提供一个特定的参数签名。 from inspect import Signature, Parameter def make_sig(*names): parms = [Parameter(name, Parameter.POSITIONAL_OR_KEYWORD) for name in names] return Signature(parms) class Structure: __signature__ = make_sig() def __init__(self, *args, **kwargs): bound_values = self.__signature__.bind(*args, **kwargs) for name, value in bound_values.arguments.items(): setattr(self, name, value) # Example use class Stock(Structure): __signature__ = make_sig('name', 'shares', 'price') class Point(Structure): __signature__ = make_sig('x', 'y') 下面是使用这个 Stock 类的示例: >>> import inspect >>> print(inspect.signature(Stock)) (name, shares, price) >>> s1 = Stock('ACME', 100, 490.1) >>> s2 = Stock('ACME', 100) Traceback (most recent call last): ... TypeError: 'price' parameter lacking default value >>> s3 = Stock('ACME', 100, 490.1, shares=50) Traceback (most recent call last): ... TypeError: multiple values for argument 'shares' >>>

哦哦喔 2020-04-17 16:19:31 0 浏览量 回答数 0

回答

Go 的优势在于能够将简单的和经过验证的想法结合起来,同时避免了其他语言中出现的许多问题。本文概述了 Go 背后的一些设计原则和工程智慧,作者认为,Go 语言具备的所有这些优点,将共同推动其成为接替 Java 并主导下一代大型软件开发平台的最有力的编程语言候选。很多优秀的编程语言只是在个别领域比较强大,如果将所有因素都纳入考虑,没有其他语言能够像 Go 语言一样“全面开花”,在大型软件工程方面,尤为如此。 基于现实经验 Go 是由经验丰富的软件行业老手一手创建的,长期以来,他们对现有语言的各种缺点有过切身体会的痛苦经历。几十年前,Rob Pike 和 Ken Thompson 在 Unix、C 和 Unicode 的发明中起到了重要作用。Robert Griensemer 在为 JavaScript 和 Java 开发 V8 和 HotSpot 虚拟机之后,在编译器和垃圾收集方面拥有数十年的经验。有太多次,他们不得不等待 Google 规模的 C++/Java 代码库进行编译。于是,他们开始着手创建新的编程语言,将他们半个世纪以来的编写代码所学到的一切经验包含进去。 专注于大型工程 小型工程项目几乎可以用任何编程语言来成功构建。当成千上万的开发人员在数十年的持续时间压力下,在包含数千万行代码的大型代码库上进行协作时,就会发生真正令人痛苦的问题。这样会导致一些问题,如下: 较长的编译时间导致中断开发。代码库由几个人 / 团队 / 部门 / 公司所拥有,混合了不同的编程风格。公司雇佣了数千名工程师、架构师、测试人员、运营专家、审计员、实习生等,他们需要了解代码库,但也具备广泛的编码经验。依赖于许多外部库或运行时,其中一些不再以原始形式存在。在代码库的生命周期中,每行代码平均被重写 10 次,被弄得千疮百痍,而且还会发生技术偏差。文档不完整。 Go 注重减轻这些大型工程的难题,有时会以使小型工程变得更麻烦为代价,例如,代码中到处都需要几行额外的代码行。 注重可维护性 Go 强调尽可能多地将工作转给自动化的代码维护工具中。Go 工具链提供了最常用的功能,如格式化代码和导入、查找符号的定义和用法、简单的重构以及代码异味的识别。由于标准化的代码格式和单一的惯用方式,机器生成的代码更改看起来非常接近 Go 中人为生成的更改并使用类似的模式,从而允许人机之间更加无缝地协作。 保持简单明了 初级程序员为简单的问题创建简单的解决方案。高级程序员为复杂的问题创建复杂的解决方案。伟大的程序员找到复杂问题的简单解决方案。 ——Charles Connell 让很多人惊讶的一点是,Go 居然不包含他们喜欢的其他语言的概念。Go 确实是一种非常小巧而简单的语言,只包含正交和经过验证的概念的最小选择。这鼓励开发人员用最少的认知开销来编写尽可能简单的代码,以便许多其他人可以理解并使用它。 使事情清晰明了 良好的代码总是显而易见的,避免了那些小聪明、难以理解的语言特性、诡异的控制流和兜圈子。 许多语言都致力提高编写代码的效率。然而,在其生命周期中,人们阅读代码的时间却远远超过最初编写代码所需的时间(100 倍)。例如,审查、理解、调试、更改、重构或重用代码。在查看代码时,往往只能看到并理解其中的一小部分,通常不会有完整的代码库概述。为了解释这一点,Go 将所有内容都明确出来。 错误处理就是一个例子。让异常在各个点中断代码并在调用链上冒泡会更容易。Go 需要手动处理和返回每个错误。这使得它可以准确地显示代码可以被中断的位置以及如何处理或包装错误。总的来说,这使得错误处理编写起来更加繁琐,但是也更容易理解。 简单易学 Go 是如此的小巧而简单,以至于人们可以在短短几天内就能研究通整个语言及其基本概念。根据我们的经验,培训用不了一个星期(相比于掌握其他语言需要几个月),初学者就能够理解 Go 专家编写的代码,并为之做出贡献。为了方便吸引更多的用户,Go 网站提供了所有必要的教程和深入研究的文章。这些教程在浏览器中运行,允许人们在将 Go 安装到本地计算机上之前就能够学习和使用 Go。 解决之道 Go 强调的是团队之间的合作,而不是个人的自我表达。 在 Go(和 Python)中,所有的语言特性都是相互正交和互补的,通常有一种方法可以做一些事情。如果你想让 10 个 Python 或 Go 程序员来解决同一个问题,你将会得到 10 个相对类似的解决方案。不同的程序员在彼此的代码库中感觉更自在。在查看其他人的代码时,国骂会更少,而且人们的工作可以更好地融合在一起,从而形成了一致的整体,人人都为之感到自豪,并乐于工作。这还避免了大型工程的问题,如: 开发人员认为良好的工作代码很“混乱”,并要求在开始工作之前进行重写,因为他们的思维方式与原作者不同。 不同的团队成员使用不同的语言子集来编写相同代码库的部分内容。 ![image.png](https://ucc.alicdn.com/pic/developer-ecology/e64418f1455d46aaacfdd03fa949f16d.png) 简单、内置的并发性 Go 专为现代多核硬件设计。 目前使用的大多数编程语言(Java、JavaScript、Python、Ruby、C、C++)都是 20 世纪 80 年代到 21 世纪初设计的,当时大多数 CPU 只有一个计算内核。这就是为什么它们本质上是单线程的,并将并行化视为边缘情况的马后炮。通过现成和同步点之类的附加组件来实现,而这些附加组件既麻烦又难以正确使用。第三方库虽然提供了更简单的并发形式,如 Actor 模型,但是总有多个可用选项,结果导致了语言生态系统的碎片化。今天的硬件拥有越来越多的计算内核,软件必须并行化才能高效运行。Go 是在多核处理器时代编写的,并且在语言中内置了简单、高级的 CSP 风格并发性。 面向计算的语言原语 就深层而言,计算机系统接收数据,对其进行处理(通常要经过几个步骤),然后输出结果数据。例如,Web 服务器从客户端接收 HTTP 请求,并将其转换为一系列数据库或后端调用。一旦这些调用返回,它就将接收到的数据转换成 HTML 或 JSON 并将其输出给调用者。Go 的内置语言原语直接支持这种范例: 结构表示数据 读和写代表流式 IO 函数过程数据 goroutines 提供(几乎无限的)并发性 在并行处理步骤之间传输管道数据 因为所有的计算原语都是由语言以直接形式提供的,因此 Go 源代码更直接地表达了服务器执行的操作。 OO — 好的部分 更改基类中的某些内容的副作用 面向对象非常有用。过去几十年来,面向对象的使用富有成效,并让我们了解了它的哪些部分比其他部分能够更好地扩展。Go 在面向对象方面采用了一种全新的方法,并记住了这些知识。它保留了好的部分,如封装、消息传递等。Go 还避免了继承,因为它现在被认为是有害的,并为组合提供了一流的支持。 现代标准库 目前使用的许多编程语言(Java、JavaScript、Python、Ruby)都是在互联网成为当今无处不在的计算平台之前设计的。因此,这些语言的标准库只提供了相对通用的网络支持,而这些网络并没有针对现代互联网进行优化。Go 是十年前创建的,当时互联网已全面发展。Go 的标准库允许在没有第三方库的情况下创建更复杂的网络服务。这就避免了第三方库的常见问题: 碎片化:总是有多个选项实现相同的功能。 膨胀:库常常实现的不仅仅是它们的用途。 依赖地狱:库通常依赖于特定版本的其他库。 未知质量:第三方代码的质量和安全性可能存在问题。 未知支持:第三方库的开发可能随时停止支持。 意外更改:第三方库通常不像标准库那样严格地进行版本控制。 关于这方面更多的信息请参考 Russ Cox 提供的资料 标准化格式 Gofmt 的风格没有人会去喜欢,但人人都会喜欢 gofmt。 ——Rob Pike Gofmt 是一种以标准化方式来格式化 Go 代码的程序。它不是最漂亮的格式化方式,但却是最简单、最不令人生厌的格式化方式。标准化的源代码格式具有惊人的积极影响: 集中讨论重要主题: 它消除了围绕制表符和空格、缩进深度、行长、空行、花括号的位置等一系列争论。 开发人员在彼此的代码库中感觉很自在, 因为其他代码看起来很像他们编写的代码。每个人都喜欢自由地按照自己喜欢的方式进行格式化代码,但如果其他人按照自己喜欢的方式格式化了代码,这么做很招人烦。 自动代码更改并不会打乱手写代码的格式,例如引入了意外的空白更改。 许多其他语言社区现在正在开发类似 gofmt 的东西。当作为第三方解决方案构建时,通常会有几个相互竞争的格式标准。例如,JavaScript 提供了 Prettier 和 StandardJS。这两者都可以用,也可以只使用其中的一个。但许多 JS 项目并没有采用它们,因为这是一个额外的决策。Go 的格式化程序内置于该语言的标准工具链中,因此只有一个标准,每个人都在使用它。 快速编译 ![image.png](https://ucc.alicdn.com/pic/developer-ecology/8a76f3f07f484266af42781d9e7b8692.png) 对于大型代码库来说,它们长时间的编译是促使 Go 诞生的原因。Google 主要使用的是 C++ 和 Java,与 Haskell、Scala 或 Rust 等更复杂的语言相比,它们的编译速度相对较快。尽管如此,当编译大型代码库时,即使是少量的缓慢也会加剧编译的延迟,从而激怒开发人员,并干扰流程。Go 的设计初衷是为了提高编译效率,因此它的编译器速度非常快,几乎没有编译延迟的现象。这给 Go 开发人员提供了与脚本类语言类似的即时反馈,还有静态类型检查的额外好处。 交叉编译 由于语言运行时非常简单,因此它被移植到许多平台,如 macOS、Linux、Windows、BSD、ARM 等。Go 可以开箱即用地为所有这些平台编译二进制文件。这使得从一台机器进行部署变得很容易。 快速执行 Go 的运行速度接近于 C。与 JITed 语言(Java、JavaScript、Python 等)不同,Go 二进制文件不需要启动或预热的时间,因为它们是作为编译和完全优化的本地代码的形式发布的。Go 的垃圾收集器仅引入微秒量级的可忽略的停顿。除了快速的单核性能外,Go 还可以轻松利用所有的 CPU 内核。 内存占用小 像 JVM、Python 或 Node 这样的运行时不仅仅在运行时加载程序代码,每次运行程序时,它们还会加载大型且高度复杂的基础架构,以进行编译和优化程序。如此一来,它们的启动时间就变慢了,并且还占用了大量内存(数百兆字节)。而 Go 进程的开销更小,因为它们已经完全编译和优化,只需运行即可。Go 还以非常节省内存的方式来存储数据。在内存有限且昂贵的云环境中,以及在开发过程中,这一点非常重要。我们希望在一台机器上能够快速启动整个堆栈,同时将内存留给其他软件。 部署规模小 Go 的二进制文件大小非常简洁。Go 应用程序的 Docker 镜像通常比用 Java 或 Node 编写的等效镜像要小 10 倍,这是因为它无需包含编译器、JIT,以及更少的运行时基础架构的原因。这些特点,在部署大型应用程序时很重要。想象一下,如果要将一个简单的应用程序部署到 100 个生产服务器上会怎么样?如果使用 Node/JVM 时,我们的 Docker 注册表就必须提供 100 个 docker 镜像,每个镜像 200MB,那么一共就需要 20GB。要完成这些部署就需要一些时间。想象一下,如果我们想每天部署 100 次的话,如果使用 Go 服务,那么 Docker 注册表只需提供 10 个 docker 镜像,每个镜像只有 20MB,共只需 2GB 即可。大型 Go 应用程序可以更快、更频繁地部署,从而使得重要更新能够更快地部署到生产环境中。 独立部署 Go 应用程序部署为一个包含所有依赖项的单个可执行文件,并无需安装特定版本的 JVM、Node 或 Python 运行时;也不必将库下载到生产服务器上,更无须对运行 Go 二进制文件的机器进行任何更改。甚至也不需要讲 Go 二进制文件包装到 Docker 来共享他们。你需要做的是,只是将 Go 二进制文件放到服务器上,它就会在那里运行,而不用关心服务器运行的是什么。前面所提到的那些,唯一的例外是使用net和os/user包时针对对glibc的动态链接。 供应依赖关系 Go 有意识避免使用第三方库的中央存储库。Go 应用程序直接链接到相应的 Git 存储库,并将所有相关代码下载(供应)到自己的代码库中。这样做有很多好处: 在使用第三方代码之前,我们可以对其进行审查、分析和测试。该代码就和我们自己的代码一样,是我们应用程序的一部分,应该遵循相同的质量、安全性和可靠性标准。 无需永久访问存储依赖项的各个位置。从任何地方(包括私有 Git repos)获取第三方库,你就能永久拥有它们。 经过验收后,编译代码库无需进一步下载依赖项。 若互联网某处的代码存储库突然提供不同的代码,这也并不足为奇。 即使软件包存储库速度变慢,或托管包不复存在,部署也不会因此中断。 兼容性保证 Go 团队承诺现有的程序将会继续适用于新一代语言。这使得将大型项目升级到最新版本的编译器会非常容易,并且可从它们带来的许多性能和安全性改进中获益。同时,由于 Go 二进制文件包含了它们需要的所有依赖项,因此可以在同一服务器上并行运行使用不同版本的 Go 编译器编译的二进制文件,而无需进行复杂的多个版本的运行时设置或虚拟化。 文档 在大型工程中,文档对于使软件可访问性和可维护性非常重要。与其他特性类似,Go 中的文档简单实用: 由于它是嵌入到源代码中的,因此两者可以同时维护。 它不需要特殊的语法,文档只是普通的源代码注释。 可运行单元测试通常是最好的文档形式。因此 Go 要求将它们嵌入到文档中。 所有的文档实用程序都内置在工具链中,因此每个人都使用它们。 Go linter 需要导出元素的文档,以防止“文档债务”的积累。 商业支持的开源 当商业实体在开放式环境下开发时,那么一些最流行的、经过彻底设计的软件就会出现。这种设置结合了商业软件开发的优势——一致性和精细化,使系统更为健壮、可靠、高效,并具有开放式开发的优势,如来自许多行业的广泛支持,多个大型实体和许多用户的支持,以及即使商业支持停止的长期支持。Go 就是这样发展起来的。 缺点 当然,Go 也并非完美无缺,每种技术选择都是有利有弊。在决定选择 Go 之前,有几个方面需要进行考虑考虑。 未成熟 虽然 Go 的标准库在支持许多新概念(如 HTTP 2 Server push 等)方面处于行业领先地位,但与 JVM 生态系统中的第三方库相比,用于外部 API 的第三方 Go 库可能不那么成熟。 即将到来的改进 由于清楚几乎不可能改变现有的语言元素,Go 团队非常谨慎,只在新特性完全开发出来后才添加新特性。在经历了 10 年的有意稳定阶段之后,Go 团队正在谋划对语言进行一系列更大的改进,作为 Go 2.0 之旅的一部分。 无硬实时 虽然 Go 的垃圾收集器只引入了非常短暂的停顿,但支持硬实时需要没有垃圾收集的技术,例如 Rust。 结语 本文详细介绍了 Go 语言的一些优秀的设计准则,虽然有的准则的好处平常看起来没有那么明显。但当代码库和团队规模增长几个数量级时,这些准则可能会使大型工程项目免于许多痛苦。总的来说,正是这些设计准则让 Go 语言成为了除 Java 之外的编程语言里,用于大型软件开发项目的绝佳选择。

有只黑白猫 2020-01-07 14:11:38 0 浏览量 回答数 0

回答

Java 世界,也正是这些垃圾一样的设计已经、正在浪费着无数年轻 java 开发者的生命,@JFinal 设计初衷就是为了改变现状。因此:尽量一个功能一行代码搞定早已成为 JFinal的设计原则之一。这个原则贯穿了 JFinal 整个设计过程,例如文件上传功能: UplodeFile uf = getFile(),数据库查询:User user = User.dao.findFirst("select * from user where id=?", id),整个过程无xml,无annotation。######回复 @宏哥 : 如果 java 开发者慢慢觉醒,那么这个圈子会很大######回复 @宏哥 : Java 世界大气候是这样的,真理往往掌握在少数人手里,OSC 能懂宏哥的人能有多少呢? JFinal 开源才三个多月,目前用户量没上来,总结性的东西早就想过要写了,我甚至在OSC博客写好了标题,以后补内容即可。当前我闲余时间都用来完善 JFinal,以便能早日为这颠倒的世界出点力 :)######回复 @宏哥 : Java 世界大气候是这样的,真理往往掌握在少数人手里,OSC 能懂宏哥的人能有多少呢? JFinal 开源才三个多月,目前用户量没上来,总结性的东西早就想过要写了,我甚至在OSC博客写好了标题,以后补内容即可。当前我闲余时间都用来完善 JFinal,以便能早日为这颠倒的世界出点力 :)######回复 @宏哥 : Java 世界大气候是这样的,真理往往掌握在少数人手里,OSC 能懂宏哥的人能有多少呢? JFinal 开源才三个多月,目前用户量没上来,总结性的东西早就想过要写了,我甚至在OSC博客写好了标题,以后补内容即可。当前我闲余时间都用来完善 JFinal,以便能早日为这颠倒的世界出点力 :)######我的讨论还是在一个比较小的圈子范围.###### 引用来自“JFinal”的答案 Java 世界,也正是这些垃圾一样的设计已经、正在浪费着无数年轻 java 开发者的生命   这种垃圾设计被设立为标杆的时候,整个世界都颠倒过来. ######回复 @宏哥 : 这样一个颠倒的世界,或许只有 java 开发者在使用了 python ruby php 这类开发语言后才能有所反思。######回复 @JFinal : 看你的 Jfinal 了. 坦率的说, java还确实需要这样的东西来改变.毕竟,很多应用已经建立在java上.######可怕的是现在仍有无数开发者浑然不知,以为一切都是理所当然,甚至盲目崇拜 java 世界的这些垃圾设计,更别说来改变现状了###### 哈。别说JAVA了。当年做MFC,我发现我70%的时间是在查资料,验证,尝试将MFC的类折腾成我想要实现的方式,我便逃离了。安心折腾我的C。别人爱学不学,反正我对复杂的工具配件,有恐惧心里。现在有些工具,看上去就是个打满空的棍子,出售的人会说,你必学的三条理由: 1、他足够简单。你看只是个棍子。甚至可以烧烧后,扭曲成不同的模样。 2、他足够灵活,你看有这么多个空,可以用来插上其他模块。 3、你看,一堆人在院子里学的那么认真呢。足以说明这是个好工具。 等回头,交了钱,拿了个棍子,进到院子里,看到院墙内侧上挂满了配件,由此出现两个问题: 1、你不知道他们能做什么, 2、你想做什么时,也不知道能用哪个。 之所以院子里一堆人,绝大多数都是在解决上面两个问题而已。无非他们先来,你后到而已。######是源代码还是成品程序? 如果是后者那就太恐怖了。PHP也就一个函数调用加点参数。看来写java真的是浪费生命。 ###### 引用来自“mark35”的答案 是源代码还是成品程序? 如果是后者那就太恐怖了。PHP也就一个函数调用加点参数。看来写java真的是浪费生命。 http://www.oschina.net/question/553416_61341 ######写成这种代码,人都傻了######错的不是语言,只是用的人把java搞复杂了而已……######这跟java语言有什么关系?###### 我想说原来现在有这么人是拿来主义,你在调用别人的一行函数就解决问题了,你倒是爽了.你可曾经知道别人为写这个函数所费得时间和经历,到头来还反着骂写函数人写的是垃圾,可悲呀!###### @宏哥 与 jfinal 一定有基情,广告帖...######哎,发现水军、马甲几只。

kun坤 2020-06-08 11:22:22 0 浏览量 回答数 0

回答

事件驱动I/O本质上来讲就是将基本I/O操作(比如读和写)转化为你程序需要处理的事件。 例如,当数据在某个socket上被接受后,它会转换成一个 receive 事件,然后被你定义的回调方法或函数来处理。 作为一个可能的起始点,一个事件驱动的框架可能会以一个实现了一系列基本事件处理器方法的基类开始: class EventHandler: def fileno(self): 'Return the associated file descriptor' raise NotImplemented('must implement') def wants_to_receive(self): 'Return True if receiving is allowed' return False def handle_receive(self): 'Perform the receive operation' pass def wants_to_send(self): 'Return True if sending is requested' return False def handle_send(self): 'Send outgoing data' pass 这个类的实例作为插件被放入类似下面这样的事件循环中: import select def event_loop(handlers): while True: wants_recv = [h for h in handlers if h.wants_to_receive()] wants_send = [h for h in handlers if h.wants_to_send()] can_recv, can_send, _ = select.select(wants_recv, wants_send, []) for h in can_recv: h.handle_receive() for h in can_send: h.handle_send() 事件循环的关键部分是 select() 调用,它会不断轮询文件描述符从而激活它。 在调用 select() 之前,事件循环会询问所有的处理器来决定哪一个想接受或发生。 然后它将结果列表提供给 select() 。然后 select() 返回准备接受或发送的对象组成的列表。 然后相应的 handle_receive() 或 handle_send() 方法被触发。 编写应用程序的时候,EventHandler 的实例会被创建。例如,下面是两个简单的基于UDP网络服务的处理器例子: import socket import time class UDPServer(EventHandler): def __init__(self, address): self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.sock.bind(address) def fileno(self): return self.sock.fileno() def wants_to_receive(self): return True class UDPTimeServer(UDPServer): def handle_receive(self): msg, addr = self.sock.recvfrom(1) self.sock.sendto(time.ctime().encode('ascii'), addr) class UDPEchoServer(UDPServer): def handle_receive(self): msg, addr = self.sock.recvfrom(8192) self.sock.sendto(msg, addr) if __name__ == '__main__': handlers = [ UDPTimeServer(('',14000)), UDPEchoServer(('',15000)) ] event_loop(handlers) 测试这段代码,试着从另外一个Python解释器连接它: >>> from socket import * >>> s = socket(AF_INET, SOCK_DGRAM) >>> s.sendto(b'',('localhost',14000)) 0 >>> s.recvfrom(128) (b'Tue Sep 18 14:29:23 2012', ('127.0.0.1', 14000)) >>> s.sendto(b'Hello',('localhost',15000)) 5 >>> s.recvfrom(128) (b'Hello', ('127.0.0.1', 15000)) >>> 实现一个TCP服务器会更加复杂一点,因为每一个客户端都要初始化一个新的处理器对象。 下面是一个TCP应答客户端例子: class TCPServer(EventHandler): def __init__(self, address, client_handler, handler_list): self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) self.sock.bind(address) self.sock.listen(1) self.client_handler = client_handler self.handler_list = handler_list def fileno(self): return self.sock.fileno() def wants_to_receive(self): return True def handle_receive(self): client, addr = self.sock.accept() # Add the client to the event loop's handler list self.handler_list.append(self.client_handler(client, self.handler_list)) class TCPClient(EventHandler): def __init__(self, sock, handler_list): self.sock = sock self.handler_list = handler_list self.outgoing = bytearray() def fileno(self): return self.sock.fileno() def close(self): self.sock.close() # Remove myself from the event loop's handler list self.handler_list.remove(self) def wants_to_send(self): return True if self.outgoing else False def handle_send(self): nsent = self.sock.send(self.outgoing) self.outgoing = self.outgoing[nsent:] class TCPEchoClient(TCPClient): def wants_to_receive(self): return True def handle_receive(self): data = self.sock.recv(8192) if not data: self.close() else: self.outgoing.extend(data) if __name__ == '__main__': handlers = [] handlers.append(TCPServer(('',16000), TCPEchoClient, handlers)) event_loop(handlers) TCP例子的关键点是从处理器中列表增加和删除客户端的操作。 对每一个连接,一个新的处理器被创建并加到列表中。当连接被关闭后,每个客户端负责将其从列表中删除。 如果你运行程序并试着用Telnet或类似工具连接,它会将你发送的消息回显给你。并且它能很轻松的处理多客户端连接。

哦哦喔 2020-04-17 17:18:08 0 浏览量 回答数 0

回答

01状态机介绍 游戏中的状态机一般都是有限状态机,简写为FSM(有限状态机),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。 状态机的每一个状态至少需要有以下三个操作: 启动:当从其他状态进入这个状态时,需要进行的初始化操作; 更新:在这个状态运行时进行的更新操作; 清理:当从这个状态退出时,需要进行的清除操作。 状态需要的变量: 下一个:表示这个状态退出后要转到的下一个状态; 坚持:在状态间转换时需要传递的数据; 完成:表示这个状态是否结束,状态机会根据这个值来决定转换状态。 游戏界面状态机的状态转换图如下,箭头表示可能的状态转换方向:(注意有个转换不太好画出来:超时状态可以转换到Game Over状态。) 这几个状态的意思比较简单,下面把游戏界面的截图发一下。 主菜单:主菜单,启动程序就进入这个状态,可以用上和下键选择玩家1或玩家2,按回车键开启游戏。 加载屏幕:游戏开始前的加载界面。 游戏运行:游戏运行时的状态,在代码实现中是Level类。 游戏结束:人物死亡且生命数量为0时到这个状态。 超时:在游戏中时间超时会到这个状态,这个和Game Over类似,就不截图了。 02状态机代码实现 因为这篇文章的目的是游戏界面的状态机实现,所以专门写了一个state_demo.py文件,让大家可以更加方便的看代码。 游戏启动代码 开始是pygame的初始化,设置屏幕大小为c.SCREEN_SIZE(800,600)。所有的常量都保存在单独的constants.py中。 import os import pygame as pg import constants as c pg.init() pg.event.set_allowed([pg.KEYDOWN, pg.KEYUP, pg.QUIT]) pg.display.set_caption(c.ORIGINAL_CAPTION) SCREEN = pg.display.set_mode(c.SCREEN_SIZE) SCREEN_RECT = SCREEN.get_rect() load_all_gfx函数查找指定目录下所有符合后缀名的图片,使用pg.image.load函数加载,保存在图形set中。 GFX保存在资源/图形目录找到的所有图片,后面获取各种图形时会用到。 def load_all_gfx(directory, colorkey=(255,0,255), accept=('.png', '.jpg', '.bmp', '.gif')): graphics = {} for pic in os.listdir(directory): name, ext = os.path.splitext(pic) if ext.lower() in accept: img = pg.image.load(os.path.join(directory, pic)) if img.get_alpha(): img = img.convert_alpha() else: img = img.convert() img.set_colorkey(colorkey) graphics[name] = img return graphics GFX = load_all_gfx(os.path.join("resources","graphics")) 下面是demo的入口函数,先创建了一个保存所有状态的state_dict设置,调用setup_states函数设置启动状态是MAIN_MENU。 if __name__=='__main__': game = Control() state_dict = {c.MAIN_MENU: Menu(), c.LOAD_SCREEN: LoadScreen(), c.LEVEL: Level(), c.GAME_OVER: GameOver(), c.TIME_OUT: TimeOut()} game.setup_states(state_dict, c.MAIN_MENU) game.main() 状态类 先定义一个状态基类,按照上面说的状态需要的三个操作分别定义函数(启动,更新,清理)。在init函数中定义了上面说的三个变量(next,persist,```js done),还有start_time和current_time用于记录时间。 class State(): def init(self): self.start_time = 0.0 self.current_time = 0.0 self.done = False self.next = None self.persist = {} @abstractmethod def startup(self, current_time, persist): '''abstract method''' def cleanup(self): self.done = False return self.persist @abstractmethod def update(sefl, surface, keys, current_time): '''abstract method''' 看一个状态类LoadScreen的具体实现,这个状态的显示效果如图3。 startup函数保存了预期的persist,设置next为Level状态类,start_time保存进入该状态的开始时间。初始化一个Infoclass,这个就是专门用来显示界面信息的。 update函数根据在这个状态已运行的时间(current_time-self.start_time),决定显示内容和是否结束状态(self.done = True)。 ```js class LoadScreen(State): def __init__(self): State.__init__(self) self.time_list = [2400, 2600, 2635] def startup(self, current_time, persist): self.start_time = current_time self.persist = persist self.game_info = self.persist self.next = self.set_next_state() info_state = self.set_info_state() self.overhead_info = Info(self.game_info, info_state) def set_next_state(self): return c.LEVEL def set_info_state(self): return c.LOAD_SCREEN def update(self, surface, keys, current_time): if (current_time - self.start_time) < self.time_list[0]: surface.fill(c.BLACK) self.overhead_info.update(self.game_info) self.overhead_info.draw(surface) elif (current_time - self.start_time) < self.time_list[1]: surface.fill(c.BLACK) elif (current_time - self.start_time) < self.time_list[2]: surface.fill((106, 150, 252)) else: self.done = True 信息类 下面介绍的信息类,界面的显示大部分都是由它来完成,初始化函数中create_info_labels函数创建通用的信息,create_state_labels函数对于不同的状态,会初始化不同的信息。 class Info(): def __init__(self, game_info, state): self.coin_total = game_info[c.COIN_TOTAL] self.total_lives = game_info[c.LIVES] self.state = state self.game_info = game_info self.create_font_image_dict() self.create_info_labels() self.create_state_labels() self.flashing_coin = FlashCoin(280, 53) create_font_image_dict函数从之前加载的图片GFX ['text_images']中,截取字母和数字对应的图形,保存在一个设置中,在后面创建文字时会用到。 def create_font_image_dict(self): self.image_dict = {} image_list = [] image_rect_list = [# 0 - 9 (3, 230, 7, 7), (12, 230, 7, 7), (19, 230, 7, 7), (27, 230, 7, 7), (35, 230, 7, 7), (43, 230, 7, 7), (51, 230, 7, 7), (59, 230, 7, 7), (67, 230, 7, 7), (75, 230, 7, 7), # A - Z (83, 230, 7, 7), (91, 230, 7, 7), (99, 230, 7, 7), (107, 230, 7, 7), (115, 230, 7, 7), (123, 230, 7, 7), (3, 238, 7, 7), (11, 238, 7, 7), (20, 238, 7, 7), (27, 238, 7, 7), (35, 238, 7, 7), (44, 238, 7, 7), (51, 238, 7, 7), (59, 238, 7, 7), (67, 238, 7, 7), (75, 238, 7, 7), (83, 238, 7, 7), (91, 238, 7, 7), (99, 238, 7, 7), (108, 238, 7, 7), (115, 238, 7, 7), (123, 238, 7, 7), (3, 246, 7, 7), (11, 246, 7, 7), (20, 246, 7, 7), (27, 246, 7, 7), (48, 246, 7, 7), # -* (68, 249, 6, 2), (75, 247, 6, 6)] character_string = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ -*' for character, image_rect in zip(character_string, image_rect_list): self.image_dict[character] = get_image(GFX['text_images'], *image_rect, (92, 148, 252), 2.9) get_image函数从一个大的表面工作表中按照面积(x,y,宽度,高度)截取的部分图片放入表面图像对应的起始位置(0,0),并按比例参数调整大小。 pygame的blit函数介绍如下: pg.Surface.blit(source, dest, area=None, special_flags=0) -> Rect draw one image onto another def get_image(sheet, x, y, width, height, colorkey, scale): image = pg.Surface([width, height]) rect = image.get_rect() image.blit(sheet, (0, 0), (x, y, width, height)) image.set_colorkey(colorkey) image = pg.transform.scale(image, (int(rect.width*scale), int(rect.height*scale))) return image 看一下create_info_labels函数中其中一个字符串'MARIO'是如何在界面上显示的。 create_label函数参数(x,y)表示字符串在界面上的起始位置,从self.image_dict中根据字符获取对应的表面对象。 set_label_rects函数会设置字符串中每一个表面对象rect的(x,y)值。 pygame.Rect 对象中常用的成员变量(x,y),表示这个Surface的左上角的位置。 top, bottom: 表示Surface 在y轴上最上边和最下边的值, 所以top和y 值是一样的 left, right: 表示Surface 在x轴上最左边和最右边的值,所以left 和x 值是一样的 下面的坐标图可以看到,在左上角是整个屏幕的原点(0,0),图中标识了附件矩形的四个顶点的坐标。 def create_info_labels(self): ... self.mario_label = [] ... self.create_label(self.mario_label, 'MARIO', 75, 30) def create_label(self, label_list, string, x, y): for letter in string: label_list.append(Character(self.image_dict[letter])) self.set_label_rects(label_list, x, y) def set_label_rects(self, label_list, x, y): for i, letter in enumerate(label_list): letter.rect.x = x + ((letter.rect.width + 3) * i) letter.rect.y = y if letter.image == self.image_dict['-']: letter.rect.y += 7 letter.rect.x += 2 控制类 Control是状态机类,main函数是游戏的主循环,setup_states函数设置游戏启动时运行的状态。 class Control(): def __init__(self): self.screen = pg.display.get_surface() self.done = False self.clock = pg.time.Clock() self.fps = 60 self.current_time = 0.0 self.keys = pg.key.get_pressed() self.state_dict = {} self.state_name = None self.state = None def setup_states(self, state_dict, start_state): self.state_dict = state_dict self.state_name = start_state self.state = self.state_dict[self.state_name] def main(self): while not self.done: self.event_loop() self.update() pg.display.update() self.clock.tick(self.fps) event_loop函数负责监听输入(键盘输入和退出按钮),slef.keys保存键盘输入。 如果检测到当前状态结束,就调用flip_state函数进行旧状态的清理操作,并转换到下一个状态。更新函数会检测状态的完成值,调用状态的更新函数。 def update(self): self.current_time = pg.time.get_ticks() if self.state.done: self.flip_state() self.state.update(self.screen, self.keys, self.current_time) def flip_state(self): previous, self.state_name = self.state_name, self.state.next persist = self.state.cleanup() self.state = self.state_dict[self.state_name] self.state.startup(self.current_time, persist) def event_loop(self): for event in pg.event.get(): if event.type == pg.QUIT: self.done = True elif event.type == pg.KEYDOWN: self.keys = pg.key.get_pressed() elif event.type == pg.KEYUP: self.keys = pg.key.get_pressed() 03完整代码 有两个文件constants.py和state_demo.py,constants.py保存了所有的字符串定义和常量。 constants.py GAME_TIME_OUT表示游戏的超时时间,这边为了demo演示,设成5秒,实际是300秒。 SCREEN_HEIGHT = 600 SCREEN_WIDTH = 800 SCREEN_SIZE = (SCREEN_WIDTH,SCREEN_HEIGHT) ORIGINAL_CAPTION = "Super Mario Bros" GAME_TIME_OUT = 5 ## COLORS ## # R G B BLACK = ( 0, 0, 0) SIZE_MULTIPLIER = 2.5 BRICK_SIZE_MULTIPLIER = 2.69 BACKGROUND_MULTIPLER = 2.679 GROUND_HEIGHT = SCREEN_HEIGHT - 62 #STATES FOR ENTIRE GAME MAIN_MENU = 'main menu' LOAD_SCREEN = 'load screen' TIME_OUT = 'time out' GAME_OVER = 'game over' LEVEL = 'level' #MAIN MENU CURSOR STATES PLAYER1 = '1 PLAYER GAME' PLAYER2 = '2 PLAYER GAME' #GAME INFO DICTIONARY KEYS COIN_TOTAL = 'coin total' SCORE = 'score' TOP_SCORE = 'top score' LIVES = 'lives' CURRENT_TIME = 'current time' LEVEL_NUM = 'level num' PLAYER_NAME = 'player name' PLAYER_MARIO = 'mario' PLAYER_LUIGI = 'luigi' ITEM_SHEET = 'item_objects' state_demo.py 上面讲的状态类,状态机类都放在这里。 import os import pygame as pg from abc import ABC, abstractmethod import constants as c class State(): def __init__(self): self.start_time = 0.0 self.current_time = 0.0 self.done = False self.next = None self.persist = {} @abstractmethod def startup(self, current_time, persist): '''abstract method''' def cleanup(self): self.done = False return self.persist @abstractmethod def update(sefl, surface, keys, current_time): '''abstract method''' class Menu(State): def __init__(self): State.__init__(self) persist = {c.COIN_TOTAL: 0, c.SCORE: 0, c.LIVES: 3, c.TOP_SCORE: 0, c.CURRENT_TIME: 0.0, c.LEVEL_NUM: 1, c.PLAYER_NAME: c.PLAYER_MARIO} self.startup(0.0, persist) def startup(self, current_time, persist): self.next = c.LOAD_SCREEN self.persist = persist self.game_info = persist self.overhead_info = Info(self.game_info, c.MAIN_MENU) self.setup_background() self.setup_player() self.setup_cursor() def setup_background(self): self.background = GFX['level_1'] self.background_rect = self.background.get_rect() self.background = pg.transform.scale(self.background, (int(self.background_rect.width*c.BACKGROUND_MULTIPLER), int(self.background_rect.height*c.BACKGROUND_MULTIPLER))) self.viewport = SCREEN.get_rect(bottom=SCREEN_RECT.bottom) self.image_dict = {} image = get_image(GFX['title_screen'], 1, 60, 176, 88, (255, 0, 220), c.SIZE_MULTIPLIER) rect = image.get_rect() rect.x, rect.y = (170, 100) self.image_dict['GAME_NAME_BOX'] = (image, rect) def setup_player(self): self.player_list = [] player_rect_info = [(178, 32, 12, 16), (178, 128, 12, 16)] for rect in player_rect_info: image = get_image(GFX['mario_bros'], *rect, c.BLACK, 2.9) rect = image.get_rect() rect.x, rect.bottom = 110, c.GROUND_HEIGHT self.player_list.append((image, rect)) self.player_index = 0 def setup_cursor(self): self.cursor = pg.sprite.Sprite() self.cursor.image = get_image(GFX[c.ITEM_SHEET], 24, 160, 8, 8, c.BLACK, 3) rect = self.cursor.image.get_rect() rect.x, rect.y = (220, 358) self.cursor.rect = rect self.cursor.state = c.PLAYER1 def update(self, surface, keys, current_time): self.current_time = current_time self.game_info[c.CURRENT_TIME] = self.current_time self.player_image = self.player_list[self.player_index][0] self.player_rect = self.player_list[self.player_index][1] self.update_cursor(keys) self.overhead_info.update(self.game_info) surface.blit(self.background, self.viewport, self.viewport) surface.blit(self.image_dict['GAME_NAME_BOX'][0], self.image_dict['GAME_NAME_BOX'][1]) surface.blit(self.player_image, self.player_rect) surface.blit(self.cursor.image, self.cursor.rect) self.overhead_info.draw(surface) def update_cursor(self, keys): if self.cursor.state == c.PLAYER1: self.cursor.rect.y = 358 if keys[pg.K_DOWN]: self.cursor.state = c.PLAYER2 self.player_index = 1 self.game_info[c.PLAYER_NAME] = c.PLAYER_LUIGI elif self.cursor.state == c.PLAYER2: self.cursor.rect.y = 403 if keys[pg.K_UP]: self.cursor.state = c.PLAYER1 self.player_index = 0 self.game_info[c.PLAYER_NAME] = c.PLAYER_MARIO if keys[pg.K_RETURN]: self.done = True class LoadScreen(State): def __init__(self): State.__init__(self) self.time_list = [2400, 2600, 2635] def startup(self, current_time, persist): self.start_time = current_time self.persist = persist self.game_info = self.persist self.next = self.set_next_state() info_state = self.set_info_state() self.overhead_info = Info(self.game_info, info_state) def set_next_state(self): return c.LEVEL def set_info_state(self): return c.LOAD_SCREEN def update(self, surface, keys, current_time): if (current_time - self.start_time) < self.time_list[0]: surface.fill(c.BLACK) self.overhead_info.update(self.game_info) self.overhead_info.draw(surface) elif (current_time - self.start_time) < self.time_list[1]: surface.fill(c.BLACK) elif (current_time - self.start_time) < self.time_list[2]: surface.fill((106, 150, 252)) else: self.done = True class GameOver(LoadScreen): def __init__(self): LoadScreen.__init__(self) self.time_list = [3000, 3200, 3235] def set_next_state(self): return c.MAIN_MENU def set_info_state(self): return c.GAME_OVER class TimeOut(LoadScreen): def __init__(self): LoadScreen.__init__(self) self.time_list = [2400, 2600, 2635] def set_next_state(self): if self.persist[c.LIVES] == 0: return c.GAME_OVER else: return c.LOAD_SCREEN def set_info_state(self): return c.TIME_OUT class Level(State): def __init__(self): State.__init__(self) def startup(self, current_time, persist): self.game_info = persist self.persist = self.game_info self.player = None self.overhead_info = Info(self.game_info, c.LEVEL) self.setup_background() def setup_background(self): self.background = GFX['level_1'] self.bg_rect = self.background.get_rect() self.background = pg.transform.scale(self.background, (int(self.bg_rect.width*c.BACKGROUND_MULTIPLER), int(self.bg_rect.height*c.BACKGROUND_MULTIPLER))) self.bg_rect = self.background.get_rect() self.level = pg.Surface((self.bg_rect.w, self.bg_rect.h)).convert() self.viewport = SCREEN.get_rect(bottom=self.bg_rect.bottom) def update(self, surface, keys, current_time): self.game_info[c.CURRENT_TIME] = self.current_time = current_time self.overhead_info.update(self.game_info, self.player) if self.overhead_info.time <= 0: self.update_game_info() self.done = True self.draw(surface) def update_game_info(self): self.persist[c.LIVES] -= 1 if self.persist[c.LIVES] == 0: self.next = c.GAME_OVER elif self.overhead_info.time == 0: self.next = c.TIME_OUT else: self.next = c.LOAD_SCREEN def draw(self, surface): self.level.blit(self.background, self.viewport, self.viewport) surface.blit(self.level, (0,0), self.viewport) self.overhead_info.draw(surface) class Character(pg.sprite.Sprite): def __init__(self, image): pg.sprite.Sprite.__init__(self) self.image = image self.rect = self.image.get_rect() class Info(): def __init__(self, game_info, state): self.coin_total = game_info[c.COIN_TOTAL] self.total_lives = game_info[c.LIVES] self.state = state self.game_info = game_info self.create_font_image_dict() self.create_info_labels() self.create_state_labels() self.flashing_coin = FlashCoin(280, 53) def create_font_image_dict(self): self.image_dict = {} image_list = [] image_rect_list = [# 0 - 9 (3, 230, 7, 7), (12, 230, 7, 7), (19, 230, 7, 7), (27, 230, 7, 7), (35, 230, 7, 7), (43, 230, 7, 7), (51, 230, 7, 7), (59, 230, 7, 7), (67, 230, 7, 7), (75, 230, 7, 7), # A - Z (83, 230, 7, 7), (91, 230, 7, 7), (99, 230, 7, 7), (107, 230, 7, 7), (115, 230, 7, 7), (123, 230, 7, 7), (3, 238, 7, 7), (11, 238, 7, 7), (20, 238, 7, 7), (27, 238, 7, 7), (35, 238, 7, 7), (44, 238, 7, 7), (51, 238, 7, 7), (59, 238, 7, 7), (67, 238, 7, 7), (75, 238, 7, 7), (83, 238, 7, 7), (91, 238, 7, 7), (99, 238, 7, 7), (108, 238, 7, 7), (115, 238, 7, 7), (123, 238, 7, 7), (3, 246, 7, 7), (11, 246, 7, 7), (20, 246, 7, 7), (27, 246, 7, 7), (48, 246, 7, 7), # -* (68, 249, 6, 2), (75, 247, 6, 6)] character_string = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ -*' for character, image_rect in zip(character_string, image_rect_list): self.image_dict[character] = get_image(GFX['text_images'], *image_rect, (92, 148, 252), 2.9) def create_info_labels(self): self.score_text = [] self.coin_count_text = [] self.mario_label = [] self.world_label = [] self.time_label = [] self.stage_label = [] self.create_label(self.score_text, '000000', 75, 55) self.create_label(self.coin_count_text, '*00', 300, 55) self.create_label(self.mario_label, 'MARIO', 75, 30) self.create_label(self.world_label, 'WORLD', 450, 30) self.create_label(self.time_label, 'TIME', 625, 30) self.create_label(self.stage_label, '1-1', 472, 55) self.info_labels = [self.score_text, self.coin_count_text, self.mario_label, self.world_label, self.time_label, self.stage_label] def create_state_labels(self): if self.state == c.MAIN_MENU: self.create_main_menu_labels() elif self.state == c.LOAD_SCREEN: self.create_player_image() self.create_load_screen_labels() elif self.state == c.LEVEL: self.create_level_labels() elif self.state == c.GAME_OVER: self.create_game_over_labels() elif self.state == c.TIME_OUT: self.create_time_out_labels() def create_player_image(self): self.life_times_image = get_image(GFX['text_images'], 75, 247, 6, 6, (92, 148, 252), 2.9) self.life_times_rect = self.life_times_image.get_rect(center=(378, 295)) self.life_total_label = [] self.create_label(self.life_total_label, str(self.total_lives), 450, 285) if self.game_info[c.PLAYER_NAME] == c.PLAYER_MARIO: rect = (178, 32, 12, 16) else: rect = (178, 128, 12, 16) self.player_image = get_image(GFX['mario_bros'], *rect, (92, 148, 252), 2.9) self.player_rect = self.player_image.get_rect(center=(320, 290)) def create_main_menu_labels(self): mario_game = [] luigi_game = [] top = [] top_score = [] self.create_label(mario_game, c.PLAYER1, 272, 360) self.create_label(luigi_game, c.PLAYER2, 272, 405) self.create_label(top, 'TOP - ', 290, 465) self.create_label(top_score, '000000', 400, 465) self.state_labels = [mario_game, luigi_game, top, top_score, *self.info_labels] def create_load_screen_labels(self): world_label = [] self.stage_label2 = [] self.create_label(world_label, 'WORLD', 280, 200) self.create_label(self.stage_label2, '1-1', 430, 200) self.state_labels = [world_label, self.stage_label2, *self.info_labels, self.life_total_label] def create_level_labels(self): self.time = c.GAME_TIME_OUT self.current_time = 0 self.clock_time_label = [] self.create_label(self.clock_time_label, str(self.time), 645, 55) self.state_labels = [*self.info_labels, self.clock_time_label] def create_game_over_labels(self): game_label = [] over_label = [] self.create_label(game_label, 'GAME', 280, 300) self.create_label(over_label, 'OVER', 400, 300) self.state_labels = [game_label, over_label, *self.info_labels] def create_time_out_labels(self): timeout_label = [] self.create_label(timeout_label, 'TIME OUT', 290, 310) self.state_labels = [timeout_label, *self.info_labels] def create_label(self, label_list, string, x, y): for letter in string: label_list.append(Character(self.image_dict[letter])) self.set_label_rects(label_list, x, y) def set_label_rects(self, label_list, x, y): for i, letter in enumerate(label_list): letter.rect.x = x + ((letter.rect.width + 3) * i) letter.rect.y = y if letter.image == self.image_dict['-']: letter.rect.y += 7 letter.rect.x += 2 def update(self, level_info, level=None): self.level = level self.handle_level_state(level_info) def handle_level_state(self, level_info): self.score = level_info[c.SCORE] self.update_text(self.score_text, self.score) self.update_text(self.coin_count_text, level_info[c.COIN_TOTAL]) self.update_text(self.stage_label, level_info[c.LEVEL_NUM]) self.flashing_coin.update(level_info[c.CURRENT_TIME]) if self.state == c.LOAD_SCREEN: self.update_text(self.stage_label2, level_info[c.LEVEL_NUM]) if self.state == c.LEVEL: if (level_info[c.CURRENT_TIME] - self.current_time) > 1000: self.current_time = level_info[c.CURRENT_TIME] self.time -= 1 self.update_text(self.clock_time_label, self.time, True) def update_text(self, text, score, reset=False): if reset and len(text) > len(str(score)): text.remove(text[0]) index = len(text) - 1 for digit in reversed(str(score)): rect = text[index].rect text[index] = Character(self.image_dict[digit]) text[index].rect = rect index -= 1 def draw(self, surface): self.draw_info(surface, self.state_labels) if self.state == c.LOAD_SCREEN: surface.blit(self.player_image, self.player_rect) surface.blit(self.life_times_image, self.life_times_rect) surface.blit(self.flashing_coin.image, self.flashing_coin.rect) def draw_info(self, surface, label_list): for label in label_list: for letter in label: surface.blit(letter.image, letter.rect) class FlashCoin(pg.sprite.Sprite): def __init__(self, x, y): pg.sprite.Sprite.__init__(self) self.frame_index = 0 self.frames = [] self.load_frames() self.image = self.frames[self.frame_index] self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.animation_timer = 0 def load_frames(self): sheet = GFX[c.ITEM_SHEET] frame_rect_list = [(1, 160, 5, 8), (9, 160, 5, 8), (17, 160, 5, 8), (9, 160, 5, 8)] for frame_rect in frame_rect_list: self.frames.append(get_image(sheet, *frame_rect, c.BLACK, c.BRICK_SIZE_MULTIPLIER)) def update(self, current_time): time_list = [375, 125, 125, 125] if self.animation_timer == 0: self.animation_timer = current_time elif (current_time - self.animation_timer) > time_list[self.frame_index]: self.frame_index += 1 if self.frame_index == 4: self.frame_index = 0 self.animation_timer = current_time self.image = self.frames[self.frame_index] class Control(): def __init__(self): self.screen = pg.display.get_surface() self.done = False self.clock = pg.time.Clock() self.fps = 60 self.current_time = 0.0 self.keys = pg.key.get_pressed() self.state_dict = {} self.state_name = None self.state = None def setup_states(self, state_dict, start_state): self.state_dict = state_dict self.state_name = start_state self.state = self.state_dict[self.state_name] def update(self): self.current_time = pg.time.get_ticks() if self.state.done: self.flip_state() self.state.update(self.screen, self.keys, self.current_time) def flip_state(self): previous, self.state_name = self.state_name, self.state.next persist = self.state.cleanup() self.state = self.state_dict[self.state_name] self.state.startup(self.current_time, persist) def event_loop(self): for event in pg.event.get(): if event.type == pg.QUIT: self.done = True elif event.type == pg.KEYDOWN: self.keys = pg.key.get_pressed() elif event.type == pg.KEYUP: self.keys = pg.key.get_pressed() def main(self): while not self.done: self.event_loop() self.update() pg.display.update() self.clock.tick(self.fps) def get_image(sheet, x, y, width, height, colorkey, scale): image = pg.Surface([width, height]) rect = image.get_rect() image.blit(sheet, (0, 0), (x, y, width, height)) image.set_colorkey(colorkey) image = pg.transform.scale(image, (int(rect.width*scale), int(rect.height*scale))) return image def load_all_gfx(directory, colorkey=(255,0,255), accept=('.png', '.jpg', '.bmp', '.gif')): graphics = {} for pic in os.listdir(directory): name, ext = os.path.splitext(pic) if ext.lower() in accept: img = pg.image.load(os.path.join(directory, pic)) if img.get_alpha(): img = img.convert_alpha() else: img = img.convert() img.set_colorkey(colorkey) graphics[name] = img return graphics # pygame related initial code pg.init() pg.event.set_allowed([pg.KEYDOWN, pg.KEYUP, pg.QUIT]) pg.display.set_caption(c.ORIGINAL_CAPTION) SCREEN = pg.display.set_mode(c.SCREEN_SIZE) SCREEN_RECT = SCREEN.get_rect() GFX = load_all_gfx(os.path.join("resources","graphics")) if __name__=='__main__': game = Control() state_dict = {c.MAIN_MENU: Menu(), c.LOAD_SCREEN: LoadScreen(), c.LEVEL: Level(), c.GAME_OVER: GameOver(), c.TIME_OUT: TimeOut()} game.setup_states(state_dict, c.MAIN_MENU) game.main() 用到的图片 图片文件名要保存为对应的,不然代码中会找到,并且保存到state_demo.py所在目录下的resources \ graphics子目录中。如果能上github,可以直接下载resources \ graphics目录中的图片。 1,item_objects.png 2,level_1.png 3,mario_bros.png 4,text_images.png 5,tile_set.png 6,title_screen.png 编译环境:python3.7 + pygame1.9。 原文链接

珍宝珠 2019-12-25 10:11:52 0 浏览量 回答数 0
阿里云大学 云服务器ECS com域名 网站域名whois查询 开发者平台 小程序定制 小程序开发 国内短信套餐包 开发者技术与产品 云数据库 图像识别 开发者问答 阿里云建站 阿里云备案 云市场 万网 阿里云帮助文档 免费套餐 开发者工具 企业信息查询 小程序开发制作 视频内容分析 企业网站制作 视频集锦 代理记账服务 2020阿里巴巴研发效能峰会 企业建站模板 云效成长地图 高端建站