Buscar..


Introducción

Python se ofrece a sí mismo no solo como un popular lenguaje de scripting, sino que también es compatible con el paradigma de programación orientado a objetos. Las clases describen datos y proporcionan métodos para manipular esos datos, todos incluidos en un solo objeto. Además, las clases permiten la abstracción al separar los detalles de implementación concretos de las representaciones abstractas de datos.

El código que utiliza clases es generalmente más fácil de leer, entender y mantener.

Herencia basica

La herencia en Python se basa en ideas similares utilizadas en otros lenguajes orientados a objetos como Java, C ++, etc. Una nueva clase puede derivarse de una clase existente de la siguiente manera.

class BaseClass(object):
    pass

class DerivedClass(BaseClass):
    pass

BaseClass es la clase ya existente ( principal ), y DerivedClass es la nueva clase ( secundaria ) que hereda (o subclases ) atributos de BaseClass . Nota : a partir de Python 2.2, todas las clases heredan implícitamente de la clase de object , que es la clase base para todos los tipos incorporados.

Definimos una clase de Rectangle principal en el siguiente ejemplo, que se hereda implícitamente del object :

class Rectangle():
    def __init__(self, w, h):
        self.w = w
        self.h = h
    
    def area(self):
        return self.w * self.h
        
    def perimeter(self):
        return 2 * (self.w + self.h)

La clase Rectangle se puede usar como una clase base para definir una clase Square , ya que un cuadrado es un caso especial de rectángulo.

class Square(Rectangle):
    def __init__(self, s):
        # call parent constructor, w and h are both s
        super(Square, self).__init__(s, s)
        self.s = s

La clase Square heredará automáticamente todos los atributos de la clase Rectangle , así como la clase de objeto. super() se utiliza para llamar al __init__() de la clase Rectangle , esencialmente llamando a cualquier método anulado de la clase base. Nota : en Python 3, super() no requiere argumentos.

Los objetos de clase derivados pueden acceder y modificar los atributos de sus clases base:

r.area()
# Output: 12
r.perimeter()  
# Output: 14

s.area()
# Output: 4
s.perimeter()
# Output: 8

Funciones incorporadas que funcionan con herencia.

issubclass(DerivedClass, BaseClass) : devuelve True si DerivedClass es una subclase de BaseClass

isinstance(s, Class) : devuelve True si s es una instancia de Class o cualquiera de las clases derivadas de Class

# subclass check        
issubclass(Square, Rectangle)
# Output: True

# instantiate
r = Rectangle(3, 4)
s = Square(2)

isinstance(r, Rectangle)  
# Output: True
isinstance(r, Square)
# Output: False
# A rectangle is not a square

isinstance(s, Rectangle)
# Output: True
# A square is a rectangle
isinstance(s, Square)
# Output: True

Variables de clase e instancia

Las variables de instancia son únicas para cada instancia, mientras que las variables de clase son compartidas por todas las instancias.

class C:
    x = 2  # class variable

    def __init__(self, y):
        self.y = y  # instance variable

C.x
# 2
C.y
# AttributeError: type object 'C' has no attribute 'y'

c1 = C(3)
c1.x
# 2
c1.y
# 3

c2 = C(4)
c2.x
# 2
c2.y
# 4

Se puede acceder a las variables de clase en las instancias de esta clase, pero la asignación al atributo de clase creará una variable de instancia que sombrea la variable de clase

c2.x = 4
c2.x
# 4
C.x
# 2

Tenga en cuenta que la mutación de variables de clase de instancias puede llevar a algunas consecuencias inesperadas.

class D:
    x = []
    def __init__(self, item):
        self.x.append(item)  # note that this is not an assigment!

d1 = D(1)
d2 = D(2)

d1.x
# [1, 2]
d2.x
# [1, 2]
D.x
# [1, 2]

Métodos enlazados, no enlazados y estáticos.

La idea de métodos enlazados y no enlazados se eliminó en Python 3 . En Python 3 cuando declara un método dentro de una clase, está usando una palabra clave def , creando así un objeto de función. Esta es una función regular, y la clase circundante funciona como su espacio de nombres. En el siguiente ejemplo, declaramos el método f dentro de la clase A , y se convierte en una función Af :

Python 3.x 3.0
class A(object):
    def f(self, x):
        return 2 * x
