Movatterモバイル変換


[0]ホーム

URL:


Packt
Search iconClose icon
Search icon CANCEL
Subscription
0
Cart icon
Your Cart(0 item)
Close icon
You have no products in your basket yet
Save more on your purchases!discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Profile icon
Account
Close icon

Change country

Modal Close icon
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timerSALE ENDS IN
0Days
:
00Hours
:
00Minutes
:
00Seconds
Home> Programming> Object Oriented Programming> Mastering Object-oriented Python
Mastering Object-oriented Python
Mastering Object-oriented Python

Mastering Object-oriented Python: If you want to master object-oriented Python programming this book is a must-have. With 750 code samples and a relaxed tutorial, it's a seamless route to programming Python.

Arrow left icon
Profile Icon Steven F. LottProfile Icon Steven F. Lott
Arrow right icon
€37.99
Full star iconFull star iconFull star iconFull star iconHalf star icon4.2(13 Ratings)
PaperbackApr 2014634 pages Edition
eBook
€25.99 €28.99
Paperback
€37.99
Subscription
Free Trial
Renews at €18.99p/m
Arrow left icon
Profile Icon Steven F. LottProfile Icon Steven F. Lott
Arrow right icon
€37.99
Full star iconFull star iconFull star iconFull star iconHalf star icon4.2(13 Ratings)
PaperbackApr 2014634 pages Edition
eBook
€25.99 €28.99
Paperback
€37.99
Subscription
Free Trial
Renews at €18.99p/m
eBook
€25.99 €28.99
Paperback
€37.99
Subscription
Free Trial
Renews at €18.99p/m

What do you get with Print?

Product feature iconInstant access to your digital copy whilst your Print order is Shipped
Product feature icon Paperback book shipped to your preferred address
Product feature icon Redeem a companion digital copy on all Print orders
Product feature icon Access this title in our online reader with advanced features
Product feature iconDRM FREE - Read whenever, wherever and however you want
OR

Contact Details

Modal Close icon
Payment Processing...
tickCompleted

Shipping Address

Billing Address

Shipping Methods
Table of content iconView table of contentsPreview book icon Preview Book

Mastering Object-oriented Python

Chapter 1. The __init__() Method

The__init__() method is profound for two reasons. Initialization is the first big step in an object's life; every object must be initialized properly to work properly. The second reason is that the argument values for__init__() can take on many forms.

Because there are so many ways to provide argument values to__init__(), there is a vast array of use cases for object creation. We take a look at several of them. We want to maximize clarity, so we need to define an initialization that properly characterizes the problem domain.

Before we can get to the__init__() method, however, we need to take a look at the implicit class hierarchy in Python, glancing, briefly, at the class namedobject. This will set the stage for comparing default behavior with the different kinds of behavior we want from our own classes.

In this chapter, we take a look at different forms of initialization for simple objects (for example, playing cards). After this, we can take a look at more complex objects, such as hands that involve collections and players that involve strategies and states.

The implicit superclass – object


Each Pythonclass definition has an implicit superclass:object. It's a very simple class definition that does almost nothing. We can create instances ofobject, but we can't do much with them because many of the special methods simply raise exceptions.

When we define our own class,object is the superclass. The following is an example class definition that simply extendsobject with a new name:

class X:    pass

The following are some interactions with our class:

>>> X.__class__<class 'type'>>>> X.__class__.__base__<class 'object'>

We can see that a class is an object of the class namedtype and that the base class for our new class is the class namedobject.

As we look at each method, we also take a look at the default behavior inherited fromobject. In some cases, the superclass special method behavior will be exactly what we want. In other cases, we'll need to override the special method.

The base class object __init__() method


Fundamental to the life cycle of an object are its creation, initialization, and destruction. We'll defer creation and destruction to a later chapter on more advanced special methods and only focus on initialization for now.

The superclass of all classes,object, has a default implementation of__init__() that amounts topass. We aren't required to implement__init__(). If we don't implement it, then no instance variables will be created when the object is created. In some cases, this default behavior is acceptable.

We can always add attributes to an object that's a subclass of the foundational base class,object. Consider the following class that requires two instance variables but doesn't initialize them:

class Rectangle:    def area( self ):        return self.length * self.width

TheRectangle class has a method that uses two attributes to return a value. The attributes have not been initialized anywhere. This is legal Python. It's a little strange to avoid specifically setting attributes, but it's valid.

The following is an interaction with theRectangle class:

>>> r= Rectangle()>>> r.length, r.width = 13, 8>>> r.area()104

While this is legal, it's a potential source of deep confusion, which is a good reason to avoid it.

However, this kind of design grants flexibility, so there could be times when we needn't set allof the attributes in the__init__() method. We walk a fine line here. An optional attribute is a kind of subclass that's not formally declared as aproper subclass. We're creating polymorphism in a way that could lead to confusing and inappropriate use of convolutedif statements. While uninitialized attributes may be useful, they could be the symptom of a bad design.

TheZen of Python poem (import this) offers the following advice:

"Explicit is better than implicit."

An__init__() method should make the instance variables explicit.

Tip

Pretty Poor Polymorphism

There's a fine line between flexibility and foolishness.

We may have stepped over the edge offflexible intofoolish as soon as we feel the need to write:

if 'x' in self.__dict__:

Or:

try:    self.xexcept AttributeError:

It's time to reconsider the API and add a common method or attribute. Refactoring is better than addingif statements.

Implementing __init__() in a superclass


We initialize an object by implementing the__init__() method. When an object is created, Python first creates an empty object and then calls the__init__() method for that new object. This method function generally creates the object's instance variables and performs any other one-time processing.

The following are some example definitions of aCard class hierarchy. We'll define aCard superclass and three subclasses that are variations of the basic theme ofCard. We have two instance variables that have been set directly from argument values and two variables that have been calculated by an initialization method:

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, 11class FaceCard( Card ):    def _points( self ):        return 10, 10

In thisexample, we factored the__init__() method into the superclass so that a common initialization in the superclass,Card, appliesto all the three subclassesNumberCard,AceCard, andFaceCard.

This shows a common polymorphic design. Each subclass provides a unique implementation of the_points() method. All the subclasses have identical signatures: they have the same methods and attributes. Objects of these three subclasses can be used interchangeably in an application.

If we simply use characters for suits, we will be able to createCard instances as shown in the following code snippet:

cards = [ AceCard('A', '♠'), NumberCard('2','♠'), NumberCard('3','♠'), ]

We enumerated the class, rank, and suit for several cards in a list. In the long run, we need a much smarter factory function to buildCard instances; enumerating all 52 cards this way is tedious and error prone. Before we get to the factory functions, we take a look at a number of other issues.

Using __init__() to create manifest constants


We can define a class for the suits of our cards. In blackjack, the suits don't matter, and a simplecharacter string couldwork.

We use suit construction as an example of creating constant objects. In many cases, our application will have a small domain of objects that can be defined by a collection of constants. A smalldomain of static objects may be part of implementing aStrategy orState design pattern.

In some cases, we may have a pool of constant objects created in an initialization or configuration file, or we might create constant objects based on command-line parameters. We'll return to the details of initialization design and startup design inChapter 16,Coping with the Command Line.

Pythonhas no simple formalmechanism for defining an object as immutable. We'll look at techniques to assure immutability inChapter 3,Attribute Access, Properties, and Descriptors. In this example, it might make sense for the attributes of a suit to be immutable.

The following is a class that we'll use to build four manifest constants:

class Suit:    def __init__( self, name, symbol ):        self.name= name        self.symbol= symbol

The following is the domain of "constants" built around this class:

Club, Diamond, Heart, Spade = Suit('Club','♣'), Suit('Diamond','♦'), Suit('Heart','♥'), Suit('Spade','♠')

We can now createcards as shown in the following code snippet:

cards = [ AceCard('A', Spade), NumberCard('2', Spade), NumberCard('3', Spade), ]

For an example this small, this method isn't a huge improvement over single character suit codes. In more complex cases, there may be a short list of Strategy or State objects that can be created like this. This can make the Strategy or State design patterns work efficiently by reusing objects from a small, static pool of constants.

We do have to acknowledge that in Python these objects aren't technically constant; they are mutable. There may be some benefit in doing the extra coding to make these objects truly immutable.

Tip

The irrelevance of immutability

Immutability can become an attractive nuisance. It's sometimes justified by the mythical "malicious programmer" who modifies the constant value in their application. As a design consideration, this is silly. This mythical, malicious programmer can't be stopped this way. There's no easy way to "idiot-proof" code in Python. The malicious programmer has access to the source and can tweak it just as easily as they can write code to modify a constant.

It's better not to struggle too long to define the classes of immutable objects. InChapter 3,Attribute Access, Properties, and Descriptors, we'll show ways to implement immutability that provides suitable diagnostic information for a buggy program.