A.f
# <function A.f at ...>  (in Python 3.x)

En Python 2, el comportamiento fue diferente: los objetos de función dentro de la clase fueron reemplazados implícitamente por objetos de tipo instancemethod , que se denominaron métodos no vinculados porque no estaban vinculados a ninguna instancia de clase en particular. Fue posible acceder a la función subyacente utilizando la propiedad .__func__ .

Python 2.x 2.3
A.f
# <unbound method A.f>   (in Python 2.x)
A.f.__class__
# <type 'instancemethod'>
A.f.__func__
# <function f at ...>

Los últimos comportamientos se confirman mediante inspección: los métodos se reconocen como funciones en Python 3, mientras que la distinción se mantiene en Python 2.

Python 3.x 3.0
import inspect

inspect.isfunction(A.f)
# True
inspect.ismethod(A.f)
# False
Python 2.x 2.3
import inspect

inspect.isfunction(A.f)
# False
inspect.ismethod(A.f)
# True

En ambas versiones de Python function / method Af se puede llamar directamente, siempre que pase una instancia de clase A como primer argumento.

A.f(1, 7)
# Python 2: TypeError: unbound method f() must be called with
#                      A instance as first argument (got int instance instead) 
# Python 3: 14   
a = A()
A.f(a, 20)
# Python 2 & 3: 40

Supongamos ahora que a es una instancia de la clase A , lo que es af entonces? Bueno, intuitivamente este debe ser el mismo método f de clase A , sólo que debe de alguna manera "saber" que se aplica al objeto a - método en Python esto se llama unida a a .

Los detalles esenciales son los siguientes: writing af invoca el método magic __getattribute__ de a , que primero verifica si a tiene un atributo llamado f (no lo hace), luego verifica la clase A si contiene un método con ese nombre (lo hace), y crea un nuevo objeto m del method de tipo que tiene la referencia al Af original en m.__func__ , y una referencia al objeto a en m.__self__ . Cuando este objeto se llama como una función, simplemente hace lo siguiente: m(...) => m.__func__(m.__self__, ...) . Por lo tanto, este objeto se denomina método enlazado porque, cuando se invoca, sabe que debe proporcionar el objeto al que estaba vinculado como primer argumento. (Estas cosas funcionan de la misma manera en Python 2 y 3).

a = A()
a.f
# <bound method A.f of <__main__.A object at ...>>
a.f(2)
# 4

# Note: the bound method object a.f is recreated *every time* you call it:
a.f is a.f  # False
# As a performance optimization you can store the bound method in the object's
# __dict__, in which case the method object will remain fixed:
a.f = a.f
a.f is a.f  # True

Finalmente, Python tiene métodos de clase y métodos estáticos , tipos especiales de métodos. Los métodos de clase funcionan de la misma manera que los métodos regulares, excepto que cuando se invocan en un objeto, se unen a la clase del objeto en lugar de al objeto. Así m.__self__ = type(a) . Cuando llama a dicho método enlazado, pasa la clase de a como primer argumento. Los métodos estáticos son incluso más simples: no vinculan nada en absoluto, y simplemente devuelven la función subyacente sin ninguna transformación.

class D(object):
    multiplier = 2

    @classmethod
    def f(cls, x):
        return cls.multiplier * x

    @staticmethod
    def g(name):
        print("Hello, %s" % name)

D.f
# <bound method type.f of <class '__main__.D'>>
D.f(12)
# 24
D.g
# <function D.g at ...>
D.g("world")
# Hello, world

Tenga en cuenta que los métodos de clase están vinculados a la clase incluso cuando se accede a ellos en la instancia:

d = D()
d.multiplier = 1337
(D.multiplier, d.multiplier)
# (2, 1337)
d.f
# <bound method D.f of <class '__main__.D'>>
d.f(10)
# 20

Vale la pena señalar que en el nivel más bajo, las funciones, los métodos, los métodos estáticos, etc., son en realidad descriptores que invocan los métodos especiales __get__ , __set __ y opcionalmente __del__ . Para más detalles sobre métodos de clase y métodos estáticos:

Clases de estilo nuevo vs. estilo antiguo

Python 2.x 2.2.0

Las clases de nuevo estilo se introdujeron en Python 2.2 para unificar clases y tipos . Heredan del tipo de object nivel superior. Una clase de nuevo estilo es un tipo definido por el usuario y es muy similar a los tipos incorporados.