Leveraging __init__() via a factory function


We can build a complete deck of cards via a factory function. This beats enumerating all 52 cards. InPython, we have two common approaches to factories as follows:

  • We define a function that creates objects of the required classes.

  • We define a class that has methods for creating objects. This is the full factory design pattern, as described in books on design patterns. In languages such as Java, a factory class hierarchy is required because the language doesn't support standalone functions.

In Python, a class isn'trequired. It's merely a good idea when there are related factories that are complex. One of the strengths of Python is that we're not forced to use a class hierarchy when a simple function might do just as well.

Note

While this is a book about object-oriented programming, a function really is fine. It's common, idiomatic Python.

We can always rewrite a function to be a proper callable object if the need arises. From a callable object, we can refactor it into a class hierarchy for our factories. We'll look at callable objects inChapter 5,Using Callables and Contexts.

The advantage of class definitions in general is to achieve code reuse via inheritance. The function of a factory class is to wrap some target class hierarchy and the complexities of object construction. If we have a factory class, we can add subclasses to the factory class when extending the target class hierarchy. This gives us polymorphic factory classes; the different factory class definitions have the same method signatures and can be used interchangeably.

This class-level polymorphism can be very helpful with statically compiled languages such as Java or C++. The compiler can resolve the details of the class and methods when generating code.

If the alternative factory definitions don't actually reuse any code, then a class hierarchy won't be helpful in Python. We can simply use functions that have the same signatures.

The following is a factory function for our variousCard subclasses:

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" )

This function builds aCard class from a numericrank number and asuit object. We can now build cards more simply. We've encapsulated the construction issues into a single factory function, allowing an application to be built without knowing precisely how the class hierarchy and polymorphic design works.

The following is an example of how we can build a deck with this factory function:

deck = [card(rank, suit)    for rank in range(1,14)        for suit in (Club, Diamond, Heart, Spade)]

This enumerates all the ranks and suits to create a complete deck of 52 cards.

Faulty factory design and the vague else clause

Note the structure of theif statement in thecard() function. We did not use a catch-allelse clause to do any processing; we merely raised an exception. The use of a catch-allelse clause is subject to a tiny scrap of debate.

On the one hand, it can be argued that the condition that belongs on anelse clause should never be left unstated because it may hide subtle design errors. On the other hand, someelse clause conditions are truly obvious.

It's important to avoid the vagueelse clause.

Consider the following variant on this factory function definition:

def card2( rank, suit ):    if rank == 1: return AceCard( 'A', suit )    elif 2 <= rank < 11: return NumberCard( str(rank), suit )    else:        name = { 11: 'J', 12: 'Q', 13: 'K' }[rank]        return FaceCard( name, suit )

The following is what will happen when we try to build a deck:

deck2 = [card2(rank, suit) for rank in range(13) for suit in (Club, Diamond, Heart, Spade)]

Does it work? What if theif conditions were more complex?

Someprogrammers can understand thisif statement at aglance. Others will struggle to determine if all of the cases are properly exclusive.

For advanced Python programming, we should not leave it to the reader to deduce the conditions that apply to anelse clause. Either the condition should be obvious to the newest of n00bz, or it should be explicit.

Tip

When to use catch-all else

Rarely. Use it only when the condition is obvious. When in doubt, be explicit and useelse to raise an exception.

Avoid the vagueelse clause.

Simplicity and consistency using elif sequences

Our factory function,card(), is a mixture of two very common factory design patterns:


  • Anif-elif sequence

  • A mapping

For thesake of simplicity, it's better to focus on just one of these techniques rather than on both.

We can always replace a mapping withelif conditions. (Yes, always. The reverse is not true though; transformingelif conditions to a mapping can be challenging.)

The following is aCard factory without the mapping:

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" )

We rewrote thecard() factory function. The mapping was transformed into additionalelif clauses. Thisfunction has the advantage that it is more consistent than the previous version.

Simplicity using mapping and class objects

In some cases, we can use a mapping instead of a chain ofelif conditions. It's possible to findconditions that are so complex that a chain ofelif conditions is the only sensible way to express them. For simple cases, however, a mappingoften works better and can be easy to read.

Sinceclass is a first-class object, we can easily map from therank parameter to the class that must be constructed.

The following is aCard factory that uses only a mapping:

def card4( rank, suit ):    class_= {1: AceCard, 11: FaceCard, 12: FaceCard,        13: FaceCard}.get(rank, NumberCard)    return class_( rank, suit )

We've mapped therank object to a class. Then, we applied the class to therank andsuit values to build the finalCard instance.

We can use adefaultdict class as well. However, it's no simpler for a trivial static mapping. It looks like the following code snippet:

defaultdict( lambda: NumberCard, {1: AceCard, 11: FaceCard, 12: FaceCard, 12: FaceCard} )

Note that thedefault of adefaultdict class must be a function of zero arguments. We've used alambda construct to create the necessary function wrapper around a constant. This function, however, has a serious deficiency. It lacks the translation from1 toA and13 toK that we had in previous versions. When we try to add that feature, we run into a problem.

We need to change the mapping to provide both aCard subclass as well as the string version of therank object. What can we do for this two-part mapping? There are four common solutions:

  • We can do two parallel mappings. We don't suggest this, but we'll show it to emphasize what's undesirable about it.

  • We can map to a two-tuple. This also has some disadvantages.

  • We can map to apartial() function. Thepartial() function is a feature of thefunctools module.

  • We can also consider modifying our class definition to fit more readily with this kind of mapping. We'll look at this alternative in the next section on pushing__init__() into the subclass definitions.

We'lllookat each of these with a concrete example.

Two parallel mappings

The followingis the essence of the two parallel mappings solution:

class_= {1: AceCard, 11: FaceCard, 12: FaceCard, 13: FaceCard }.get(rank, NumberCard)rank_str= {1:'A', 11:'J', 12:'Q', 13:'K'}.get(rank,str(rank))return class_( rank_str, suit )

This is not desirable. It involves a repetition of the sequence of the mapping keys1,11,12, and13. Repetition is bad because parallel structures never seem to stay that way after the software has been updated.

Tip

Don't use parallel structures

Two parallel structures should be replaced with tuples or some kind of proper collection.

Mapping to a tuple of values

The following is the essence of how mapping is done to a two-tuple:

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 )

This is reasonably pleasant. It's not much code to sort out the special cases of playing cards. We will see how it could be modified or expanded if we need to alter theCard class hierarchy to add additional subclasses ofCard.

It does feel odd to map arank value to aclass object and just one of the two arguments to that class initializer. It seems more sensible to map the rank to a simple class or function object without the clutter of providing some (but not all) of the arguments.

The partial function solution

Rather than map to a two-tuple of function and one of the arguments, we can create apartial() function. This is a function that already has some (but not all) of its arguments provided. We'll use thepartial() functionfrom thefunctools library to create a partial of a class with therank argument.

The following is a mapping fromrank to apartial() function that can be used for object construction:

from functools import partialpart_class= {    1:  partial(AceCard,'A'),    11: partial(FaceCard,'J'),    12: partial(FaceCard,'Q'),    13: partial(FaceCard,'K'),    }.get(rank, partial(NumberCard, str(rank)))return part_class( suit )

The mapping associates arank object with apartial() function that is assigned topart_class. Thispartial() function can then be applied to thesuit object to create the final object. The use ofpartial() functions is a common technique for functional programming. It works in this specific situation where we have a function instead of an object method.

In general, however,partial() functions aren't helpful for most object-oriented programming. Rather than createpartial() functions, we can simply update the methods of a class to accept the arguments in different combinations. Apartial() function is similar to creating a fluent interface for object construction.

Fluent APIs for factories

In some cases, we design a class where there's a defined order for method usage. Evaluating methods sequentially is very much like creating apartial() function.

We might havex.a().b() in an object notation. We can think of it as. Thex.a() function is a kind ofpartial() function that's waiting forb(). We can think of this as if it were.

The idea here is that Python offers us two alternatives for managing a state. We can either update an object or create apartial() function that is (in a way) stateful. Because of this equivalence, we can rewrite apartial() function into a fluent factory object. We make the setting of therank object a fluent method that returnsself. Setting thesuit object will actually create theCard instance.

The following is a fluentCard factory class with two method functions that must be used in a specific order:

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 )

Therank() method updates the state of the constructor, and thesuit() method actually creates the finalCard object.

This factory class can be used as follows:

card8 = CardFactory()deck8 = [card8.rank(r+1).suit(s) for r in range(13) for s in (Club, Diamond, Heart, Spade)]

First, we create a factory instance, then we use that instance to createCard instances. This doesn't materially change how__init__() itself works in theCard class hierarchy. It does, however, change the way that our client application creates objects.

Implementing __init__() in each subclass


As we look at the factory functions for creatingCard objects, we see some alternative designs for theCard class. We might want to refactor the conversion of the rank number so that it is the responsibility of theCard class itself. This pushes the initialization down into each subclass.

This often requires some common initialization of a superclass as well as subclass-specific initialization. We need to follow theDon't Repeat Yourself (DRY) principle to keep thecode from getting cloned into each of the subclasses.

The following is an example where the initialization is the responsibility of each subclass:

class Card:    passclass NumberCard( Card ):    def  __init__( self, rank, suit ):        self.suit= suit        self.rank= str(rank)        self.hard = self.soft = rankclass AceCard( Card ):    def  __init__( self, rank, suit ):        self.suit= suit        self.rank= "A"        self.hard, self.soft =  1, 11class FaceCard( Card ):    def  __init__( self, rank, suit ):        self.suit= suit        self.rank= {11: 'J', 12: 'Q', 13: 'K' }[rank]        self.hard = self.soft = 10

This isstill clearly polymorphic. Thelack of a truly common initialization, however, leads to some unpleasant redundancy. What's unpleasant here is the repeated initialization ofsuit. This must bepulled up into the superclass. We can have each__init__() subclass make an explicit reference to the superclass.

This version of theCard class has an initializer at the superclass level that is used by each subclass, as shown in the following code snippet:

class Card:    def __init__( self, rank, suit, hard, soft ):        self.rank= rank        self.suit= suit        self.hard= hard        self.soft= softclass 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 )

We've provided__init__() at both the subclass and superclass level. This has the small advantage that it simplifies our factory function, as shown in the following code snippet:

def card10( rank, suit ):    if rank == 1: return AceCard( rank, suit )    elif 2 <= rank < 11: return NumberCard( rank, suit )    elif 11 <= rank < 14: return FaceCard( rank, suit )    else:        raise Exception( "Rank out of range" )

Simplifying a factory function should not be our focus. We can see from this variation thatwe've created rather complex__init__() methods for a relatively minor improvement in a factory function. This is a commontrade-off.

Tip

Factory functions encapsulate complexity

There's a trade-off that occurs between sophisticated__init__() methods and factory functions. It's often better to stick with more direct but less programmer-friendly__init__() methods and push the complexity into factory functions. A factory function works well if you wish to wrap and encapsulate the construction complexities.

Simple composite objects


A composite object can also be called acontainer. We'll look at a simple composite object: a deck of individual cards. This is a basic collection. Indeed, it's so basic that we can, without too much struggle, use a simplelist as a deck.

Before designing a new class, we need to ask this question: is using a simplelist appropriate?

We can userandom.shuffle() to shuffle the deck anddeck.pop() to deal cards into a player'sHand.

Some programmers rush to define new classes as if using a built-in class violates some object-oriented design principle. Avoiding a new class leaves us with something as shown in the following code snippet:

d= [card6(r+1,s) for r in range(13) for s in (Club, Diamond, Heart, Spade)]random.shuffle(d)hand= [ d.pop(), d.pop() ]

If it's that simple, why write a new class?

The answer isn't perfectly clear. One advantage is that a class offer a simplified, implementation-free interface to the object. As we noted previously, when discussing factories, a class isn't a requirement in Python.

In the preceding code, the deck only has two simple use cases and a class definition doesn't seem to simplify things very much. It does have the advantage of concealing the implementation's details. But the details are so trivial that exposing them seems to have little cost. We're focused primarily on the__init__() method in this chapter, so we'll look at somedesigns to create and initialize a collection.

To design a collection of objects, we have the following three general design strategies:

  • Wrap: This design pattern is an existing collection definition. This might be an example of theFacade design pattern.

  • Extend: This design pattern is an existing collection class. This is ordinary subclass definition.

  • Invent: This is designed from scratch. We'll look at this inChapter 6,Creating Containers and Collections.

These three concepts are central to object-oriented design. We must always make this choice when designing a class.

Wrapping a collection class

Thefollowing is a wrapper design thatcontains an internal collection:

class Deck:    def __init__( self ):        self._cards = [card6(r+1,s) for r in range(13) for s in (Club, Diamond, Heart, Spade)]        random.shuffle( self._cards )    def pop( self ):        return self._cards.pop()

We've definedDeck so that the internal collection is alist object. Thepop() method ofDeck simply delegates to the wrappedlist object.

We can then create aHand instance with the following kind of code:

d= Deck()hand= [ d.pop(), d.pop() ]

Generally, a Facade design pattern or wrapper class contains methods that are simply delegated to the underlying implementation class. This delegation can become wordy. For a sophisticated collection, we may wind up delegating a large number of methods to the wrapped object.

Extending a collection class

An alternative to wrapping is to extend a built-in class. By doing this, we have the advantage of not having to reimplement thepop() method; we can simply inherit it.

Thepop() method has the advantage that it creates a class without writing too much code. Inthis example, extending thelist class has the disadvantage that this provides many more functions than we truly need.

The following is a definition ofDeck that extends the built-inlist:

class Deck2( list ):    def __init__( self ):        super().__init__( card6(r+1,s) for r in range(13) for s in (Club, Diamond, Heart, Spade) )        random.shuffle( self )

In some cases, our methods will have to explicitly use the superclass methods in order to have proper class behavior. We'll see other examples of this in the following sections.

We leverage the superclass's__init__() method to populate ourlist object with an initial single deck of cards. Then we shuffle the cards. Thepop() method is simply inherited fromlist and works perfectly. Other methods inherited from thelist also work.

More requirements and another design

In a casino, the cards are often dealt from a shoe that has half a dozen decks of cards all mingled together. This considerationmakes it necessary for us to build our own version ofDeck and not simply use an unadornedlist object.

Additionally, a casino shoe is not dealt fully. Instead, a marker card is inserted. Because of the marker, some cards are effectively set aside and not used for play.

The following isDeck definition that contains multiple sets of 52-card decks:

class Deck3(list):    def __init__(self, decks=1):        super().__init__()        for i in range(decks):            self.extend( card6(r+1,s) for r in range(13) for s in (Club, Diamond, Heart, Spade) )        random.shuffle( self )        burn= random.randint(1,52)        for i in range(burn): self.pop()

Here, we used the__init__() superclass to build an empty collection. Then, we usedself.extend() to append multiple 52-card decks to the shoe. We could also usesuper().extend() since we did not provide an overriding implementation in this class.

Wecould also carry out the entire taskviasuper().__init__() using a more deeply nested generator expression, as shown in the following code snippet:

( card6(r+1,s) for r in range(13) for s in (Club, Diamond, Heart, Spade) for d in range(decks) )

This class provides us with a collection ofCard instances that we can use to emulate casino blackjack as dealt from a shoe.

There's a peculiar ritual in a casino where they reveal the burned card. If we're going to design a card-counting player strategy, we might want to emulate this nuance too.

Complex composite objects


The following is an example of a blackjackHand description that might be suitable for emulatingplay strategies:

class Hand:    def __init__( self, dealer_card ):        self.dealer_card= dealer_card        self.cards= []    def hard_total(self ):        return sum(c.hard for c in self.cards)    def soft_total(self ):        return sum(c.soft for c in self.cards)

In this example, we have an instance variableself.dealer_card based on a parameter of the__init__() method. Theself.cards instance variable, however, is not based on any parameter. This kind of initialization creates an empty collection.

To create an instance ofHand, we can use the following code:

d = Deck()h = Hand( d.pop() )h.cards.append( d.pop() )h.cards.append( d.pop() )

This has the disadvantage that a long-winded sequence of statements is used to build an instance of aHand object. It can become difficult to serialize theHand object and rebuild it with an initialization such as this one. Even if we were to create an explicitappend() method in this class, it would still take multiple steps to initialize the collection.

We could try to create a fluent interface, but that doesn't really simplify things; it's merely a change in the syntax of the way that aHand object is built. A fluent interface still leads to multiple method evaluations. When we take a look at the serialization of objects inPart 2,Persistence and Serialization we'dlike an interface that's a single class-level function, ideally the class constructor. We'll look at this in depth inChapter 9,Serializing and Saving - JSON, YAML, Pickle, CSV, and XML.

Note also that the hard total and soft total method functions shown here don't fully follow the rules of blackjack. We return to this issue inChapter 2,Integrating Seamlessly with Python – Basic Special Methods.

Complete composite object initialization

Ideally, the__init__() initializer method will create a complete instance of an object. This is a bitmore complex when creating a complete instance of a container that contains an internal collection of other objects. It'll be helpful if we can build this composite in a single step.

It's common to have both a method to incrementally accrete items as well as the initializer special method that can load all of the items in one step.