# new-style class
class New(object):
    pass

# new-style instance
new = New()

new.__class__
# <class '__main__.New'>
type(new)
# <class '__main__.New'>
issubclass(New, object)
# True

Las clases de estilo antiguo no heredan de object . Las instancias de estilo antiguo siempre se implementan con un tipo de instance incorporado.

# old-style class
class Old:
    pass

# old-style instance
old = Old()

old.__class__
# <class __main__.Old at ...>
type(old)
# <type 'instance'>
issubclass(Old, object)
# False
Python 3.x 3.0.0

En Python 3, se eliminaron las clases de estilo antiguo.

Las clases de nuevo estilo en Python 3 heredan implícitamente del object , por lo que ya no es necesario especificar MyClass(object) .

class MyClass:
    pass

my_inst = MyClass()

type(my_inst)
# <class '__main__.MyClass'>
my_inst.__class__
# <class '__main__.MyClass'>
issubclass(MyClass, object)
# True

Valores por defecto para variables de instancia

Si la variable contiene un valor de un tipo inmutable (por ejemplo, una cadena), está bien asignar un valor predeterminado como este

class Rectangle(object):
    def __init__(self, width, height, color='blue'):
        self.width = width
        self.height = height
        self.color = color
    
    def area(self):
        return self.width  * self.height 

# Create some instances of the class
default_rectangle = Rectangle(2, 3)
print(default_rectangle.color) # blue

red_rectangle = Rectangle(2, 3, 'red')
print(red_rectangle.color) # red

Hay que tener cuidado al inicializar objetos mutables como listas en el constructor. Considere el siguiente ejemplo:

class Rectangle2D(object):
    def __init__(self, width, height, pos=[0,0], color='blue'):  
        self.width = width
        self.height = height
        self.pos = pos
        self.color = color

r1 = Rectangle2D(5,3)
r2 = Rectangle2D(7,8)
r1.pos[0] = 4
r1.pos # [4, 0]
r2.pos # [4, 0] r2's pos has changed as well

Este comportamiento se debe al hecho de que en Python los parámetros predeterminados están vinculados en la ejecución de la función y no en la declaración de la función. Para obtener una variable de instancia predeterminada que no se comparte entre las instancias, se debe usar una construcción como esta:

class Rectangle2D(object):
    def __init__(self, width, height, pos=None, color='blue'):  
        self.width = width
        self.height = height
        self.pos = pos or [0, 0] # default value is [0, 0]
        self.color = color

r1 = Rectangle2D(5,3)
r2 = Rectangle2D(7,8)
r1.pos[0] = 4
r1.pos # [4, 0]
r2.pos # [0, 0] r2's pos hasn't changed 

Consulte también Argumentos predeterminados mutables y “Menos asombro” y el Argumento predeterminado mutable .

Herencia múltiple

Python utiliza el algoritmo de linealización C3 para determinar el orden en el que resolver los atributos de clase, incluidos los métodos. Esto se conoce como Orden de resolución de métodos (MRO).

Aquí hay un ejemplo simple:

class Foo(object):
    foo = 'attr foo of Foo'
    

class Bar(object):
    foo = 'attr foo of Bar' # we won't see this.
    bar = 'attr bar of Bar'

class FooBar(Foo, Bar):
    foobar = 'attr foobar of FooBar'

Ahora si creamos una instancia de FooBar, si buscamos el atributo foo, veremos que el atributo de Foo se encuentra primero

fb = FooBar()

y

>>> fb.foo
'attr foo of Foo'

Aquí está el MRO de FooBar:

>>> FooBar.mro()
[<class '__main__.FooBar'>, <class '__main__.Foo'>, <class '__main__.Bar'>, <type 'object'>]

Se puede decir simplemente que el algoritmo MRO de Python es

  1. Profundidad primero (por ejemplo, FooBar luego Foo ) a menos que
  2. un padre compartido ( object ) está bloqueado por un hijo ( Bar ) y
  3. No se permiten relaciones circulares.

Es decir, por ejemplo, Bar no puede heredar de FooBar, mientras que FooBar hereda de Bar.

Para un ejemplo completo en Python, vea la entrada de wikipedia .

Otra característica poderosa en la herencia es super . super puede obtener características de clases para padres.