For example, we might have a class such as the following code snippet:

class Hand2:    def __init__( self, dealer_card, *cards ):        self.dealer_card= dealer_card        self.cards = list(cards)    def hard_total(self ):        return sum(c.hard for c in self.cards)    def soft_total(self ):        return sum(c.soft for c in self.cards)

This initialization sets all of the instance variables in a single step. The other methods are simply copies of the previous class definition. We can build aHand2 object in two ways. This first example loads one card at a time into aHand2 object:

d = Deck()P = Hand2( d.pop() )p.cards.append( d.pop() )p.cards.append( d.pop() )

This second example uses the*cards parameter to load a sequence ofCards class in a single step:

d = Deck()h = Hand2( d.pop(), d.pop(), d.pop() )

For unit testing, it's often helpful to build a composite object in a single statement in this way. More importantly, some of the serialization techniques from the next part will benefit from a way of building a composite object in a single, simple evaluation.

Stateless objects without __init__()


The following is an example of a degenerate class that doesn't need an__init__() method. It's a common design pattern forStrategy objects. A Strategy object is plugged into a Master object to implement an algorithm or decision. It may rely on data in the master object; the Strategy object may nothave any data of its own. We often design strategy classes to follow theFlyweight design pattern: we avoid internal storage in theStrategy object. All values are provided toStrategy as method argument values. TheStrategy object itself can be stateless. It's more a collection of method functions than anything else.

In this case, we're providing the game play decisions for aPlayer instance. The following is an example of a (dumb) strategy to pick cards and decline the other bets:

class GameStrategy:    def insurance( self, hand ):        return False    def split( self, hand ):        return False    def double( self, hand ):        return False    def hit( self, hand ):        return sum(c.hard for c in hand.cards) <= 17

Each method requires the currentHand as an argument value. The decisions are based on the available information; that is, on the dealer's cards and the player's cards.

We can build a single instance of this strategy for use by variousPlayer instances as shown in the following code snippet:

dumb = GameStrategy()

We can imagine creating a family of related strategy classes, each one using different rules for the various decisions a player is offered in blackjack.

Some additional class definitions


As notedpreviously, a player has two strategies: one for betting and one for playing their hand. EachPlayer instance has a sequence of interactions with a larger simulation engine. We'll call the larger engine theTable class.

TheTable class requires the following sequence of events by thePlayer instances:

  • The player must place an initial bet based on the betting strategy.

  • The player will then receive a hand.

  • If the hand is splittable, the player must decide to split or not based on the play strategy. This can create additionalHand instances. In some casinos, the additional hands are also splittable.

  • For eachHand instance, the player must decide to hit, double, or stand based on the play strategy.

  • The player will then receive payouts, and they must update their betting strategy based on their wins and losses.

From this, we can see that theTable class has a number of API methods to receive a bet, create aHand object, offer a split, resolve each hand, and pay off the bets. This is a large object that tracks the state of play with a collection ofPlayers.

The following is the beginning of aTable class that handles the bets and cards:

class Table:    def __init__( self ):        self.deck = Deck()    def place_bet( self, amount ):        print( "Bet", amount )    def get_hand( self ):        try:            self.hand= Hand2( d.pop(), d.pop(), d.pop() )            self.hole_card= d.pop()        except IndexError:            # Out of cards: need to shuffle.            self.deck= Deck()            return self.get_hand()        print( "Deal", self.hand )        return self.hand    def can_insure( self, hand ):        return hand.dealer_card.insure

TheTable class is used by thePlayer class to accept a bet, create aHand object, and determine if theinsurance bet is in play for this hand. Additional methods can be used by thePlayer class to get cards and determine the payout.

The exception handling shown inget_hand() is not a precise model of casino play. This may lead to minor statistical inaccuracies. A more accurate simulation requires developing a deck that reshuffles itself when empty instead of raising an exception.

In order to interact properly and simulate realistic play, thePlayer class needs a betting strategy. The betting strategy is a stateful object that determines the level of the initial bet. The various betting strategies generally change the bet based on whether the game was a win or a loss.

Ideally, we'd like to have a family of betting strategy objects. Python has a module with decorators that allows us to create an abstract superclass. An informal approach to creating Strategy objects is to raise an exception for methods thatmust be implemented by a subclass.

We'vedefined an abstract superclass as well as a specific subclass as follows to define a flat betting strategy:

class BettingStrategy:    def bet( self ):        raise NotImplementedError( "No bet method" )    def record_win( self ):        pass    def record_loss( self ):        passclass Flat(BettingStrategy):    def bet( self ):        return 1

The superclass defines the methods with handy default values. The basicbet() method in the abstract superclass raises an exception. The subclass must override thebet() method. The other methods can be left to provide the default values. Given the game strategy in the previous section plus the betting strategy here, we can look at more complex__init__() techniques surrounding thePlayer class.

We can make use of theabc module to formalize an abstract superclass definition. It would look like the following code snippet:

import abcclass BettingStrategy2(metaclass=abc.ABCMeta):    @abstractmethod    def bet( self ):        return 1    def record_win( self ):        pass    def record_loss( self ):        pass

This has the advantage that it makes the creation of an instance ofBettingStrategy2, or any subclass that failed to implementbet(), impossible. If we try to create an instance of this class with an unimplemented abstract method, it will raise an exception instead of creating an object.

And yes, the abstract method has an implementation. It can be accessed viasuper().bet().

Multi-strategy __init__()


We may have objects that are created from a variety of sources. For example, we might need to clone an object as part of creating a memento, or freeze an object so that it can be used as the key of a dictionary or placed into a set; this is the idea behind theset andfrozenset built-in classes.

There are several overall design patterns that have multiple ways to build an object. One design pattern is complex__init__() that is called multi-strategy initialization. Also, there are multiple class-level (static) constructor methods.

These are incompatible approaches. They have radically different interfaces.

Tip

Avoid clone methods

A clone method that unnecessarily duplicates an object is rarely needed in Python. Using cloning may be an indication of failure to understand the object-oriented design principles available in Python.

A clone method encapsulates the knowledge of object creation in the wrong place. The source object that's being cloned cannot know about the structure of the target object that was built from the clone. However, the reverse (targets having knowledge about a source) is acceptable if the source provides a reasonably well-encapsulated interface.

The examples we have shown here are effectively cloning because they're so simple. We'll expand on them in the next chapter. However, to show ways in which these fundamental techniques are used to do more than trivial cloning, we'll look at turning a mutableHand object into a frozen, immutableHand object.

The following is an example of aHand object that can be built in either of the two ways:

class Hand3:    def __init__( self, *args, **kw ):        if len(args) == 1 and isinstance(args[0],Hand3):            # Clone an existing hand; often a bad idea            other= args[0]            self.dealer_card= other.dealer_card            self.cards= other.cards        else:            # Build a fresh, new hand.            dealer_card, *cards = args            self.dealer_card=  dealer_card            self.cards= list(cards)

In the first case, aHand3 instance has been built from an existingHand3 object. In the second case, aHand3 object has been built from individualCard instances.

Thisparallels the way afrozenset object can be built from individual items or an existingsetobject. We look more at creating immutable objects in the next chapter. Creating a newHand from an existingHand allows us to create a memento of aHand object using a construct like the following code snippet:

h = Hand( deck.pop(), deck.pop(), deck.pop() )memento= Hand( h )

We saved theHand object in thememento variable. This can be used to compare the final with the original hand that was dealt, or we canfreeze it for use in a set or mapping too.

More complex initialization alternatives

In order towrite a multi-strategy initialization, we're often forced to give up on specific named parameters. This design has the advantage that it is flexible, but the disadvantage that it has opaque, meaningless parameter names. It requires a great deal of documentation explaining the variant use cases.

We can expand our initialization to also split aHand object. The result of splitting aHand object is simply another constructor. The following code snippet shows how the splitting of aHand object might look:

class Hand4:    def __init__( self, *args, **kw ):        if len(args) == 1 and isinstance(args[0],Hand4):            # Clone an existing handl often a bad idea            other= args[0]            self.dealer_card= other.dealer_card            self.cards= other.cards        elif len(args) == 2 and isinstance(args[0],Hand4) and 'split' in kw:            # Split an existing hand            other, card= args            self.dealer_card= other.dealer_card            self.cards= [other.cards[kw['split']], card]        elif len(args) == 3:            # Build a fresh, new hand.            dealer_card, *cards = args            self.dealer_card=  dealer_card            self.cards= list(cards)        else:            raise TypeError( "Invalid constructor args={0!r} kw={1!r}".format(args, kw) )    def __str__( self ):        return ", ".join( map(str, self.cards) )

This design involves getting extra cards to build proper, split hands. When we create oneHand4 object from anotherHand4 object, we provide a split keyword argument that uses the index of theCard class from the originalHand4 object.

The following code snippet shows how we'd use this to split a hand:

d = Deck()h = Hand4( d.pop(), d.pop(), d.pop() )s1 = Hand4( h, d.pop(), split=0 )s2 = Hand4( h, d.pop(), split=1 )

We created an initialh instance ofHand4 and split it into two otherHand4 instances,s1 ands2, and dealt an additionalCard class into each. The rules of blackjack only allow this when the initial hand has two cards of equal rank.

While this__init__() method is rather complex, it has the advantage that it can parallel the way in whichfronzenset is created from an existing set. The disadvantage is that it needs a large docstring to explain all these variations.

Initializing static methods

When wehave multiple ways to create an object, it's sometimes more clear to use static methods to create and return instances rather than complex__init__() methods.

It's also possible to use class methods as alternate initializers, but there's little tangible advantage to receiving the class as an argument to the method. In the case of freezing or splitting aHand object, we might want to create two new static methods to freeze or split aHand object. Using static methods as surrogate constructors is a tiny syntax change in construction, but it has huge advantages when organizing the code.

The following is a version ofHand with static methods that can be used to build new instances ofHand from an existingHand instance:

class Hand5:    def __init__( self, dealer_card, *cards ):        self.dealer_card= dealer_card        self.cards = list(cards)    @staticmethod    def freeze( other ):        hand= Hand5( other.dealer_card, *other.cards )        return hand    @staticmethod    def split( other, card0, card1 ):        hand0= Hand5( other.dealer_card, other.cards[0], card0 )        hand1= Hand5( other.dealer_card, other.cards[1], card1 )        return hand0, hand1    def __str__( self ):        return ", ".join( map(str, self.cards) )

Onemethod freezes or creates amemento version. The other method splits aHand5 instance to create two new child instances ofHand5.

This is considerably more readable and preserves the use of the parameter names to explain the interface.

The following code snippet shows how we can split aHand5 instance with this version of the class:

d = Deck()h = Hand5( d.pop(), d.pop(), d.pop() )s1, s2 = Hand5.split( h, d.pop(), d.pop() )

We created an initialh instance ofHand5, split it into two other hands,s1 ands2, and dealt an additionalCard class into each. Thesplit() static method is much simpler than the equivalent functionality implemented via__init__(). However, it doesn't follow the pattern of creating afronzenset object from an existingsetobject.

Yet more __init__() techniques


We'll take a look at a few other, more advanced__init__() techniques. These aren't quite souniversally useful as the techniques in the previous sections.

The following is a definition for thePlayer class that uses two strategy objects and atable object. This shows an unpleasant-looking__init__() method:

class Player:    def __init__( self, table, bet_strategy, game_strategy ):        self.bet_strategy = bet_strategy        self.game_strategy = game_strategy        self.table= table    def game( self ):        self.table.place_bet( self.bet_strategy.bet() )        self.hand= self.table.get_hand()        if self.table.can_insure( self.hand ):            if self.game_strategy.insurance( self.hand ):                self.table.insure( self.bet_strategy.bet() )        # Yet more... Elided for now

The__init__() method forPlayer seems to do little more than bookkeeping. We're simply transferring named parameters to same-named instance variables. If we have numerous parameters, simply transferring the parameters into the internal variables will amount to a lot of redundant-looking code.

We canuse thisPlayer class (and related objects) as follows:

table = Table()flat_bet = Flat()dumb = GameStrategy()p = Player( table, flat_bet, dumb )p.game()

We can provide a very short and very flexible initialization by simply transferring keyword argument values directly into the internal instance variables.

The following is a way to build aPlayer class using keyword argument values:

class Player2:    def __init__( self, **kw ):        """Must provide table, bet_strategy, game_strategy."""        self.__dict__.update( kw )    def game( self ):        self.table.place_bet( self.bet_strategy.bet() )        self.hand= self.table.get_hand()        if self.table.can_insure( self.hand ):            if self.game_strategy.insurance( self.hand ):                self.table.insure( self.bet_strategy.bet() )        # etc.

This sacrifices a great deal of readability for succinctness. It crosses over into a realm of potential obscurity.

Since the__init__() method is reduced to one line, it removes a certain level of "wordiness" from the method. This wordiness, however, is transferred to each individual object constructor expression. We have to add the keywords to the object initialization expression since we're no longer using positional parameters, as shown in the following code snippet:

p2 = Player2( table=table, bet_strategy=flat_bet, game_strategy=dumb )

Why do this?

It does have apotential advantage. A class defined like this is quite open to extension. We can, with only a few specific worries, supply additional keyword parameters to a constructor.

The following is the expected use case:

>>> p1= Player2( table=table, bet_strategy=flat_bet, game_strategy=dumb)>>> p1.game()

The following is a bonus use case:

>>> p2= Player2( table=table, bet_strategy=flat_bet, game_strategy=dumb, log_name="Flat/Dumb" )>>> p2.game()

We'veadded alog_name attribute without touching the class definition. This can be used, perhaps, as part of a larger statistical analysis. ThePlayer2.log_name attribute can be used to annotate logs or other collected data.

We are limited in what we can add; we can only add parameters that fail to conflict with the names already in use within the class. Some knowledge of the class implementation is required to create a subclass that doesn't abuse the set of keywords already in use. Since the**kw parameter provides little information, we need to read carefully. In most cases, we'd rather trust the class to work than review the implementation details.

This kind of keyword-based initialization can be done in a superclass definition to make it slightly simpler for the superclass to implement subclasses. We can avoiding writing an additional__init__() method in each subclass when the unique feature of the subclass involves simple new instance variables.

The disadvantage of this is that we have obscure instance variables that aren't formally documented via a subclass definition. If it's only one small variable, an entire subclass might be too much programming overhead to add a single variable to a class. However, one small variable often leads to a second and a third. Before long, we'll realize that a subclass would have been smarter than an extremely flexible superclass.

We can (and should) hybridize this with a mixed positional and keyword implementation as shown in the following code snippet:

class Player3( Player ):    def __init__( self, table, bet_strategy, game_strategy, **extras ):        self.bet_strategy = bet_strategy        self.game_strategy = game_strategy        self.table= table        self.__dict__.update( extras )

This is more sensible than a completely open definition. We've made the required parameters positional parameters. We've left any nonrequired parameters as keywords. This clarifies the use of any extra keyword arguments given to the__init__() method.

This kind of flexible, keyword-based initialization depends on whether we have relatively transparent class definitions. This openness to change requires some care to avoid debuggingname clashes because the keyword parameter names are open-ended.

Initialization with type validation

Type validation is rarely a sensible requirement. In a way, this might be a failure to fully understand Python. The notionalobjective is to validate that all of the arguments are of aproper type. The issue with trying to do this is that the definition ofproper is often far too narrow to be truly useful.

This is different from validating that objects meet other criteria. Numeric range checking, for example, may be essential to prevent infinite loops.

What can create problems is trying to do something like the following in an__init__() method:

class ValidPlayer:    def __init__( self, table, bet_strategy, game_strategy ):        assert isinstance( table, Table )        assert isinstance( bet_strategy, BettingStrategy )        assert isinstance( game_strategy, GameStrategy )        self.bet_strategy = bet_strategy        self.game_strategy = game_strategy        self.table= table

Theisinstance() method checks circumvent Python's normalduck typing.

Wewrite a casino game simulation in order to experiment with endless variations onGameStrategy. These are so simple (merely four methods) that there's little real benefit from inheritance from the superclass. We could define the classes independently, lacking an overall superclass.

The initialization error-checking shown in this example would force us to create subclasses merely to pass the error check. No usable code is inherited from the abstract superclass.

One of the biggest duck typing issues surrounds numeric types. Different numeric types will work in different contexts. Attempts to validate the types of arguments may prevent a perfectly sensible numeric type from working properly. When attempting validation, we have the following two choices in Python:

  • We write validation so that a relatively narrow collection of types is permitted, and someday the code will break because a new type that would have worked sensibly is prohibited

  • We eschew validation so that a broad collection of types is permitted, and someday the code will break because a type that would not work sensibly was used

Notethat both are essentiallythe same. The code could perhaps break someday. It either breaks because a type was prevented from being used even though it's sensible or a type that's not really sensible was used.

Tip

Just allow it

Generally, it's considered better Python style to simply permit any type of data to be used.

We'll return to this inChapter 4,The ABCs of Consistent Design.

The question is this: why restrict potential future use cases?

And the usual answer is that there's no good reason to restrict potential future use cases.

Rather than prevent a sensible, but possibly unforeseen, use case, we can provide documentation, testing, and debug logging to help other programmers understand any restrictions on the types that can be processed. We have to provide the documentation, logging, and test cases anyway, so there's minimal additional work involved.

The following is an example docstring that provides the expectations of the class:

class Player:    def __init__( self, table, bet_strategy, game_strategy ):        """Creates a new player associated with a table, and configured with proper betting and play strategies        :param table: an instance of :class:`Table`        :param bet_strategy: an instance of :class:`BettingStrategy`        :param  game_strategy: an instance of :class:`GameStrategy`        """        self.bet_strategy = bet_strategy        self.game_strategy = game_strategy        self.table= table

The programmer using this class has been warned about what the type restrictions are. The use of other types is permitted. If the type isn't compatible with the expected type, then things will break. Ideally, we'll use too likeunittest ordoctest to uncover the breakage.

Initialization, encapsulation, and privacy

The general Python policy regarding privacy can be summed up as follows:we're all adults here.

Object-oriented design makes an explicit distinction between interface and implementation. This is a consequence of the idea of encapsulation. A class encapsulates a data structure, an algorithm, an external interface, or something meaningful. The idea is to have the capsule separate the class-based interface from the implementation details.

However, noprogramming language reflects every design nuance. Python, typically, doesn't implement all design considerations as explicit code.

One aspect of a class design that is not fully carried into code is the distinction between theprivate (implementation) andpublic (interface) methods or attributes of an object. The notion of privacy in languages that support it (C++ or Java are two examples) is already quite complex. These languages include settings such as private, protected, and public as well as "not specified", which is a kind of semiprivate. The private keyword is often used incorrectly, making subclass definition needlessly difficult.

Python's notion of privacy is simple, as follows:

  • It's allessentially public. The source code is available. We're all adults. Nothing can be truly hidden.

  • Conventionally, we'll treat some names in a way that's less public. They're generally implementation details that are subject to change without notice, but there's no formal notion of private.

Names that begin with_ are honored as less public by some parts of Python. Thehelp() function generally ignores these methods. Tools such as Sphinx can conceal these names from documentation.

Python's internal names begin (and end) with__. This is how Python internals are kept from colliding with application features above the internals. The collection of these internal names is fully defined by the language reference. Further, there's no benefit to trying to use__ to attempt to create a "super private" attribute or method in our code. All that happens is that we create a potential future problem if a release of Python ever starts using a name we chose for internal purposes. Also, we're likely to run afoul of the internal name mangling that is applied to these names.

The rules for the visibility of Python names are as follows:

  • Most names are public.

  • Names that start with_ are somewhat less public. Use them for implementation details that are truly subject to change.

  • Names that begin and end with__ are internal to Python. We never make these up; we use the names defined by the language reference.

Generally, the Python approach is to register the intent of a method (or attribute) using documentationand a well-chosen name. Often, the interface methods will haveelaborate documentation, possibly includingdoctest examples, while the implementation methods will have more abbreviated documentation and may not havedoctest examples.

For programmers new to Python, it's sometimes surprising that privacy is not more widely used. For programmers experienced in Python, it's surprising how many brain calories get burned sorting out private and public declarations that aren't really very helpful because the intent is obvious from the method names and the documentation.

Summary


In this chapter, we have reviewed the various design alternatives of the__init__() method. In the next chapter, we will take a look at the special methods, along with a few advanced ones as well.

Left arrow icon

Page1 of 14

Right arrow icon
Download code iconDownload Code

What you will learn

  • Create applications with flexible logging, powerful configuration and commandline options, automated unit tests, and good documentation
  • Get to grips with different design patterns for the __init__() method
  • Design callable objects and context managers
  • Perform object serialization in formats such as JSON, YAML, Pickle, CSV, and XML
  • Map Python objects to a SQL database using the builtin SQLite module
  • Transmit Python objects via RESTful web services
  • Devise strategies for automated unit testing, including how to use the doctest and the unittest.mock module
  • Parse commandline arguments and integrate this with configuration files and environment variables
Estimated delivery feeDeliver to Italy

Premium delivery7 - 10 business days

€17.95
(Includes tracking information)

Product Details

Country selected
Publication date, Length, Edition, Language, ISBN-13
Publication date :Apr 22, 2014
Length:634 pages
Edition :
Language :English
ISBN-13 :9781783280971
Category :
Languages :

What do you get with Print?

Product feature iconInstant access to your digital copy whilst your Print order is Shipped
Product feature icon Paperback book shipped to your preferred address
Product feature icon Redeem a companion digital copy on all Print orders
Product feature icon Access this title in our online reader with advanced features
Product feature iconDRM FREE - Read whenever, wherever and however you want
OR

Contact Details

Modal Close icon
Payment Processing...
tickCompleted

Shipping Address

Billing Address

Shipping Methods
Estimated delivery feeDeliver to Italy

Premium delivery7 - 10 business days

€17.95
(Includes tracking information)

Product Details

Publication date :Apr 22, 2014
Length:634 pages
Edition :
Language :English
ISBN-13 :9781783280971
Category :
Languages :
Concepts :

Packt Subscriptions

See our plans and pricing
Modal Close icon
€18.99billed monthly
Feature tick iconUnlimited access to Packt's library of 7,000+ practical books and videos
Feature tick iconConstantly refreshed with 50+ new titles a month
Feature tick iconExclusive Early access to books as they're written
Feature tick iconSolve problems while you work with advanced search and reference features
Feature tick iconOffline reading on the mobile app
Feature tick iconSimple pricing, no contract
€189.99billed annually
Feature tick iconUnlimited access to Packt's library of 7,000+ practical books and videos
Feature tick iconConstantly refreshed with 50+ new titles a month
Feature tick iconExclusive Early access to books as they're written
Feature tick iconSolve problems while you work with advanced search and reference features
Feature tick iconOffline reading on the mobile app
Feature tick iconChoose a DRM-free eBook or Video every month to keep
Feature tick iconPLUS own as many other DRM-free eBooks or Videos as you like for just €5 each
Feature tick iconExclusive print discounts
€264.99billed in 18 months
Feature tick iconUnlimited access to Packt's library of 7,000+ practical books and videos
Feature tick iconConstantly refreshed with 50+ new titles a month
Feature tick iconExclusive Early access to books as they're written
Feature tick iconSolve problems while you work with advanced search and reference features
Feature tick iconOffline reading on the mobile app
Feature tick iconChoose a DRM-free eBook or Video every month to keep
Feature tick iconPLUS own as many other DRM-free eBooks or Videos as you like for just €5 each
Feature tick iconExclusive print discounts

Frequently bought together


Python Data Analysis
Python Data Analysis
Read more
Oct 2014348 pages
Full star icon3.9 (16)
eBook
eBook
€28.99€32.99
€41.99
Python 3 Object Oriented Programming
Python 3 Object Oriented Programming
Read more
Jul 2010404 pages
Full star icon4.6 (54)
eBook
eBook
€28.99€32.99
€41.99
Mastering Object-oriented Python
Mastering Object-oriented Python
Read more
Apr 2014634 pages
Full star icon4.2 (13)
eBook
eBook
€25.99€28.99
€37.99
Stars icon
Total121.97
Python Data Analysis
€41.99
Python 3 Object Oriented Programming
€41.99
Mastering Object-oriented Python
€37.99
Total121.97Stars icon

Table of Contents

18 Chapters
The __init__() MethodChevron down iconChevron up icon
The __init__() Method
The implicit superclass – object
The base class object __init__() method
Implementing __init__() in a superclass
Using __init__() to create manifest constants
Leveraging __init__() via a factory function
Implementing __init__() in each subclass
Simple composite objects
Complex composite objects
Stateless objects without __init__()
Some additional class definitions
Multi-strategy __init__()
Yet more __init__() techniques
Summary
Integrating Seamlessly with Python Basic Special MethodsChevron down iconChevron up icon
Integrating Seamlessly with Python Basic Special Methods
The __repr__() and __str__() methods
The __format__() method
The __hash__() method
The __bool__() method
The __bytes__() method
The comparison operator methods
The __del__() method
The __new__() method and immutable objects
The __new__() method and metaclasses
Summary
Attribute Access, Properties, and DescriptorsChevron down iconChevron up icon
Attribute Access, Properties, and Descriptors
Basic attribute processing
Creating properties
Using special methods for attribute access
The __getattribute__() method
Creating descriptors
Summary, design considerations, and trade-offs
The ABCs of Consistent DesignChevron down iconChevron up icon
The ABCs of Consistent Design
Abstract base classes
Base classes and polymorphism
Callables
Containers and collections
Numbers
Some additional abstractions
The abc module
Summary, design considerations, and trade-offs
Using Callables and ContextsChevron down iconChevron up icon
Using Callables and Contexts
Designing with ABC callables
Improving performance
Using functools for memoization
Complexities and the callable API
Managing contexts and the with statement
Defining the __enter__() and __exit__() methods
Context manager as a factory
Summary
Creating Containers and CollectionsChevron down iconChevron up icon
Creating Containers and Collections
ABCs of collections
Examples of special methods
Using the standard library extensions
Creating new kinds of collections
Defining a new kind of sequence
Creating a new kind of mapping
Creating a new kind of set
Summary
Creating NumbersChevron down iconChevron up icon
Creating Numbers
ABCs of numbers
The arithmetic operator's special methods
Creating a numeric class
Computing a numeric hash
Implementing other special methods
Optimization with the in-place operators
Summary
Decorators and Mixins – Cross-cutting AspectsChevron down iconChevron up icon
Decorators and Mixins – Cross-cutting Aspects
Class and meaning
Using built-in decorators
Using standard library mixin classes
Writing a simple function decorator
Parameterizing a decorator
Creating a method function decorator
Creating a class decorator
Adding method functions to a class
Using decorators for security
Summary
Serializing and Saving – JSON, YAML, Pickle, CSV, and XMLChevron down iconChevron up icon
Serializing and Saving – JSON, YAML, Pickle, CSV, and XML
Understanding persistence, class, state, and representation
Filesystem and network considerations
Defining classes to support persistence
Dumping and loading with JSON
Dumping and loading with YAML
Dumping and loading with pickle
Dumping and loading with CSV
Dumping and loading with XML
Summary
Storing and Retrieving Objects via ShelveChevron down iconChevron up icon
Storing and Retrieving Objects via Shelve
Analyzing persistent object use cases
Creating a shelf
Designing shelvable objects
Searching, scanning, and querying
Designing an access layer for shelve
Creating indexes to improve efficiency
Adding yet more index maintenance
The writeback alternative to index updates
Summary
Storing and Retrieving Objects via SQLiteChevron down iconChevron up icon
Storing and Retrieving Objects via SQLite
SQL databases, persistence, and objects
Processing application data with SQL
Mapping Python objects to SQLite BLOB columns
Mapping Python objects to database rows manually
Improving performance with indices
Adding an ORM layer
Querying post objects given a tag string
Improving performance with indices
Summary
Transmitting and Sharing ObjectsChevron down iconChevron up icon
Transmitting and Sharing Objects
Class, state, and representation
Using HTTP and REST to transmit objects
Implementing a REST server – WSGI and mod_wsgi
Using Callable classes for WSGI applications
Creating a secure REST service
Implementing REST with a web application framework
Using a message queue to transmit objects
Summary
Configuration Files and PersistenceChevron down iconChevron up icon
Configuration Files and Persistence
Configuration file use cases
Representation, persistence, state, and usability
Storing the configuration in the INI files
Handling more literals via the eval() variants
Storing the configuration in PY files
Why is exec() a nonproblem?
Using ChainMap for defaults and overrides
Storing the configuration in JSON or YAML files
Storing the configuration in property files
Storing the configuration in XML files – PLIST and others
Summary
The Logging and Warning ModulesChevron down iconChevron up icon
The Logging and Warning Modules
Creating a basic log
Configuration gotcha
Specializing logging for control, debug, audit, and security
Using the warnings module
Advanced logging – the last few messages and network destinations
Summary
Designing for TestabilityChevron down iconChevron up icon
Designing for Testability
Defining and isolating units for testing
Using doctest to define test cases
Using setup and teardown
The TestCase class hierarchy
Using externally defined expected results
Automated integration or performance testing
Summary
Coping With the Command LineChevron down iconChevron up icon
Coping With the Command Line
The OS interface and the command line
Parsing the command line with argparse
Integrating command-line options and environment variables
Customizing the help output
Creating a top-level main() function
Programming In The Large
Additional composite command design patterns
Integrating with other applications
Summary
The Module and Package DesignChevron down iconChevron up icon
The Module and Package Design
Designing a module
Whole module versus module items
Designing a package
Designing a main script and the __main__ module
Designing long-running applications
Organizing code into src, bin, and test
Installing Python modules
Summary
Quality and DocumentationChevron down iconChevron up icon
Quality and Documentation
Writing docstrings for the help() function
Using pydoc for documentation
Better output via the RST markup
Writing effective docstrings
Writing file-level docstrings, including modules and packages
More sophisticated markup techniques
Using Sphinx to produce the documentation
Writing the documentation
Literate programming
Summary

Recommendations for you

Left arrow icon
Debunking C++ Myths
Debunking C++ Myths
Read more
Dec 2024226 pages
Full star icon5 (1)
eBook
eBook
€20.99€23.99
€29.99
Go Recipes for Developers
Go Recipes for Developers
Read more
Dec 2024350 pages
eBook
eBook
€20.99€23.99
€29.99
50 Algorithms Every Programmer Should Know
50 Algorithms Every Programmer Should Know
Read more
Sep 2023538 pages
Full star icon4.5 (68)
eBook
eBook
€26.98€29.99
€37.99
€37.99
Asynchronous Programming with C++
Asynchronous Programming with C++
Read more
Nov 2024424 pages
Full star icon5 (1)
eBook
eBook
€22.99€25.99
€31.99
Modern CMake for C++
Modern CMake for C++
Read more
May 2024504 pages
Full star icon4.7 (12)
eBook
eBook
€26.98€29.99
€37.99
Learn Python Programming
Learn Python Programming
Read more
Nov 2024616 pages
Full star icon5 (1)
eBook
eBook
€20.99€23.99
€29.99
Learn to Code with Rust
Learn to Code with Rust
Read more
Nov 202457hrs 40mins
Video
Video
€56.99
Modern Python Cookbook
Modern Python Cookbook
Read more
Jul 2024818 pages
Full star icon4.9 (21)
eBook
eBook
€28.99€32.99
€41.99
Right arrow icon

Customer reviews

Top Reviews
Rating distribution
Full star iconFull star iconFull star iconFull star iconHalf star icon4.2
(13 Ratings)
5 star53.8%
4 star23.1%
3 star15.4%
2 star7.7%
1 star0%
Filter icon Filter
Top Reviews

Filter reviews by




DavidJul 01, 2017
Full star iconFull star iconFull star iconFull star iconFull star icon5
A+ thanks!
Amazon Verified reviewAmazon
Z-Car ManSep 10, 2015
Full star iconFull star iconFull star iconFull star iconFull star icon5
Although I have worked in all areas of the software lifecycle I was new to Python a year ago. When I heard I would be programming in Python I brought a number of books aimed at the intermediate Python programmer. These were expensive and verbose, and failed to hit the mark. How you can write over 1000 pages and skimp some of the fundamentals is beyond me. My original “C” book, Kernigan and Richie, was just over 200 pages and to the point. Word processors have a lot to answer for! My exposure to OO has been limited (C# for a few months and not explained well) and some wxPython. With the project I am now working on I felt I needed a good book but not a door stop. After researching quite a bit I came across this book and felt it was worth buying. At nearly 600 pages it is not a small book but Steven Lott has kept it to the point and relevant throughout. Many authors on software related subjects could learn a lot on how to structure a book from him.The book is broken down into three main sections: Pythonic Classes via Special Methods, Persistence and Serialization and Testing, Debugging, Deploying, and Maintaining. This makes it more manageable. My OO skills in Python have grown markedly in a week. Highly recommended.
Amazon Verified reviewAmazon
Amazon CustomerDec 17, 2016
Full star iconFull star iconFull star iconFull star iconFull star icon5
As represented; no problems
Amazon Verified reviewAmazon
Mike DriscollMay 16, 2014
Full star iconFull star iconFull star iconFull star iconFull star icon5
This is one of Packt’s best books and also one of the best advanced Python books I’ve read. Let’s take a few moments to talk about the chapters. The book is based around the concept of casino Blackjack, which is kind of an odd topic for a programming book. Regardless, the author uses it and a few other examples to help demonstrate some fairly advanced topics in Python. The first chapter is all about Python __init__() method. It shows the reader how to use __init__ in a superclass and a factory function. The second chapter jumps into all of Python’s basic special methods, such as __repr__, __format__, __hash__, etc. You will learn a lot about metaprogramming in these first couple of chapters. Also note that this book is for Python 3 only.Chapter 3 digs into attributes, properties and descriptors. You will learn how to use __slots__, create immutable objects and work with “eagerly computer attributes”. Chapters 4-6 are about creating and working with callables, contexts and containers. There is information about memoization, creating custom callables, how to use __enter__ / __exit__, working with the collections module (deque, ChainMap, OrderedDict, etc) and more! Chapter 7 talks about creating your own Number, which was something I’d never considered doing. The author admits that you normally wouldn’t do it either, but he does teach the reader some interesting concepts (numeric hashes and in-place operators). Chapter 8 finishes up Part 1 with information on decorators and mixins.Part 2 is all about persistence and serialization. Chapter 9 focuses on JSON, YAML and Pickle. The author favors YAML, so you’ll see a lot of examples using it in this section. Chapter 10 digs into using Python shelve objects and using CRUD operations in relation to complex objects. Chapter 11 is about SQLite. Chapter 12 goes into details of using Python to create a REST server and create WSGI applications. Chapter 13 rounds out Part 2 by covering configuration files using Python, JSON, YAML and PLIST.The last section of the book covers testing, debugging, deploying and maintaining. It jumps right in with chapter 14′s topics on the logging and warning modules. Chapter 15 details how to create unittests and doctests in Python. Chapter 16 covers working with command line options via argparse and creating a main() function. In chapter 17, we learn how to design modules and packages. The last chapter of the book goes over quality assurance and documentation. You will learn a bit about the RST markup language and Sphinx for creating documentation.I found Part 1 to be the most interesting part of the book. I learned a great deal about how classes work, metaprogramming techniques and the difference between callables and functions. I think the book is worth purchasing just for that section! Part 2 has a lot of interesting items too, although I question the author’s insistence on using YAML over JSON. I also do not understand why PLIST was even covered as a configuration file type. Part 3 seemed a bit rushed to me. The chapters aren’t as detailed nor the examples as interesting. On the other hand, I may be a bit jaded as this section was mostly material that I already knew. Overall, I found this to be one of the best Python books I’ve read in the last few years. I would definitely recommend it to anyone who wants to learn more about Python’s internals, especially Python’s “magic methods”.
Amazon Verified reviewAmazon
Louis P.Mar 14, 2016
Full star iconFull star iconFull star iconFull star iconFull star icon5
Great content on python classes and some other topics (serialization, etc.). I like a book better than having to google every step or search the documentation. I don't use it anymore but it was a great way to make progress with python.
Amazon Verified reviewAmazon
  • Arrow left icon Previous
  • 1
  • 2
  • 3
  • Arrow right icon Next

People who bought this also bought

Left arrow icon
50 Algorithms Every Programmer Should Know
50 Algorithms Every Programmer Should Know
Read more
Sep 2023538 pages
Full star icon4.5 (68)
eBook
eBook
€26.98€29.99
€37.99
€37.99
Event-Driven Architecture in Golang
Event-Driven Architecture in Golang
Read more
Nov 2022384 pages
Full star icon4.9 (11)
eBook
eBook
€26.98€29.99
€37.99
The Python Workshop Second Edition
The Python Workshop Second Edition
Read more
Nov 2022600 pages
Full star icon4.6 (22)
eBook
eBook
€27.99€31.99
€38.99
Template Metaprogramming with C++
Template Metaprogramming with C++
Read more
Aug 2022480 pages
Full star icon4.6 (14)
eBook
eBook
€25.99€28.99
€35.99
Domain-Driven Design with Golang
Domain-Driven Design with Golang
Read more
Dec 2022204 pages
Full star icon4.4 (19)
eBook
eBook
€23.99€26.99
€33.99
Right arrow icon

About the authors

Left arrow icon
Profile icon Steven F. Lott
Steven F. Lott
Steven Lott has been programming since computers were large, expensive, and rare. Working for decades in high tech has given him exposure to a lot of ideas and techniques, some bad, but most are helpful to others. Since the 1990s, Steven has been engaged with Python, crafting an array of indispensable tools and applications. His profound expertise has led him to contribute significantly to Packt Publishing, penning notable titles like "Mastering Object-Oriented," "The Modern Python Cookbook," and "Functional Python Programming." A self-proclaimed technomad, Steven's unconventional lifestyle sees him residing on a boat, often anchored along the vibrant east coast of the US. He tries to live by the words “Don't come home until you have a story.”
Read more
See other products by Steven F. Lott
Profile icon Steven F. Lott
Steven F. Lott
LinkedIn icon
Steven Lott has been programming since computers were large, expensive, and rare. Working for decades in high tech has given him exposure to a lot of ideas and techniques, some bad, but most are helpful to others. Since the 1990s, Steven has been engaged with Python, crafting an array of indispensable tools and applications. His profound expertise has led him to contribute significantly to Packt Publishing, penning notable titles like "Mastering Object-Oriented," "The Modern Python Cookbook," and "Functional Python Programming." A self-proclaimed technomad, Steven's unconventional lifestyle sees him residing on a boat, often anchored along the vibrant east coast of the US. He tries to live by the words “Don't come home until you have a story.”
Read more
See other products by Steven F. Lott
Right arrow icon
Getfree access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

What is the digital copy I get with my Print order?Chevron down iconChevron up icon

When you buy any Print edition of our Books, you can redeem (for free) the eBook edition of the Print Book you’ve purchased. This gives you instant access to your book when you make an order via PDF, EPUB or our online Reader experience.

What is the delivery time and cost of print book?Chevron down iconChevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium:Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela
What is custom duty/charge?Chevron down iconChevron up icon

Customs duty are charges levied on goods when they cross international borders. It is a tax that is imposed on imported goods. These duties are charged by special authorities and bodies created by local governments and are meant to protect local industries, economies, and businesses.

Do I have to pay customs charges for the print book order?Chevron down iconChevron up icon

The orders shipped to the countries that are listed under EU27 will not bear custom charges. They are paid by Packt as part of the order.

List of EU27 countries:www.gov.uk/eu-eea:

A custom duty or localized taxes may be applicable on the shipment and would be charged by the recipient country outside of the EU27 which should be paid by the customer and these duties are not included in the shipping charges been charged on the order.

How do I know my custom duty charges?Chevron down iconChevron up icon

The amount of duty payable varies greatly depending on the imported goods, the country of origin and several other factors like the total invoice amount or dimensions like weight, and other such criteria applicable in your country.

For example:

  • If you live inMexico, and the declared value of your ordered items is over $ 50, for you to receive a package, you will have to pay additional import tax of 19% which will be $ 9.50 to the courier service.
  • Whereas if you live inTurkey, and the declared value of your ordered items is over € 22, for you to receive a package, you will have to pay additional import tax of 18% which will be € 3.96 to the courier service.
How can I cancel my order?Chevron down iconChevron up icon

Cancellation Policy for Published Printed Books:

You can cancel any order within 1 hour of placing the order. Simply contact customercare@packt.com with your order details or payment transaction id. If your order has already started the shipment process, we will do our best to stop it. However, if it is already on the way to you then when you receive it, you can contact us at customercare@packt.com using the returns and refund process.

Please understand that Packt Publishing cannot provide refunds or cancel any order except for the cases described in our Return Policy (i.e. Packt Publishing agrees to replace your printed book because it arrives damaged or material defect in book), Packt Publishing will not accept returns.

What is your returns and refunds policy?Chevron down iconChevron up icon

Return Policy:

We want you to be happy with your purchase from Packtpub.com. We will not hassle you with returning print books to us. If the print book you receive from us is incorrect, damaged, doesn't work or is unacceptably late, please contact Customer Relations Team on customercare@packt.com with the order number and issue details as explained below:

  1. If you ordered (eBook, Video or Print Book) incorrectly or accidentally, please contact Customer Relations Team on customercare@packt.com within one hour of placing the order and we will replace/refund you the item cost.
  2. Sadly, if your eBook or Video file is faulty or a fault occurs during the eBook or Video being made available to you, i.e. during download then you should contact Customer Relations Team within 14 days of purchase on customercare@packt.com who will be able to resolve this issue for you.
  3. You will have a choice of replacement or refund of the problem items.(damaged, defective or incorrect)
  4. Once Customer Care Team confirms that you will be refunded, you should receive the refund within 10 to 12 working days.
  5. If you are only requesting a refund of one book from a multiple order, then we will refund you the appropriate single item.
  6. Where the items were shipped under a free shipping offer, there will be no shipping costs to refund.

On the off chance your printed book arrives damaged, with book material defect, contact our Customer Relation Team on customercare@packt.com within 14 days of receipt of the book with appropriate evidence of damage and we will work with you to secure a replacement copy, if necessary. Please note that each printed book you order from us is individually made by Packt's professional book-printing partner which is on a print-on-demand basis.

What tax is charged?Chevron down iconChevron up icon

Currently, no tax is charged on the purchase of any print book (subject to change based on the laws and regulations). A localized VAT fee is charged only to our European and UK customers on eBooks, Video and subscriptions that they buy. GST is charged to Indian customers for eBooks and video purchases.

What payment methods can I use?Chevron down iconChevron up icon

You can pay with the following card types:

  1. Visa Debit
  2. Visa Credit
  3. MasterCard
  4. PayPal
What is the delivery time and cost of print books?Chevron down iconChevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium:Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela

[8]ページ先頭

©2009-2025 Movatter.jp