class Foo(object):
    def foo_method(self):
        print "foo Method"

class Bar(object):
    def bar_method(self):
        print "bar Method"

class FooBar(Foo, Bar):
    def foo_method(self):
        super(FooBar, self).foo_method()

Herencia múltiple con el método init de clase, cuando cada clase tiene su propio método init, entonces intentamos obtener una inercia múltiple, luego solo se llama al método init de la clase que se hereda primero.

para el siguiente ejemplo, solo se llama al método de inicio de clase Foo que no se llama a la clase de barra de inicio

    class Foo(object):
        def __init__(self):
            print "foo init"

    class Bar(object):
        def __init__(self):
            print "bar init"

    class FooBar(Foo, Bar):
        def __init__(self):
            print "foobar init"
            super(FooBar, self).__init__()

    a = FooBar()

Salida:

    foobar init
    foo init

Pero eso no significa que la clase Bar no sea hereditaria. La instancia de la clase FooBar final también es una instancia de la clase Bar y la clase Foo .

print isinstance(a,FooBar)
print isinstance(a,Foo)
print isinstance(a,Bar) 

Salida:

True
True
True

Descriptores y búsquedas punteadas

Los descriptores son objetos que son (generalmente) atributos de clases y que tienen cualquiera de los métodos especiales __get__ , __set__ o __delete__ .

Los descriptores de datos tienen cualquiera de __set__ , o __delete__

Estos pueden controlar la búsqueda de puntos en una instancia y se utilizan para implementar funciones, staticmethod , classmethod y property . Una búsqueda de puntos (por ejemplo, la instancia foo de la clase Foo busca la bar atributos, es decir, foo.bar ) utiliza el siguiente algoritmo:

  1. bar se mira en la clase, Foo . Si está allí y es un descriptor de datos , entonces se usa el descriptor de datos. Así es como la property puede controlar el acceso a los datos en una instancia, y las instancias no pueden anular esto. Si un descriptor de datos no está allí, entonces

  2. bar se mira en la instancia __dict__ . Es por esto que podemos anular o bloquear los métodos que se invocan desde una instancia con una búsqueda de puntos. Si existe una bar en la instancia, se utiliza. Si no, entonces nosotros

  3. Busca en la clase Foo para bar . Si es un Descriptor , entonces se usa el protocolo del descriptor. Así es como se implementan las funciones (en este contexto, los métodos classmethod ), el classmethod y el staticmethod . De lo contrario, simplemente devuelve el objeto allí, o hay un AttributeError

Métodos de clase: inicializadores alternos

Los métodos de clase presentan formas alternativas para construir instancias de clases. Para ilustrar, veamos un ejemplo.

Supongamos que tenemos una clase de Person relativamente simple:

class Person(object):

    def __init__(self, first_name, last_name, age):
        self.first_name = first_name
        self.last_name = last_name
        self.age = age
        self.full_name = first_name + " " + last_name
    
    def greet(self):
        print("Hello, my name is " + self.full_name + ".")

Podría ser útil tener una forma de crear instancias de esta clase especificando un nombre completo en lugar del nombre y apellido por separado. Una forma de hacer esto sería tener el last_name como un parámetro opcional, y suponiendo que si no se da, pasamos el nombre completo en:

class Person(object):

    def __init__(self, first_name, age, last_name=None):
        if last_name is None:
            self.first_name, self.last_name = first_name.split(" ", 2)
        else:
            self.first_name = first_name
            self.last_name = last_name
        
        self.full_name = self.first_name + " " + self.last_name
        self.age = age

    def greet(self):
        print("Hello, my name is " + self.full_name + ".")

Sin embargo, hay dos problemas principales con este bit de código:

  1. Los parámetros first_name y last_name ahora son last_name , ya que puede ingresar un nombre completo para first_name . Además, si hay más casos y / o más parámetros que tienen este tipo de flexibilidad, la ramificación if / elif / else puede ser molesta rápidamente.

  2. No es tan importante, pero aún así vale la pena señalar: ¿y si last_name es None , pero first_name no se divide en dos o más cosas a través de los espacios? Tenemos otra capa de validación de entrada y / o manejo de excepciones ...

Introduzca los métodos de clase. En lugar de tener un solo inicializador, crearemos un inicializador separado, llamado from_full_name , y lo classmethod decorador de classmethod (incorporado).

class Person(object):

    def __init__(self, first_name, last_name, age):
        self.first_name = first_name
        self.last_name = last_name
        self.age = age
        self.full_name = first_name + " " + last_name
    
    @classmethod
    def from_full_name(cls, name, age):
        if " " not in name:
            raise ValueError
        first_name, last_name = name.split(" ", 2)
        return cls(first_name, last_name, age)
    
    def greet(self):
        print("Hello, my name is " + self.full_name + ".")

Note cls lugar de self como el primer argumento de from_full_name . Los métodos de clase se aplican a la clase general, no a una instancia de una clase dada (que es lo que el self generalmente denota). Por lo tanto, si cls es nuestra Person de clase, entonces el valor devuelto por la from_full_name método de clase es Person(first_name, last_name, age) , que utiliza la Person 's __init__ para crear una instancia de la Person de clase. En particular, si tuviéramos que hacer una subclase Employee of Person , from_full_name también funcionaría en la clase Employee .

Para mostrar que esto funciona como se esperaba, __init__ instancias de Person de más de una manera sin la bifurcación en __init__ :

In [2]: bob = Person("Bob", "Bobberson", 42)

In [3]: alice = Person.from_full_name("Alice Henderson", 31)

In [4]: bob.greet()
Hello, my name is Bob Bobberson.

In [5]: alice.greet()
Hello, my name is Alice Henderson.

Otras referencias:

Composición de la clase

La composición de clase permite relaciones explícitas entre objetos. En este ejemplo, las personas viven en ciudades que pertenecen a países. La composición permite a las personas acceder al número de todas las personas que viven en su país:

class Country(object):
    def __init__(self):
        self.cities=[]
        
    def addCity(self,city):
        self.cities.append(city)
        

class City(object):
    def __init__(self, numPeople):
        self.people = []
        self.numPeople = numPeople
        
        
    def addPerson(self, person):
        self.people.append(person)
    
    def join_country(self,country):
        self.country = country
        country.addCity(self)
        
        for i in range(self.numPeople):
                person(i).join_city(self)
  

class Person(object):
    def __init__(self, ID):
        self.ID=ID

    def join_city(self, city):
        self.city = city
        city.addPerson(self)
        
    def people_in_my_country(self):
        x= sum([len(c.people) for c in self.city.country.cities])
        return x
        
US=Country()
NYC=City(10).join_country(US)
SF=City(5).join_country(US)

print(US.cities[0].people[0].people_in_my_country())

# 15

Parche de mono

En este caso, "parche de mono" significa agregar una nueva variable o método a una clase después de que se haya definido. Por ejemplo, digamos que definimos la clase A como

class A(object):
    def __init__(self, num):
        self.num = num

    def __add__(self, other):
        return A(self.num + other.num)

Pero ahora queremos agregar otra función más adelante en el código. Supongamos que esta función es la siguiente.

def get_num(self):
    return self.num

Pero, ¿cómo añadimos esto como un método en A ? Eso es simple, simplemente colocamos esa función en A con una declaración de asignación.

A.get_num = get_num

¿Por qué funciona esto? Porque las funciones son objetos como cualquier otro objeto, y los métodos son funciones que pertenecen a la clase.

La función get_num estará disponible para todos los existentes (ya creados) así como para las nuevas instancias de A

Estas adiciones están disponibles en todas las instancias de esa clase (o sus subclases) automáticamente. Por ejemplo:

foo = A(42)

A.get_num = get_num

bar = A(6);

foo.get_num() # 42

bar.get_num() # 6

Tenga en cuenta que, a diferencia de otros idiomas, esta técnica no funciona para ciertos tipos integrados y no se considera un buen estilo.

Listado de todos los miembros de la clase

La función dir() se puede usar para obtener una lista de los miembros de una clase:

dir(Class)

Por ejemplo:

>>> dir(list)
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

Es común buscar solo miembros "no mágicos". Esto se puede hacer usando una comprensión simple que enumera los miembros con nombres que no comienzan con __ :

>>> [m for m in dir(list) if not m.startswith('__')]
['append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

Advertencias:

Las clases pueden definir un __dir__() . Si ese método existe, se llama a dir() a __dir__() , de lo contrario, Python intentará crear una lista de miembros de la clase. Esto significa que la función dir puede tener resultados inesperados. Dos citas de importancia de la documentación oficial de python :

Si el objeto no proporciona dir (), la función intenta recopilar información del atributo dict del objeto, si está definido, y de su tipo de objeto. La lista resultante no está necesariamente completa, y puede ser inexacta cuando el objeto tiene un getattr () personalizado.

Nota: dado que dir () se proporciona principalmente como una conveniencia para su uso en una solicitud interactiva, intenta proporcionar un conjunto interesante de nombres más de lo que trata de proporcionar un conjunto de nombres definido de manera rigurosa o consistente, y su comportamiento detallado puede cambiar lanzamientos Por ejemplo, los atributos de metaclase no están en la lista de resultados cuando el argumento es una clase.

Introducción a las clases

Una clase, funciona como una plantilla que define las características básicas de un objeto en particular. Aquí hay un ejemplo:

class Person(object):
     """A simple class."""                            # docstring
     species = "Homo Sapiens"                         # class attribute

     def __init__(self, name):                        # special method
         """This is the initializer. It's a special
         method (see below).
         """
         self.name = name                             # instance attribute

     def __str__(self):                               # special method
         """This method is run when Python tries 
         to cast the object to a string. Return 
         this string when using print(), etc.
         """
         return self.name

     def rename(self, renamed):                       # regular method
         """Reassign and print the name attribute."""
         self.name = renamed
         print("Now my name is {}".format(self.name))

Hay algunas cosas que se deben tener en cuenta al observar el ejemplo anterior.

  1. La clase se compone de atributos (datos) y métodos (funciones).
  2. Los atributos y métodos se definen simplemente como variables y funciones normales.
  3. Como se indica en la cadena de documentación correspondiente, el __init__() se llama inicializador . Es equivalente al constructor en otros lenguajes orientados a objetos, y es el método que se ejecuta por primera vez cuando crea un nuevo objeto o una nueva instancia de la clase.
  4. Los atributos que se aplican a toda la clase se definen primero y se denominan atributos de clase .
  5. Los atributos que se aplican a una instancia específica de una clase (un objeto) se denominan atributos de instancia . Generalmente se definen dentro de __init__() ; esto no es necesario, pero se recomienda (ya que los atributos definidos fuera de __init__() corren el riesgo de ser accedidos antes de que se definan).
  6. Cada método, incluido en la definición de clase, pasa el objeto en cuestión como su primer parámetro. La palabra self se usa para este parámetro (el uso de self es en realidad por convención, ya que la palabra self no tiene un significado inherente en Python, pero esta es una de las convenciones más respetadas de Python, y siempre se debe seguir).
  7. Aquellos que están acostumbrados a la programación orientada a objetos en otros lenguajes pueden sorprenderse por algunas cosas. Una es que Python no tiene un concepto real de elementos private , por lo que todo, de manera predeterminada, imita el comportamiento de la palabra clave public C ++ / Java. Para obtener más información, consulte el ejemplo "Miembros de la clase privada" en esta página.
  8. Algunos de los métodos de la clase tienen la siguiente forma: __functionname__(self, other_stuff) . Todos estos métodos se denominan "métodos mágicos" y son una parte importante de las clases en Python. Por ejemplo, la sobrecarga de operadores en Python se implementa con métodos mágicos. Para más información, consulte la documentación relevante .

Ahora vamos a hacer algunos ejemplos de nuestra clase de Person !

>>> # Instances
>>> kelly = Person("Kelly")
>>> joseph = Person("Joseph")
>>> john_doe = Person("John Doe")

Actualmente tenemos tres objetos Person , kelly , joseph y john_doe .

Podemos acceder a los atributos de la clase desde cada instancia utilizando el operador de punto . Note nuevamente la diferencia entre los atributos de clase e instancia:

>>> # Attributes
>>> kelly.species
'Homo Sapiens'
>>> john_doe.species
'Homo Sapiens'
>>> joseph.species
'Homo Sapiens'
>>> kelly.name
'Kelly'
>>> joseph.name
'Joseph'

Podemos ejecutar los métodos de la clase usando el mismo operador de punto . :

>>> # Methods
>>> john_doe.__str__()
'John Doe'
>>>  print(john_doe)
'John Doe'
>>>  john_doe.rename("John")
'Now my name is John'

Propiedades

Las clases de Python admiten propiedades , que parecen variables de objetos normales, pero con la posibilidad de adjuntar comportamiento y documentación personalizados.

class MyClass(object):

    def __init__(self):
       self._my_string = ""
    
    @property
    def string(self):
        """A profoundly important string."""
        return self._my_string

    @string.setter
    def string(self, new_value):
        assert isinstance(new_value, str), \
               "Give me a string, not a %r!" % type(new_value)
        self._my_string = new_value

    @string.deleter
    def x(self):
        self._my_string = None

De la clase de objeto MyClass parecerá tener tiene una propiedad .string , sin embargo su comportamiento está ahora estrictamente controlado:

mc = MyClass()
mc.string = "String!"
print(mc.string)
del mc.string

Además de la sintaxis útil que se mencionó anteriormente, la sintaxis de la propiedad permite la validación u otros aumentos a esos atributos. Esto podría ser especialmente útil con las API públicas, donde se debe brindar un nivel de ayuda al usuario.

Otro uso común de las propiedades es permitir que la clase presente "atributos virtuales", atributos que no se almacenan en realidad, sino que se calculan solo cuando se solicitan.

class Character(object):
    def __init__(name, max_hp):
        self._name = name
        self._hp = max_hp
        self._max_hp = max_hp

    # Make hp read only by not providing a set method
    @property
    def hp(self):
        return self._hp

    # Make name read only by not providing a set method
    @property
    def name(self):
        return self.name

    def take_damage(self, damage):
        self.hp -= damage
        self.hp = 0 if self.hp <0 else self.hp

    @property
    def is_alive(self):
        return self.hp != 0

    @property
    def is_wounded(self):
        return self.hp < self.max_hp if self.hp > 0 else False

    @property
    def is_dead(self):
        return not self.is_alive

bilbo = Character('Bilbo Baggins', 100)
bilbo.hp
# out : 100
bilbo.hp = 200        
# out : AttributeError: can't set attribute
# hp attribute is read only.

bilbo.is_alive
# out : True
bilbo.is_wounded
# out : False
bilbo.is_dead
# out : False

bilbo.take_damage( 50 )

bilbo.hp
# out : 50

bilbo.is_alive
# out : True
bilbo.is_wounded
# out : True
bilbo.is_dead
# out : False

bilbo.take_damage( 50 )
bilbo.hp
# out : 0

bilbo.is_alive
# out : False
bilbo.is_wounded
# out : False
bilbo.is_dead
# out : True

Clase de singleton

Un singleton es un patrón que restringe la creación de instancias de una clase a una instancia / objeto. Para más información sobre los patrones de diseño singleton de python, consulte aquí .

class Singleton:
    def __new__(cls):
        try:
            it = cls.__it__
        except AttributeError:
            it = cls.__it__ = object.__new__(cls)
        return it

    def __repr__(self):
        return '<{}>'.format(self.__class__.__name__.upper())

    def __eq__(self, other):
         return other is self

Otro método es decorar tu clase. Siguiendo el ejemplo de esta respuesta, crea una clase Singleton:

class Singleton:
    """
    A non-thread-safe helper class to ease implementing singletons.
    This should be used as a decorator -- not a metaclass -- to the
    class that should be a singleton.

    The decorated class can define one `__init__` function that
    takes only the `self` argument. Other than that, there are
    no restrictions that apply to the decorated class.

    To get the singleton instance, use the `Instance` method. Trying
    to use `__call__` will result in a `TypeError` being raised.

    Limitations: The decorated class cannot be inherited from.

    """

    def __init__(self, decorated):
        self._decorated = decorated

    def Instance(self):
        """
        Returns the singleton instance. Upon its first call, it creates a
        new instance of the decorated class and calls its `__init__` method.
        On all subsequent calls, the already created instance is returned.

        """
        try:
            return self._instance
        except AttributeError:
            self._instance = self._decorated()
            return self._instance

    def __call__(self):
        raise TypeError('Singletons must be accessed through `Instance()`.')

    def __instancecheck__(self, inst):
        return isinstance(inst, self._decorated)

Para usar puedes usar el método de Instance

@Singleton
class Single:
    def __init__(self):
        self.name=None
        self.val=0
    def getName(self):
        print(self.name)

x=Single.Instance()
y=Single.Instance()
x.name='I\'m single'
x.getName() # outputs I'm single
y.getName() # outputs I'm single


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow